From f62ae1288a776527c7f7ba3951531fbd07bc63da Mon Sep 17 00:00:00 2001 From: Klement Sekera Date: Tue, 11 Oct 2016 11:47:09 +0200 Subject: refactor test framework Change-Id: I31da3b1857b6399f9899276a2d99cdd19436296c Signed-off-by: Klement Sekera Signed-off-by: Matej Klotton Signed-off-by: Jan Gelety Signed-off-by: Juraj Sloboda --- test/vpp_interface.py | 240 ++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 240 insertions(+) create mode 100644 test/vpp_interface.py (limited to 'test/vpp_interface.py') diff --git a/test/vpp_interface.py b/test/vpp_interface.py new file mode 100644 index 00000000..c596ab5a --- /dev/null +++ b/test/vpp_interface.py @@ -0,0 +1,240 @@ +from abc import abstractmethod, ABCMeta +import socket +from logging import info, error +from scapy.layers.l2 import Ether, ARP + +from scapy.layers.inet6 import IPv6, ICMPv6ND_NS, ICMPv6ND_NA, ICMPv6NDOptSrcLLAddr, ICMPv6NDOptDstLLAddr + + +class VppInterface(object): + """ + Generic VPP interface + """ + __metaclass__ = ABCMeta + + @property + def sw_if_index(self): + """Interface index assigned by VPP""" + return self._sw_if_index + + @property + def remote_mac(self): + """MAC-address of the remote interface "connected" to this interface""" + return self._remote_mac + + @property + def local_mac(self): + """MAC-address of the VPP interface""" + return self._local_mac + + @property + def local_ip4(self): + """Local IPv4 address on VPP interface (string)""" + return self._local_ip4 + + @property + def local_ip4n(self): + """Local IPv4 address - raw, suitable as API parameter""" + return self._local_ip4n + + @property + def remote_ip4(self): + """IPv4 address of remote peer "connected" to this interface""" + return self._remote_ip4 + + @property + def remote_ip4n(self): + """IPv4 address of remote peer - raw, suitable as API parameter""" + return self._remote_ip4n + + @property + def local_ip6(self): + """Local IPv6 address on VPP interface (string)""" + return self._local_ip6 + + @property + def local_ip6n(self): + """Local IPv6 address - raw, suitable as API parameter""" + return self._local_ip6n + + @property + def remote_ip6(self): + """IPv6 address of remote peer "connected" to this interface""" + return self._remote_ip6 + + @property + def remote_ip6n(self): + """IPv6 address of remote peer - raw, suitable as API parameter""" + return self._remote_ip6n + + @property + def name(self): + """Name of the interface""" + return self._name + + @property + def dump(self): + """Raw result of sw_interface_dump for this interface""" + return self._dump + + @property + def test(self): + """Test case creating this interface""" + return self._test + + def post_init_setup(self): + """Additional setup run after creating an interface object""" + self._remote_mac = "02:00:00:00:ff:%02x" % self.sw_if_index + + self._local_ip4 = "172.16.%u.1" % self.sw_if_index + self._local_ip4n = socket.inet_pton(socket.AF_INET, self.local_ip4) + self._remote_ip4 = "172.16.%u.2" % self.sw_if_index + self._remote_ip4n = socket.inet_pton(socket.AF_INET, self.remote_ip4) + + self._local_ip6 = "fd01:%u::1" % self.sw_if_index + self._local_ip6n = socket.inet_pton(socket.AF_INET6, self.local_ip6) + self._remote_ip6 = "fd01:%u::2" % self.sw_if_index + self._remote_ip6n = socket.inet_pton(socket.AF_INET6, self.remote_ip6) + + r = self.test.vapi.sw_interface_dump() + for intf in r: + if intf.sw_if_index == self.sw_if_index: + self._name = intf.interface_name.split(b'\0', 1)[0] + self._local_mac = ':'.join(intf.l2_address.encode('hex')[i:i + 2] + for i in range(0, 12, 2)) + self._dump = intf + break + else: + raise Exception( + "Could not find interface with sw_if_index %d " + "in interface dump %s" % + (self.sw_if_index, repr(r))) + + @abstractmethod + def __init__(self, test, index): + self._test = test + self.post_init_setup() + info("New %s, MAC=%s, remote_ip4=%s, local_ip4=%s" % + (self.__name__, self.remote_mac, self.remote_ip4, self.local_ip4)) + + def config_ip4(self): + """Configure IPv4 address on the VPP interface""" + addr = self.local_ip4n + addr_len = 24 + self.test.vapi.sw_interface_add_del_address( + self.sw_if_index, addr, addr_len) + + def config_ip6(self): + """Configure IPv6 address on the VPP interface""" + addr = self._local_ip6n + addr_len = 64 + self.test.vapi.sw_interface_add_del_address( + self.sw_if_index, addr, addr_len, is_ipv6=1) + + def disable_ipv6_ra(self): + """Configure IPv6 RA suppress on the VPP interface""" + self.test.vapi.sw_interface_ra_suppress(self.sw_if_index) + + def create_arp_req(self): + """Create ARP request applicable for this interface""" + return (Ether(dst="ff:ff:ff:ff:ff:ff", src=self.remote_mac) / + ARP(op=ARP.who_has, pdst=self.local_ip4, + psrc=self.remote_ip4, hwsrc=self.remote_mac)) + + def create_ndp_req(self): + return (Ether(dst="ff:ff:ff:ff:ff:ff", src=self.remote_mac) / + IPv6(src=self.remote_ip6, dst=self.local_ip6) / + ICMPv6ND_NS(tgt=self.local_ip6) / + ICMPv6NDOptSrcLLAddr(lladdr=self.remote_mac)) + + def resolve_arp(self, pg_interface=None): + """Resolve ARP using provided packet-generator interface + + :param pg_interface: interface used to resolve, if None then this + interface is used + + """ + if pg_interface is None: + pg_interface = self + info("Sending ARP request for %s on port %s" % + (self.local_ip4, pg_interface.name)) + arp_req = self.create_arp_req() + pg_interface.add_stream(arp_req) + pg_interface.enable_capture() + self.test.pg_start() + info(self.test.vapi.cli("show trace")) + arp_reply = pg_interface.get_capture() + if arp_reply is None or len(arp_reply) == 0: + info("No ARP received on port %s" % pg_interface.name) + return + arp_reply = arp_reply[0] + # Make Dot1AD packet content recognizable to scapy + if arp_reply.type == 0x88a8: + arp_reply.type = 0x8100 + arp_reply = Ether(str(arp_reply)) + try: + if arp_reply[ARP].op == ARP.is_at: + info("VPP %s MAC address is %s " % + (self.name, arp_reply[ARP].hwsrc)) + self._local_mac = arp_reply[ARP].hwsrc + else: + info("No ARP received on port %s" % pg_interface.name) + except: + error("Unexpected response to ARP request:") + error(arp_reply.show()) + raise + + def resolve_ndp(self, pg_interface=None): + """Resolve NDP using provided packet-generator interface + + :param pg_interface: interface used to resolve, if None then this + interface is used + + """ + if pg_interface is None: + pg_interface = self + info("Sending NDP request for %s on port %s" % + (self.local_ip6, pg_interface.name)) + ndp_req = self.create_ndp_req() + pg_interface.add_stream(ndp_req) + pg_interface.enable_capture() + self.test.pg_start() + info(self.test.vapi.cli("show trace")) + ndp_reply = pg_interface.get_capture() + if ndp_reply is None or len(ndp_reply) == 0: + info("No NDP received on port %s" % pg_interface.name) + return + ndp_reply = ndp_reply[0] + # Make Dot1AD packet content recognizable to scapy + if ndp_reply.type == 0x88a8: + ndp_reply.type = 0x8100 + ndp_reply = Ether(str(ndp_reply)) + try: + ndp_na = ndp_reply[ICMPv6ND_NA] + opt = ndp_na[ICMPv6NDOptDstLLAddr] + info("VPP %s MAC address is %s " % + (self.name, opt.lladdr)) + self._local_mac = opt.lladdr + except: + error("Unexpected response to NDP request:") + error(ndp_reply.show()) + raise + + def admin_up(self): + """ Put interface ADMIN-UP """ + self.test.vapi.sw_interface_set_flags(self.sw_if_index, admin_up_down=1) + + def add_sub_if(self, sub_if): + """ + Register a sub-interface with this interface + + :param sub_if: sub-interface + + """ + if not hasattr(self, 'sub_if'): + self.sub_if = sub_if + else: + if isinstance(self.sub_if, list): + self.sub_if.append(sub_if) + else: + self.sub_if = sub_if -- cgit 1.2.3-korg From 8fe8cc21d1e389d8e971a303e53c9e703aaaa0e0 Mon Sep 17 00:00:00 2001 From: Neale Ranns Date: Tue, 1 Nov 2016 10:05:08 +0000 Subject: MPLS Exp-null Tests Add some 'make test' unit tests for MPLS explicit NULL label handling. Fix the stacking of the MPLS load-balance result form the lookup onto the IPx lookup object. Change-Id: I890d1221b8e3dea99bcc714ed9d0154a5f602c52 Signed-off-by: Neale Ranns --- test/framework.py | 3 +- test/test_mpls.py | 209 +++++++++++++++++++++++++++++++++++++++++ test/vpp_interface.py | 17 ++++ test/vpp_papi_provider.py | 23 +++++ vnet/vnet/dpo/dpo.c | 47 ++++----- vnet/vnet/dpo/lookup_dpo.c | 6 +- vnet/vnet/dpo/mpls_label_dpo.c | 11 ++- vnet/vnet/fib/fib_entry_src.c | 17 +--- vnet/vnet/fib/fib_test.c | 4 +- 9 files changed, 289 insertions(+), 48 deletions(-) create mode 100644 test/test_mpls.py (limited to 'test/vpp_interface.py') diff --git a/test/framework.py b/test/framework.py index 5f75e010..8c39701b 100644 --- a/test/framework.py +++ b/test/framework.py @@ -113,7 +113,8 @@ class VppTestCase(unittest.TestCase): cls.set_debug_flags(d) cls.vpp_bin = os.getenv('VPP_TEST_BIN', "vpp") cls.plugin_path = os.getenv('VPP_TEST_PLUGIN_PATH') - cls.vpp_cmdline = [cls.vpp_bin, "unix", "nodaemon", + cls.vpp_cmdline = [cls.vpp_bin, "unix", "{", "nodaemon", + "cli-listen localhost:5002", "}", "api-segment", "{", "prefix", cls.shm_prefix, "}"] if cls.plugin_path is not None: cls.vpp_cmdline.extend(["plugin_path", cls.plugin_path]) diff --git a/test/test_mpls.py b/test/test_mpls.py new file mode 100644 index 00000000..45af4704 --- /dev/null +++ b/test/test_mpls.py @@ -0,0 +1,209 @@ +#!/usr/bin/env python + +import unittest +import socket +from logging import * + +from framework import VppTestCase, VppTestRunner +from vpp_sub_interface import VppSubInterface, VppDot1QSubint, VppDot1ADSubint + +from scapy.packet import Raw +from scapy.layers.l2 import Ether, Dot1Q, ARP +from scapy.layers.inet import IP, UDP +from scapy.layers.inet6 import ICMPv6ND_NS, IPv6, UDP +from scapy.contrib.mpls import MPLS + +class TestMPLS(VppTestCase): + """ MPLS Test Case """ + + @classmethod + def setUpClass(cls): + super(TestMPLS, cls).setUpClass() + + def setUp(self): + super(TestMPLS, self).setUp() + + # create 2 pg interfaces + self.create_pg_interfaces(range(3)) + + # setup both interfaces + # assign them different tables. + table_id = 0 + + for i in self.pg_interfaces: + i.admin_up() + i.set_table_ip4(table_id) + i.set_table_ip6(table_id) + i.config_ip4() + i.config_ip6() + i.enable_mpls() + i.resolve_arp() + i.resolve_ndp() + table_id += 1 + + def tearDown(self): + super(TestMPLS, self).tearDown() + + def create_stream_ip4(self, src_if, mpls_label, mpls_ttl): + pkts = [] + for i in range(0, 257): + info = self.create_packet_info(src_if.sw_if_index, + src_if.sw_if_index) + payload = self.info_to_payload(info) + p = (Ether(dst=src_if.local_mac, src=src_if.remote_mac) / + MPLS(label=mpls_label, ttl=mpls_ttl) / + IP(src=src_if.remote_ip4, dst=src_if.remote_ip4) / + UDP(sport=1234, dport=1234) / + Raw(payload)) + info.data = p.copy() + pkts.append(p) + return pkts + + def create_stream_ip6(self, src_if, mpls_label, mpls_ttl): + pkts = [] + for i in range(0, 257): + info = self.create_packet_info(src_if.sw_if_index, + src_if.sw_if_index) + payload = self.info_to_payload(info) + p = (Ether(dst=src_if.local_mac, src=src_if.remote_mac) / + MPLS(label=mpls_label, ttl=mpls_ttl) / + IPv6(src=src_if.remote_ip6, dst=src_if.remote_ip6) / + UDP(sport=1234, dport=1234) / + Raw(payload)) + info.data = p.copy() + pkts.append(p) + return pkts + + def verify_capture_ip4(self, src_if, capture, sent): + try: + 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] + + 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, capture, sent): + try: + 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] + + 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) + + except: + raise; + + + def test_v4_exp_null(self): + """ MPLS V4 Explicit NULL test """ + + # + # The first test case has an MPLS TTL of 0 + # all packet should be dropped + # + tx = self.create_stream_ip4(self.pg0, 0, 0) + self.pg0.add_stream(tx) + + self.pg_enable_capture(self.pg_interfaces) + self.pg_start() + + rx = self.pg0.get_capture() + + try: + self.assertEqual(0, len(rx)); + except: + error("MPLS TTL=0 packets forwarded") + error(packet.show()) + raise + + # + # a stream with a non-zero MPLS TTL + # PG0 is in the default table + # + self.vapi.cli("clear trace") + tx = self.create_stream_ip4(self.pg0, 0, 2) + self.pg0.add_stream(tx) + + self.pg_enable_capture(self.pg_interfaces) + self.pg_start() + + rx = self.pg0.get_capture() + self.verify_capture_ip4(self.pg0, rx, tx) + + # + # a stream with a non-zero MPLS TTL + # PG1 is in table 1 + # we are ensuring the post-pop lookup occurs in the VRF table + # + self.vapi.cli("clear trace") + tx = self.create_stream_ip4(self.pg1, 0, 2) + self.pg1.add_stream(tx) + + self.pg_enable_capture(self.pg_interfaces) + self.pg_start() + + rx = self.pg1.get_capture() + self.verify_capture_ip4(self.pg0, rx, tx) + + def test_v6_exp_null(self): + """ MPLS V6 Explicit NULL test """ + + # + # a stream with a non-zero MPLS TTL + # PG0 is in the default table + # + self.vapi.cli("clear trace") + tx = self.create_stream_ip6(self.pg0, 2, 2) + self.pg0.add_stream(tx) + + self.pg_enable_capture(self.pg_interfaces) + self.pg_start() + + rx = self.pg0.get_capture() + self.verify_capture_ip6(self.pg0, rx, tx) + + # + # a stream with a non-zero MPLS TTL + # PG1 is in table 1 + # we are ensuring the post-pop lookup occurs in the VRF table + # + self.vapi.cli("clear trace") + tx = self.create_stream_ip6(self.pg1, 2, 2) + self.pg1.add_stream(tx) + + self.pg_enable_capture(self.pg_interfaces) + self.pg_start() + + rx = self.pg1.get_capture() + self.verify_capture_ip6(self.pg0, rx, tx) + + +if __name__ == '__main__': + unittest.main(testRunner=VppTestRunner) diff --git a/test/vpp_interface.py b/test/vpp_interface.py index c596ab5a..509ab952 100644 --- a/test/vpp_interface.py +++ b/test/vpp_interface.py @@ -131,6 +131,18 @@ class VppInterface(object): self.test.vapi.sw_interface_add_del_address( self.sw_if_index, addr, addr_len, is_ipv6=1) + def set_table_ip4(self, table_id): + """Set the interface in a IPv4 Table. + Must be called before configuring IP4 addresses""" + self.test.vapi.sw_interface_set_table( + self.sw_if_index, 0, table_id) + + def set_table_ip6(self, table_id): + """Set the interface in a IPv6 Table. + Must be called before configuring IP6 addresses""" + self.test.vapi.sw_interface_set_table( + self.sw_if_index, 1, table_id) + def disable_ipv6_ra(self): """Configure IPv6 RA suppress on the VPP interface""" self.test.vapi.sw_interface_ra_suppress(self.sw_if_index) @@ -238,3 +250,8 @@ class VppInterface(object): self.sub_if.append(sub_if) else: self.sub_if = sub_if + + def enable_mpls(self): + """Enable MPLS on the VPP interface""" + self.test.vapi.sw_interface_enable_disable_mpls( + self.sw_if_index) diff --git a/test/vpp_papi_provider.py b/test/vpp_papi_provider.py index 261a0f4a..f0eb410b 100644 --- a/test/vpp_papi_provider.py +++ b/test/vpp_papi_provider.py @@ -106,6 +106,18 @@ class VppPapiProvider(object): args = (0, b'') return self.api(vpp_papi.sw_interface_dump, args) + def sw_interface_set_table(self, sw_if_index, is_ipv6, table_id): + """ + Set the IPvX Table-id for the Interface + + :param sw_if_index: + :param is_ipv6: + :param table_id: + + """ + return self.api(vpp_papi.sw_interface_set_table, + (sw_if_index, is_ipv6, table_id)) + def sw_interface_add_del_address(self, sw_if_index, addr, addr_len, is_ipv6=0, is_add=1, del_all=0): """ @@ -121,6 +133,17 @@ class VppPapiProvider(object): return self.api(vpp_papi.sw_interface_add_del_address, (sw_if_index, is_add, is_ipv6, del_all, addr_len, addr)) + def sw_interface_enable_disable_mpls(self, sw_if_index, + is_enable=1): + """ + Enable/Disable MPLS on the interface + :param sw_if_index: + :param is_enable: (Default value = 1) + + """ + return self.api(vpp_papi.sw_interface_set_mpls_enable, + (sw_if_index, is_enable)) + def sw_interface_ra_suppress(self, sw_if_index): suppress = 1 managed = 0 diff --git a/vnet/vnet/dpo/dpo.c b/vnet/vnet/dpo/dpo.c index efee6d6b..3542b3f1 100644 --- a/vnet/vnet/dpo/dpo.c +++ b/vnet/vnet/dpo/dpo.c @@ -65,15 +65,18 @@ static const char* const * const ** dpo_nodes; /** * @brief Vector of edge indicies from parent DPO nodes to child * - * dpo_edges[child_type][child_proto][parent_type] = edge_index + * dpo_edges[child_type][child_proto][parent_type][parent_proto] = edge_index * * This array is derived at init time from the dpo_nodes above. Note that * the third dimension in dpo_nodes is lost, hence, the edge index from each * node MUST be the same. + * Including both the child and parent protocol is required to support the + * case where it changes as the grapth is traversed, most notablly when an + * MPLS label is popped. * * Note that this array is child type specific, not child instance specific. */ -static u32 ***dpo_edges; +static u32 ****dpo_edges; /** * @brief The DPO type value that can be assigend to the next dynamic @@ -269,13 +272,15 @@ dpo_get_next_node (dpo_type_t child_type, vec_validate(dpo_edges, child_type); vec_validate(dpo_edges[child_type], child_proto); - vec_validate_init_empty(dpo_edges[child_type][child_proto], - parent_dpo->dpoi_type, ~0); + vec_validate(dpo_edges[child_type][child_proto], parent_type); + vec_validate_init_empty( + dpo_edges[child_type][child_proto][parent_type], + parent_proto, ~0); /* * if the edge index has not yet been created for this node to node transistion */ - if (~0 == dpo_edges[child_type][child_proto][parent_type]) + if (~0 == dpo_edges[child_type][child_proto][parent_type][parent_proto]) { vlib_node_t *parent_node, *child_node; vlib_main_t *vm; @@ -288,45 +293,45 @@ dpo_get_next_node (dpo_type_t child_type, ASSERT(NULL != dpo_nodes[parent_type]); ASSERT(NULL != dpo_nodes[parent_type][parent_proto]); - pp = 0; + cc = 0; /* * create a graph arc from each of the parent's registered node types, * to each of the childs. */ - while (NULL != dpo_nodes[child_type][child_proto][pp]) + while (NULL != dpo_nodes[child_type][child_proto][cc]) { - parent_node = + child_node = vlib_get_node_by_name(vm, - (u8*) dpo_nodes[child_type][child_proto][pp]); + (u8*) dpo_nodes[child_type][child_proto][cc]); - cc = 0; + pp = 0; - while (NULL != dpo_nodes[parent_type][child_proto][cc]) + while (NULL != dpo_nodes[parent_type][parent_proto][pp]) { - child_node = + parent_node = vlib_get_node_by_name(vm, - (u8*) dpo_nodes[parent_type][parent_proto][cc]); + (u8*) dpo_nodes[parent_type][parent_proto][pp]); edge = vlib_node_add_next(vm, - parent_node->index, - child_node->index); + child_node->index, + parent_node->index); - if (~0 == dpo_edges[child_type][child_proto][parent_type]) + if (~0 == dpo_edges[child_type][child_proto][parent_type][parent_proto]) { - dpo_edges[child_type][child_proto][parent_type] = edge; + dpo_edges[child_type][child_proto][parent_type][parent_proto] = edge; } else { - ASSERT(dpo_edges[child_type][child_proto][parent_type] == edge); + ASSERT(dpo_edges[child_type][child_proto][parent_type][parent_proto] == edge); } - cc++; + pp++; } - pp++; + cc++; } } - return (dpo_edges[child_type][child_proto][parent_type]); + return (dpo_edges[child_type][child_proto][parent_type][parent_proto]); } /** diff --git a/vnet/vnet/dpo/lookup_dpo.c b/vnet/vnet/dpo/lookup_dpo.c index c9e0fca7..b13000dd 100644 --- a/vnet/vnet/dpo/lookup_dpo.c +++ b/vnet/vnet/dpo/lookup_dpo.c @@ -685,10 +685,10 @@ lookup_dpo_ip6_inline (vlib_main_t * vm, if (table_from_interface) { fib_index0 = - ip4_fib_table_get_index_for_sw_if_index( + ip6_fib_table_get_index_for_sw_if_index( vnet_buffer(b0)->sw_if_index[VLIB_RX]); fib_index1 = - ip4_fib_table_get_index_for_sw_if_index( + ip6_fib_table_get_index_for_sw_if_index( vnet_buffer(b1)->sw_if_index[VLIB_RX]); } else @@ -810,7 +810,7 @@ lookup_dpo_ip6_inline (vlib_main_t * vm, if (table_from_interface) { fib_index0 = - ip4_fib_table_get_index_for_sw_if_index( + ip6_fib_table_get_index_for_sw_if_index( vnet_buffer(b0)->sw_if_index[VLIB_RX]); } else diff --git a/vnet/vnet/dpo/mpls_label_dpo.c b/vnet/vnet/dpo/mpls_label_dpo.c index 532d0447..48c03be5 100644 --- a/vnet/vnet/dpo/mpls_label_dpo.c +++ b/vnet/vnet/dpo/mpls_label_dpo.c @@ -82,11 +82,12 @@ format_mpls_label_dpo (u8 *s, va_list *args) hdr.label_exp_s_ttl = clib_net_to_host_u32(mld->mld_hdr.label_exp_s_ttl); - return (format(s, "mpls-label:[%d]:%U\n%U%U", - index, - format_mpls_header, hdr, - format_white_space, indent, - format_dpo_id, &mld->mld_dpo, indent+2)); + s = format(s, "mpls-label:[%d]:", index); + s = format(s, "%U\n", format_mpls_header, hdr); + s = format(s, "%U", format_white_space, indent); + s = format(s, "%U", format_dpo_id, &mld->mld_dpo, indent+2); + + return (s); } static void diff --git a/vnet/vnet/fib/fib_entry_src.c b/vnet/vnet/fib/fib_entry_src.c index 2cbdf187..6fb2e64c 100644 --- a/vnet/vnet/fib/fib_entry_src.c +++ b/vnet/vnet/fib/fib_entry_src.c @@ -34,21 +34,6 @@ fib_entry_get_proto (const fib_entry_t * fib_entry) return (fib_entry->fe_prefix.fp_proto); } -static dpo_proto_t -fib_entry_get_payload_proto (const fib_entry_t * fib_entry) -{ - switch (fib_entry->fe_prefix.fp_proto) - { - case FIB_PROTOCOL_IP4: - case FIB_PROTOCOL_IP6: - return fib_proto_to_dpo(fib_entry->fe_prefix.fp_proto); - case FIB_PROTOCOL_MPLS: - return fib_entry->fe_prefix.fp_payload_proto; - } - - return (fib_proto_to_dpo(fib_entry->fe_prefix.fp_proto)); -} - void fib_entry_src_register (fib_source_t source, const fib_entry_src_vft_t *vft) @@ -344,7 +329,7 @@ fib_entry_src_mk_lb (fib_entry_t *fib_entry, .fct = fct, }; - lb_proto = fib_entry_get_payload_proto(fib_entry); + lb_proto = fib_proto_to_dpo(fib_entry->fe_prefix.fp_proto); fib_path_list_walk(esrc->fes_pl, fib_entry_src_collect_forwarding, diff --git a/vnet/vnet/fib/fib_test.c b/vnet/vnet/fib/fib_test.c index 800f4e6a..2194fcbc 100644 --- a/vnet/vnet/fib/fib_test.c +++ b/vnet/vnet/fib/fib_test.c @@ -4824,7 +4824,7 @@ fib_test_validate_lb_v (const load_balance_t *lb, FIB_TEST_LB((exp->special.adj == dpo->dpoi_index), "bucket %d stacks on drop %d", bucket, - exp->adj.adj); + exp->special.adj); break; } } @@ -5489,7 +5489,7 @@ fib_test_label (void) fib_test_lb_bucket_t bucket_drop = { .type = FT_LB_SPECIAL, .special = { - .adj = 1, + .adj = DPO_PROTO_IP4, }, }; -- cgit 1.2.3-korg From 0178d52384e0428368f1acf3163e664ecda7b64c Mon Sep 17 00:00:00 2001 From: Matej Klotton Date: Fri, 4 Nov 2016 11:11:44 +0100 Subject: Add IRB test - JIRA: CSIT-255 - create loopback interfaces - move pg-interface specific arp and neighbor discovery from vpp_interface to vpp_pg_interface - base configuration of IRB tests - IP test scenario Change-Id: I9945a188163652a4e22325877aef008c4d029557 Signed-off-by: Matej Klotton --- test/framework.py | 19 +++- test/test_ip4_irb.py | 247 ++++++++++++++++++++++++++++++++++++++++++++++ test/test_l2bd.py | 4 +- test/test_l2xc.py | 4 +- test/util.py | 12 ++- test/vpp_interface.py | 155 ++++++++++------------------- test/vpp_lo_interface.py | 16 +++ test/vpp_papi_provider.py | 39 ++++++++ test/vpp_pg_interface.py | 91 ++++++++++++++++- test/vpp_sub_interface.py | 15 +-- 10 files changed, 483 insertions(+), 119 deletions(-) create mode 100644 test/test_ip4_irb.py create mode 100644 test/vpp_lo_interface.py (limited to 'test/vpp_interface.py') diff --git a/test/framework.py b/test/framework.py index b10592cf..8dbc18fd 100644 --- a/test/framework.py +++ b/test/framework.py @@ -1,7 +1,5 @@ #!/usr/bin/env python -from abc import * -import os import subprocess import unittest import tempfile @@ -13,6 +11,7 @@ from threading import Thread from inspect import getdoc from hook import StepHook, PollHook from vpp_pg_interface import VppPGInterface +from vpp_lo_interface import VppLoInterface from vpp_papi_provider import VppPapiProvider from scapy.packet import Raw from log import * @@ -330,6 +329,22 @@ class VppTestCase(unittest.TestCase): cls.pg_interfaces = result return result + @classmethod + def create_loopback_interfaces(cls, interfaces): + """ + Create loopback interfaces + + :param interfaces: iterable indexes of the interfaces + + """ + result = [] + for i in interfaces: + intf = VppLoInterface(cls, i) + setattr(cls, intf.name, intf) + result.append(intf) + cls.lo_interfaces = result + return result + @staticmethod def extend_packet(packet, size): """ diff --git a/test/test_ip4_irb.py b/test/test_ip4_irb.py new file mode 100644 index 00000000..412575db --- /dev/null +++ b/test/test_ip4_irb.py @@ -0,0 +1,247 @@ +#!/usr/bin/env python +import unittest +from random import choice, randint + +from scapy.packet import Raw +from scapy.layers.l2 import Ether +from scapy.layers.inet import IP, UDP +from logging import * + +from framework import VppTestCase, VppTestRunner + +""" IRB Test Case + +config + L2 MAC learning enabled in l2bd + 2 routed interfaces untagged, bvi + 2 bridged interfaces in l2bd with bvi +test + sending ip4 eth pkts between routed interfaces + 2 routed interfaces + 2 bridged interfaces + 64B, 512B, 1518B, 9200B (ether_size) + burst of pkts per interface + 257pkts per burst + routed pkts hitting different FIB entries + bridged pkts hitting different MAC entries +verify + all packets received correctly +""" + + +class TestIpIrb(VppTestCase): + """ IRB Test Case """ + + @classmethod + def setUpClass(cls): + super(TestIpIrb, cls).setUpClass() + + cls.pg_if_packet_sizes = [64, 512, 1518, 9018] # packet sizes + cls.bd_id = 10 + cls.remote_hosts_count = 250 + + # create 3 pg interfaces, 1 loopback interface + cls.create_pg_interfaces(range(3)) + cls.create_loopback_interfaces(range(1)) + + cls.interfaces = list(cls.pg_interfaces) + cls.interfaces.extend(cls.lo_interfaces) + + for i in cls.interfaces: + i.admin_up() + + # Create BD with MAC learning enabled and put interfaces to this BD + cls.vapi.sw_interface_set_l2_bridge( + cls.loop0.sw_if_index, bd_id=cls.bd_id, bvi=1) + cls.vapi.sw_interface_set_l2_bridge( + cls.pg0.sw_if_index, bd_id=cls.bd_id) + cls.vapi.sw_interface_set_l2_bridge( + cls.pg1.sw_if_index, bd_id=cls.bd_id) + + cls.loop0.config_ip4() + cls.pg2.config_ip4() + + # configure MAC address binding to IPv4 neighbors on loop0 + cls.loop0.generate_remote_hosts(cls.remote_hosts_count) + cls.loop0.configure_extend_ipv4_mac_binding() + # configure MAC address on pg2 + cls.pg2.resolve_arp() + + # one half of hosts are behind pg0 second behind pg1 + half = cls.remote_hosts_count // 2 + cls.pg0.remote_hosts = cls.loop0.remote_hosts[:half] + cls.pg1.remote_hosts = cls.loop0.remote_hosts[half:] + + def tearDown(self): + super(TestIpIrb, self).tearDown() + if not self.vpp_dead: + info(self.vapi.cli("show l2patch")) + info(self.vapi.cli("show l2fib verbose")) + info(self.vapi.cli("show bridge-domain %s detail" % self.bd_id)) + info(self.vapi.cli("show ip arp")) + + def create_stream(self, src_ip_if, dst_ip_if, packet_sizes): + pkts = [] + for i in range(0, 257): + remote_dst_host = choice(dst_ip_if.remote_hosts) + info = self.create_packet_info( + src_ip_if.sw_if_index, dst_ip_if.sw_if_index) + payload = self.info_to_payload(info) + p = (Ether(dst=src_ip_if.local_mac, src=src_ip_if.remote_mac) / + IP(src=src_ip_if.remote_ip4, + dst=remote_dst_host.ip4) / + UDP(sport=1234, dport=1234) / + Raw(payload)) + info.data = p.copy() + size = packet_sizes[(i // 2) % len(packet_sizes)] + self.extend_packet(p, size) + pkts.append(p) + return pkts + + def create_stream_l2_to_ip(self, src_l2_if, src_ip_if, dst_ip_if, + packet_sizes): + pkts = [] + for i in range(0, 257): + info = self.create_packet_info( + src_ip_if.sw_if_index, dst_ip_if.sw_if_index) + payload = self.info_to_payload(info) + + host = choice(src_l2_if.remote_hosts) + + p = (Ether(src=host.mac, + dst = src_ip_if.local_mac) / + IP(src=host.ip4, + dst=dst_ip_if.remote_ip4) / + UDP(sport=1234, dport=1234) / + Raw(payload)) + + info.data = p.copy() + size = packet_sizes[(i // 2) % len(packet_sizes)] + self.extend_packet(p, size) + + pkts.append(p) + return pkts + + def verify_capture_l2_to_ip(self, dst_ip_if, src_ip_if, capture): + last_info = dict() + for i in self.interfaces: + last_info[i.sw_if_index] = None + + dst_ip_sw_if_index = dst_ip_if.sw_if_index + + for packet in capture: + ip = packet[IP] + udp = packet[IP][UDP] + payload_info = self.payload_to_info(str(packet[IP][UDP][Raw])) + packet_index = payload_info.index + + self.assertEqual(payload_info.dst, dst_ip_sw_if_index) + + next_info = self.get_next_packet_info_for_interface2( + payload_info.src, dst_ip_sw_if_index, + last_info[payload_info.src]) + last_info[payload_info.src] = next_info + self.assertTrue(next_info is not None) + saved_packet = next_info.data + self.assertTrue(next_info is not None) + + # MAC: src, dst + self.assertEqual(packet.src, dst_ip_if.local_mac) + self.assertEqual(packet.dst, dst_ip_if.remote_mac) + + # IP: src, dst + host = src_ip_if.host_by_ip4(ip.src) + self.assertIsNotNone(host) + self.assertEqual(ip.dst, saved_packet[IP].dst) + self.assertEqual(ip.dst, dst_ip_if.remote_ip4) + + # UDP: + self.assertEqual(udp.sport, saved_packet[UDP].sport) + self.assertEqual(udp.dport, saved_packet[UDP].dport) + + def verify_capture(self, dst_ip_if, src_ip_if, capture): + last_info = dict() + for i in self.interfaces: + last_info[i.sw_if_index] = None + + dst_ip_sw_if_index = dst_ip_if.sw_if_index + + for packet in capture: + ip = packet[IP] + udp = packet[IP][UDP] + payload_info = self.payload_to_info(str(packet[IP][UDP][Raw])) + packet_index = payload_info.index + + self.assertEqual(payload_info.dst, dst_ip_sw_if_index) + + next_info = self.get_next_packet_info_for_interface2( + payload_info.src, dst_ip_sw_if_index, + last_info[payload_info.src]) + last_info[payload_info.src] = next_info + self.assertTrue(next_info is not None) + self.assertEqual(packet_index, next_info.index) + saved_packet = next_info.data + self.assertTrue(next_info is not None) + + # MAC: src, dst + self.assertEqual(packet.src, dst_ip_if.local_mac) + host = dst_ip_if.host_by_mac(packet.dst) + + # IP: src, dst + self.assertEqual(ip.src, src_ip_if.remote_ip4) + self.assertEqual(ip.dst, saved_packet[IP].dst) + self.assertEqual(ip.dst, host.ip4) + + # UDP: + self.assertEqual(udp.sport, saved_packet[UDP].sport) + self.assertEqual(udp.dport, saved_packet[UDP].dport) + + def test_ip4_irb_1(self): + """ IPv4 IRB test 1 + + Test scenario: + ip traffic from pg2 interface must ends in both pg0 and pg1 + - arp entry present in loop0 interface for dst IP + - no l2 entree configured, pg0 and pg1 are same + """ + + stream = self.create_stream( + self.pg2, self.loop0, self.pg_if_packet_sizes) + self.pg2.add_stream(stream) + + self.pg_enable_capture(self.pg_interfaces) + self.pg_start() + + rcvd1 = self.pg0.get_capture() + rcvd2 = self.pg1.get_capture() + + self.verify_capture(self.loop0, self.pg2, rcvd1) + self.verify_capture(self.loop0, self.pg2, rcvd2) + + self.assertListEqual(rcvd1.res, rcvd2.res) + + def test_ip4_irb_2(self): + """ IPv4 IRB test 2 + + Test scenario: + ip traffic from pg0 and pg1 ends on pg2 + """ + + stream1 = self.create_stream_l2_to_ip( + self.pg0, self.loop0, self.pg2, self.pg_if_packet_sizes) + stream2 = self.create_stream_l2_to_ip( + self.pg1, self.loop0, self.pg2, self.pg_if_packet_sizes) + self.pg0.add_stream(stream1) + self.pg1.add_stream(stream2) + + self.pg_enable_capture(self.pg_interfaces) + self.pg_start() + + rcvd = self.pg2.get_capture() + self.verify_capture_l2_to_ip(self.pg2, self.loop0, rcvd) + + self.assertEqual(len(stream1) + len(stream2), len(rcvd.res)) + + +if __name__ == '__main__': + unittest.main(testRunner=VppTestRunner) diff --git a/test/test_l2bd.py b/test/test_l2bd.py index 53e1ee05..3c65cc1e 100644 --- a/test/test_l2bd.py +++ b/test/test_l2bd.py @@ -10,7 +10,7 @@ from scapy.layers.inet import IP, UDP from framework import VppTestCase, VppTestRunner from vpp_sub_interface import VppDot1QSubint -from util import TestHost +from util import Host class TestL2bd(VppTestCase): @@ -100,7 +100,7 @@ class TestL2bd(VppTestCase): hosts = self.hosts_by_pg_idx[pg_if.sw_if_index] packets = [] for j in range(start_nr, end_nr): - host = TestHost( + host = Host( "00:00:00:ff:%02x:%02x" % (pg_if.sw_if_index, j), "172.17.1%02x.%u" % (pg_if.sw_if_index, j)) packet = (Ether(dst="ff:ff:ff:ff:ff:ff", src=host.mac)) diff --git a/test/test_l2xc.py b/test/test_l2xc.py index 35c7a818..23fd7577 100644 --- a/test/test_l2xc.py +++ b/test/test_l2xc.py @@ -9,7 +9,7 @@ from scapy.layers.inet import IP, UDP from logging import * from framework import VppTestCase, VppTestRunner -from util import TestHost +from util import Host class TestL2xc(VppTestCase): @@ -85,7 +85,7 @@ class TestL2xc(VppTestCase): self.hosts_by_pg_idx[pg_if.sw_if_index] = [] hosts = self.hosts_by_pg_idx[pg_if.sw_if_index] for j in range(0, count): - host = TestHost( + host = Host( "00:00:00:ff:%02x:%02x" % (pg_if.sw_if_index, j), "172.17.1%02x.%u" % (pg_if.sw_if_index, j)) hosts.append(host) diff --git a/test/util.py b/test/util.py index 8d7c9202..6e7e275c 100644 --- a/test/util.py +++ b/test/util.py @@ -1,8 +1,7 @@ -from logging import * +import socket - -class TestHost(object): - """ Generic test host "connected" to VPP. """ +class Host(object): + """ Generic test host "connected" to VPPs interface. """ @property def mac(self): @@ -14,6 +13,11 @@ class TestHost(object): """ IPv4 address """ return self._ip4 + @property + def ip4n(self): + """ IPv4 address """ + return socket.inet_pton(socket.AF_INET, self._ip4) + @property def ip6(self): """ IPv6 address """ diff --git a/test/vpp_interface.py b/test/vpp_interface.py index 509ab952..d74248a3 100644 --- a/test/vpp_interface.py +++ b/test/vpp_interface.py @@ -1,9 +1,8 @@ from abc import abstractmethod, ABCMeta import socket -from logging import info, error -from scapy.layers.l2 import Ether, ARP +from logging import info -from scapy.layers.inet6 import IPv6, ICMPv6ND_NS, ICMPv6ND_NA, ICMPv6NDOptSrcLLAddr, ICMPv6NDOptDstLLAddr +from util import Host class VppInterface(object): @@ -20,7 +19,7 @@ class VppInterface(object): @property def remote_mac(self): """MAC-address of the remote interface "connected" to this interface""" - return self._remote_mac + return self._remote_hosts[0].mac @property def local_mac(self): @@ -35,17 +34,17 @@ class VppInterface(object): @property def local_ip4n(self): """Local IPv4 address - raw, suitable as API parameter""" - return self._local_ip4n + return socket.inet_pton(socket.AF_INET, self._local_ip4) @property def remote_ip4(self): """IPv4 address of remote peer "connected" to this interface""" - return self._remote_ip4 + return self._remote_hosts[0].ip4 @property def remote_ip4n(self): """IPv4 address of remote peer - raw, suitable as API parameter""" - return self._remote_ip4n + return socket.inet_pton(socket.AF_INET, self.remote_ip4) @property def local_ip6(self): @@ -55,17 +54,17 @@ class VppInterface(object): @property def local_ip6n(self): """Local IPv6 address - raw, suitable as API parameter""" - return self._local_ip6n + return socket.inet_pton(socket.AF_INET6, self.local_ip6) @property def remote_ip6(self): """IPv6 address of remote peer "connected" to this interface""" - return self._remote_ip6 + return self._remote_hosts[0].ip6 @property def remote_ip6n(self): """IPv6 address of remote peer - raw, suitable as API parameter""" - return self._remote_ip6n + return socket.inet_pton(socket.AF_INET6, self.remote_ip6) @property def name(self): @@ -82,19 +81,51 @@ class VppInterface(object): """Test case creating this interface""" return self._test + @property + def remote_hosts(self): + """Remote hosts list""" + return self._remote_hosts + + @remote_hosts.setter + def remote_hosts(self, value): + self._remote_hosts = value + #TODO: set hosts_by dicts + + def host_by_mac(self, mac): + return self._hosts_by_mac[mac] + + def host_by_ip4(self, ip): + return self._hosts_by_ip4[ip] + + def host_by_ip6(self, ip): + return self._hosts_by_ip6[ip] + + def generate_remote_hosts(self, count=1): + """Generate and add remote hosts for the interface.""" + self._remote_hosts = [] + self._hosts_by_mac = {} + self._hosts_by_ip4 = {} + self._hosts_by_ip6 = {} + for i in range(2, count+2): # 0: network address, 1: local vpp address + mac = "02:%02x:00:00:ff:%02x" % (self.sw_if_index, i) + ip4 = "172.16.%u.%u" % (self.sw_if_index, i) + ip6 = "fd01:%04x::%04x" % (self.sw_if_index, i) + host = Host(mac, ip4, ip6) + self._remote_hosts.append(host) + self._hosts_by_mac[mac] = host + self._hosts_by_ip4[ip4] = host + self._hosts_by_ip6[ip6] = host + def post_init_setup(self): """Additional setup run after creating an interface object""" - self._remote_mac = "02:00:00:00:ff:%02x" % self.sw_if_index + + self.generate_remote_hosts() self._local_ip4 = "172.16.%u.1" % self.sw_if_index self._local_ip4n = socket.inet_pton(socket.AF_INET, self.local_ip4) - self._remote_ip4 = "172.16.%u.2" % self.sw_if_index - self._remote_ip4n = socket.inet_pton(socket.AF_INET, self.remote_ip4) - self._local_ip6 = "fd01:%u::1" % self.sw_if_index + self._local_ip6 = "fd01:%04x::1" % self.sw_if_index self._local_ip6n = socket.inet_pton(socket.AF_INET6, self.local_ip6) - self._remote_ip6 = "fd01:%u::2" % self.sw_if_index - self._remote_ip6n = socket.inet_pton(socket.AF_INET6, self.remote_ip6) r = self.test.vapi.sw_interface_dump() for intf in r: @@ -124,6 +155,13 @@ class VppInterface(object): self.test.vapi.sw_interface_add_del_address( self.sw_if_index, addr, addr_len) + def configure_extend_ipv4_mac_binding(self): + """Configure neighbor MAC to IPv4 addresses.""" + for host in self._remote_hosts: + macn = host.mac.replace(":", "").decode('hex') + ipn = host.ip4n + self.test.vapi.ip_neighbor_add_del(self.sw_if_index, macn, ipn) + def config_ip6(self): """Configure IPv6 address on the VPP interface""" addr = self._local_ip6n @@ -147,91 +185,6 @@ class VppInterface(object): """Configure IPv6 RA suppress on the VPP interface""" self.test.vapi.sw_interface_ra_suppress(self.sw_if_index) - def create_arp_req(self): - """Create ARP request applicable for this interface""" - return (Ether(dst="ff:ff:ff:ff:ff:ff", src=self.remote_mac) / - ARP(op=ARP.who_has, pdst=self.local_ip4, - psrc=self.remote_ip4, hwsrc=self.remote_mac)) - - def create_ndp_req(self): - return (Ether(dst="ff:ff:ff:ff:ff:ff", src=self.remote_mac) / - IPv6(src=self.remote_ip6, dst=self.local_ip6) / - ICMPv6ND_NS(tgt=self.local_ip6) / - ICMPv6NDOptSrcLLAddr(lladdr=self.remote_mac)) - - def resolve_arp(self, pg_interface=None): - """Resolve ARP using provided packet-generator interface - - :param pg_interface: interface used to resolve, if None then this - interface is used - - """ - if pg_interface is None: - pg_interface = self - info("Sending ARP request for %s on port %s" % - (self.local_ip4, pg_interface.name)) - arp_req = self.create_arp_req() - pg_interface.add_stream(arp_req) - pg_interface.enable_capture() - self.test.pg_start() - info(self.test.vapi.cli("show trace")) - arp_reply = pg_interface.get_capture() - if arp_reply is None or len(arp_reply) == 0: - info("No ARP received on port %s" % pg_interface.name) - return - arp_reply = arp_reply[0] - # Make Dot1AD packet content recognizable to scapy - if arp_reply.type == 0x88a8: - arp_reply.type = 0x8100 - arp_reply = Ether(str(arp_reply)) - try: - if arp_reply[ARP].op == ARP.is_at: - info("VPP %s MAC address is %s " % - (self.name, arp_reply[ARP].hwsrc)) - self._local_mac = arp_reply[ARP].hwsrc - else: - info("No ARP received on port %s" % pg_interface.name) - except: - error("Unexpected response to ARP request:") - error(arp_reply.show()) - raise - - def resolve_ndp(self, pg_interface=None): - """Resolve NDP using provided packet-generator interface - - :param pg_interface: interface used to resolve, if None then this - interface is used - - """ - if pg_interface is None: - pg_interface = self - info("Sending NDP request for %s on port %s" % - (self.local_ip6, pg_interface.name)) - ndp_req = self.create_ndp_req() - pg_interface.add_stream(ndp_req) - pg_interface.enable_capture() - self.test.pg_start() - info(self.test.vapi.cli("show trace")) - ndp_reply = pg_interface.get_capture() - if ndp_reply is None or len(ndp_reply) == 0: - info("No NDP received on port %s" % pg_interface.name) - return - ndp_reply = ndp_reply[0] - # Make Dot1AD packet content recognizable to scapy - if ndp_reply.type == 0x88a8: - ndp_reply.type = 0x8100 - ndp_reply = Ether(str(ndp_reply)) - try: - ndp_na = ndp_reply[ICMPv6ND_NA] - opt = ndp_na[ICMPv6NDOptDstLLAddr] - info("VPP %s MAC address is %s " % - (self.name, opt.lladdr)) - self._local_mac = opt.lladdr - except: - error("Unexpected response to NDP request:") - error(ndp_reply.show()) - raise - def admin_up(self): """ Put interface ADMIN-UP """ self.test.vapi.sw_interface_set_flags(self.sw_if_index, admin_up_down=1) diff --git a/test/vpp_lo_interface.py b/test/vpp_lo_interface.py new file mode 100644 index 00000000..ed9ac725 --- /dev/null +++ b/test/vpp_lo_interface.py @@ -0,0 +1,16 @@ + +from vpp_interface import VppInterface + + +class VppLoInterface(VppInterface): + """ + VPP loopback interface + """ + + def __init__(self, test, lo_index): + """ Create VPP loopback interface """ + self._lo_index = lo_index + self._test = test + r = self.test.vapi.create_loopback() + self._sw_if_index = r.sw_if_index + self.post_init_setup() diff --git a/test/vpp_papi_provider.py b/test/vpp_papi_provider.py index f0eb410b..10445de6 100644 --- a/test/vpp_papi_provider.py +++ b/test/vpp_papi_provider.py @@ -277,6 +277,13 @@ class VppPapiProvider(object): """ return self.api(vpp_papi.create_vlan_subif, (sw_if_index, vlan)) + def create_loopback(self, mac=''): + """ + + :param mac: (Optional) + """ + return self.api(vpp_papi.create_loopback, (mac,)) + def ip_add_del_route( self, dst_address, @@ -352,3 +359,35 @@ class VppPapiProvider(object): dst_address_length, dst_address, next_hop_address)) + + def ip_neighbor_add_del(self, + sw_if_index, + mac_address, + dst_address, + vrf_id=0, + is_add=1, + is_ipv6=0, + is_static=0, + ): + """ Add neighbor MAC to IPv4 or IPv6 address. + + :param sw_if_index: + :param mac_address: + :param dst_address: + :param vrf_id: (Default value = 0) + :param is_add: (Default value = 1) + :param is_ipv6: (Default value = 0) + :param is_static: (Default value = 0) + """ + + return self.api( + vpp_papi.ip_neighbor_add_del, + (vrf_id, + sw_if_index, + is_add, + is_ipv6, + is_static, + mac_address, + dst_address + ) + ) diff --git a/test/vpp_pg_interface.py b/test/vpp_pg_interface.py index c9e4076d..81c7192e 100644 --- a/test/vpp_pg_interface.py +++ b/test/vpp_pg_interface.py @@ -1,9 +1,12 @@ import os import time -from logging import error +from logging import error, info from scapy.utils import wrpcap, rdpcap from vpp_interface import VppInterface +from scapy.layers.l2 import Ether, ARP +from scapy.layers.inet6 import IPv6, ICMPv6ND_NS, ICMPv6ND_NA, ICMPv6NDOptSrcLLAddr, ICMPv6NDOptDstLLAddr + class VppPGInterface(VppInterface): """ @@ -130,3 +133,89 @@ class VppPGInterface(VppInterface): " packets arrived" % self.out_path) return [] return output + + def create_arp_req(self): + """Create ARP request applicable for this interface""" + return (Ether(dst="ff:ff:ff:ff:ff:ff", src=self.remote_mac) / + ARP(op=ARP.who_has, pdst=self.local_ip4, + psrc=self.remote_ip4, hwsrc=self.remote_mac)) + + def create_ndp_req(self): + """Create NDP - NS applicable for this interface""" + return (Ether(dst="ff:ff:ff:ff:ff:ff", src=self.remote_mac) / + IPv6(src=self.remote_ip6, dst=self.local_ip6) / + ICMPv6ND_NS(tgt=self.local_ip6) / + ICMPv6NDOptSrcLLAddr(lladdr=self.remote_mac)) + + def resolve_arp(self, pg_interface=None): + """Resolve ARP using provided packet-generator interface + + :param pg_interface: interface used to resolve, if None then this + interface is used + + """ + if pg_interface is None: + pg_interface = self + info("Sending ARP request for %s on port %s" % + (self.local_ip4, pg_interface.name)) + arp_req = self.create_arp_req() + pg_interface.add_stream(arp_req) + pg_interface.enable_capture() + self.test.pg_start() + info(self.test.vapi.cli("show trace")) + arp_reply = pg_interface.get_capture() + if arp_reply is None or len(arp_reply) == 0: + info("No ARP received on port %s" % pg_interface.name) + return + arp_reply = arp_reply[0] + # Make Dot1AD packet content recognizable to scapy + if arp_reply.type == 0x88a8: + arp_reply.type = 0x8100 + arp_reply = Ether(str(arp_reply)) + try: + if arp_reply[ARP].op == ARP.is_at: + info("VPP %s MAC address is %s " % + (self.name, arp_reply[ARP].hwsrc)) + self._local_mac = arp_reply[ARP].hwsrc + else: + info("No ARP received on port %s" % pg_interface.name) + except: + error("Unexpected response to ARP request:") + error(arp_reply.show()) + raise + + def resolve_ndp(self, pg_interface=None): + """Resolve NDP using provided packet-generator interface + + :param pg_interface: interface used to resolve, if None then this + interface is used + + """ + if pg_interface is None: + pg_interface = self + info("Sending NDP request for %s on port %s" % + (self.local_ip6, pg_interface.name)) + ndp_req = self.create_ndp_req() + pg_interface.add_stream(ndp_req) + pg_interface.enable_capture() + self.test.pg_start() + info(self.test.vapi.cli("show trace")) + ndp_reply = pg_interface.get_capture() + if ndp_reply is None or len(ndp_reply) == 0: + info("No NDP received on port %s" % pg_interface.name) + return + ndp_reply = ndp_reply[0] + # Make Dot1AD packet content recognizable to scapy + if ndp_reply.type == 0x88a8: + ndp_reply.type = 0x8100 + ndp_reply = Ether(str(ndp_reply)) + try: + ndp_na = ndp_reply[ICMPv6ND_NA] + opt = ndp_na[ICMPv6NDOptDstLLAddr] + info("VPP %s MAC address is %s " % + (self.name, opt.lladdr)) + self._local_mac = opt.lladdr + except: + error("Unexpected response to NDP request:") + error(ndp_reply.show()) + raise diff --git a/test/vpp_sub_interface.py b/test/vpp_sub_interface.py index cd98a68c..b387d27b 100644 --- a/test/vpp_sub_interface.py +++ b/test/vpp_sub_interface.py @@ -1,9 +1,10 @@ from scapy.layers.l2 import Ether, Dot1Q from abc import abstractmethod, ABCMeta from vpp_interface import VppInterface +from vpp_pg_interface import VppPGInterface -class VppSubInterface(VppInterface): +class VppSubInterface(VppPGInterface): __metaclass__ = ABCMeta @property @@ -55,14 +56,14 @@ class VppDot1QSubint(VppSubInterface): self._vlan = vlan r = self.test.vapi.create_vlan_subif(parent.sw_if_index, self.vlan) self._sw_if_index = r.sw_if_index - self.post_init_setup() + VppInterface.post_init_setup(self) def create_arp_req(self): - packet = VppInterface.create_arp_req(self) + packet = VppPGInterface.create_arp_req(self) return self.add_dot1_layer(packet) def create_ndp_req(self): - packet = VppInterface.create_ndp_req(self) + packet = VppPGInterface.create_ndp_req(self) return self.add_dot1_layer(packet) def add_dot1_layer(self, packet): @@ -108,14 +109,14 @@ class VppDot1ADSubint(VppSubInterface): two_tags=1, exact_match=1) self._sw_if_index = r.sw_if_index - self.post_init_setup() + VppInterface.post_init_setup(self) def create_arp_req(self): - packet = VppInterface.create_arp_req(self) + packet = VppPGInterface.create_arp_req(self) return self.add_dot1_layer(packet) def create_ndp_req(self): - packet = VppInterface.create_ndp_req(self) + packet = VppPGInterface.create_ndp_req(self) return self.add_dot1_layer(packet) def add_dot1_layer(self, packet): -- cgit 1.2.3-korg From 86d87c40dd97ced69c939299204fadf1859a2f50 Mon Sep 17 00:00:00 2001 From: Matej Klotton Date: Fri, 11 Nov 2016 11:38:55 +0100 Subject: Update test documentation. - update IRB, IPv4, ipv6 doc - revert 778c2765c8ea5c6628f6d668847f0b9ae06dbf3d Change-Id: I9af5ed9329ce5fe01392cf28d5bf321cfc647e48 Signed-off-by: Matej Klotton --- Makefile | 2 +- test/doc/Makefile | 61 +++++++++++++++++------------- test/doc/framework.rst | 7 ---- test/doc/hook.rst | 7 ---- test/doc/index.rst | 10 ++--- test/doc/log.rst | 7 ---- test/doc/modules.rst | 24 ------------ test/doc/run_tests.rst | 7 ---- test/doc/scapy_handlers.rst | 22 ----------- test/doc/template_bd.rst | 7 ---- test/doc/test_ip4.rst | 7 ---- test/doc/test_ip6.rst | 7 ---- test/doc/test_l2bd.rst | 7 ---- test/doc/test_l2xc.rst | 7 ---- test/doc/test_lb.rst | 7 ---- test/doc/test_mpls.rst | 7 ---- test/doc/test_vxlan.rst | 7 ---- test/doc/util.rst | 7 ---- test/doc/vpp_interface.rst | 7 ---- test/doc/vpp_papi_provider.rst | 7 ---- test/doc/vpp_pg_interface.rst | 7 ---- test/doc/vpp_sub_interface.rst | 7 ---- test/framework.py | 27 ++++---------- test/test_ip4.py | 78 +++++++++++++++++++++++++++++--------- test/test_ip4_irb.py | 83 +++++++++++++++++++++++++---------------- test/test_ip6.py | 77 ++++++++++++++++++++++++++++++-------- test/vpp_interface.py | 85 +++++++++++++++++++++++++++--------------- test/vpp_lo_interface.py | 4 +- 28 files changed, 277 insertions(+), 315 deletions(-) delete mode 100644 test/doc/framework.rst delete mode 100644 test/doc/hook.rst delete mode 100644 test/doc/log.rst delete mode 100644 test/doc/modules.rst delete mode 100644 test/doc/run_tests.rst delete mode 100644 test/doc/scapy_handlers.rst delete mode 100644 test/doc/template_bd.rst delete mode 100644 test/doc/test_ip4.rst delete mode 100644 test/doc/test_ip6.rst delete mode 100644 test/doc/test_l2bd.rst delete mode 100644 test/doc/test_l2xc.rst delete mode 100644 test/doc/test_lb.rst delete mode 100644 test/doc/test_mpls.rst delete mode 100644 test/doc/test_vxlan.rst delete mode 100644 test/doc/util.rst delete mode 100644 test/doc/vpp_interface.rst delete mode 100644 test/doc/vpp_papi_provider.rst delete mode 100644 test/doc/vpp_pg_interface.rst delete mode 100644 test/doc/vpp_sub_interface.rst (limited to 'test/vpp_interface.py') diff --git a/Makefile b/Makefile index 41a45c88..4e084d4d 100644 --- a/Makefile +++ b/Makefile @@ -212,7 +212,7 @@ build-vpp-api: $(BR)/.bootstrap.ok VPP_PYTHON_PREFIX=$(BR)/python define test - $(if $(filter-out $(3),retest),make -C $(BR) PLATFORM=$(1) TAG=$(2) vpp-api-install plugins-install vpp-install vpp-api-test-install,) + $(if $(filter-out $(3),retest),make -C $(BR) PLATFORM=$(1) TAG=$(2) vpp-api-install plugins-install vpp-install,) make -C test \ VPP_TEST_BIN=$(BR)/install-$(2)-native/vpp/bin/vpp \ VPP_TEST_API_TEST_BIN=$(BR)/install-$(2)-native/vpp-api-test/bin/vpp_api_test \ diff --git a/test/doc/Makefile b/test/doc/Makefile index 3b4c02b8..809abef8 100644 --- a/test/doc/Makefile +++ b/test/doc/Makefile @@ -8,13 +8,15 @@ SPHINXBUILD = sphinx-build PAPER = BUILD_DOC_ROOT = $(BR)/test-doc BUILD_DOC_DIR = $(BUILD_DOC_ROOT)/build +API_DOC_GEN_DIR = $(BUILD_DOC_ROOT)/apidoc # Internal variables. PAPEROPT_a4 = -D latex_paper_size=a4 PAPEROPT_letter = -D latex_paper_size=letter -ALLSPHINXOPTS = -d $(BUILD_DOC_DIR)/.sphinx-cache $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) $(SRC_DOC_DIR) +ALLSPHINXOPTS = -d $(BUILD_DOC_DIR)/.sphinx-cache $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) $(API_DOC_GEN_DIR) -c $(SRC_DOC_DIR) # the i18n builder cannot share the environment and doctrees with the others I18NSPHINXOPTS = $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) . +INDEX_REL_PATH:=$(shell realpath --relative-to=$(API_DOC_GEN_DIR) $(SRC_DOC_DIR)/index.rst) IN_VENV:=$(shell if pip -V | grep "virtualenv" 2>&1 > /dev/null; then echo 1; else echo 0; fi) .PHONY: verify-virtualenv @@ -23,6 +25,13 @@ ifeq ($(IN_VENV),0) $(error "Not running inside virtualenv (are you running 'make test-doc' from root?)") endif +.PHONY: regen-api-doc +regen-api-doc: verify-virtualenv + @mkdir -p $(API_DOC_GEN_DIR) + #@echo ".. include:: $(INDEX_REL_PATH)" > $(API_DOC_GEN_DIR)/index.rst + @cp $(SRC_DOC_DIR)/index.rst $(API_DOC_GEN_DIR) + sphinx-apidoc -o $(API_DOC_GEN_DIR) .. + .PHONY: help help: @echo "Please use \`make ' where is one of" @@ -58,44 +67,44 @@ wipe: rm -rf $(BUILD_DOC_ROOT) .PHONY: html -html: verify-virtualenv +html: regen-api-doc verify-virtualenv $(SPHINXBUILD) -b html $(ALLSPHINXOPTS) $(BUILD_DOC_DIR)/html @echo @echo "Build finished. The HTML pages are in $(BUILD_DOC_DIR)/html." .PHONY: dirhtml -dirhtml: verify-virtualenv +dirhtml: regen-api-doc verify-virtualenv $(SPHINXBUILD) -b dirhtml $(ALLSPHINXOPTS) $(BUILD_DOC_DIR)/dirhtml @echo @echo "Build finished. The HTML pages are in $(BUILD_DOC_DIR)/dirhtml." .PHONY: singlehtml -singlehtml: verify-virtualenv +singlehtml: regen-api-doc verify-virtualenv $(SPHINXBUILD) -b singlehtml $(ALLSPHINXOPTS) $(BUILD_DOC_DIR)/singlehtml @echo @echo "Build finished. The HTML page is in $(BUILD_DOC_DIR)/singlehtml." .PHONY: pickle -pickle: verify-virtualenv +pickle: regen-api-doc verify-virtualenv $(SPHINXBUILD) -b pickle $(ALLSPHINXOPTS) $(BUILD_DOC_DIR)/pickle @echo @echo "Build finished; now you can process the pickle files." .PHONY: json -json: verify-virtualenv +json: regen-api-doc verify-virtualenv $(SPHINXBUILD) -b json $(ALLSPHINXOPTS) $(BUILD_DOC_DIR)/json @echo @echo "Build finished; now you can process the JSON files." .PHONY: htmlhelp -htmlhelp: verify-virtualenv +htmlhelp: regen-api-doc verify-virtualenv $(SPHINXBUILD) -b htmlhelp $(ALLSPHINXOPTS) $(BUILD_DOC_DIR)/htmlhelp @echo @echo "Build finished; now you can run HTML Help Workshop with the" \ ".hhp project file in $(BUILD_DOC_DIR)/htmlhelp." .PHONY: qthelp -qthelp: verify-virtualenv +qthelp: regen-api-doc verify-virtualenv $(SPHINXBUILD) -b qthelp $(ALLSPHINXOPTS) $(BUILD_DOC_DIR)/qthelp @echo @echo "Build finished; now you can run "qcollectiongenerator" with the" \ @@ -105,7 +114,7 @@ qthelp: verify-virtualenv @echo "# assistant -collectionFile $(BUILD_DOC_DIR)/qthelp/VPPtestframework.qhc" .PHONY: applehelp -applehelp: verify-virtualenv +applehelp: regen-api-doc verify-virtualenv $(SPHINXBUILD) -b applehelp $(ALLSPHINXOPTS) $(BUILD_DOC_DIR)/applehelp @echo @echo "Build finished. The help book is in $(BUILD_DOC_DIR)/applehelp." @@ -114,7 +123,7 @@ applehelp: verify-virtualenv "bundle." .PHONY: devhelp -devhelp: verify-virtualenv +devhelp: regen-api-doc verify-virtualenv $(SPHINXBUILD) -b devhelp $(ALLSPHINXOPTS) $(BUILD_DOC_DIR)/devhelp @echo @echo "Build finished." @@ -124,7 +133,7 @@ devhelp: verify-virtualenv @echo "# devhelp" .PHONY: epub -epub: verify-virtualenv +epub: regen-api-doc verify-virtualenv $(SPHINXBUILD) -b epub $(ALLSPHINXOPTS) $(BUILD_DOC_DIR)/epub @echo @echo "Build finished. The epub file is in $(BUILD_DOC_DIR)/epub." @@ -136,7 +145,7 @@ epub3: @echo "Build finished. The epub3 file is in $(BUILD_DOC_DIR)/epub3." .PHONY: latex -latex: verify-virtualenv +latex: regen-api-doc verify-virtualenv $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILD_DOC_DIR)/latex @echo @echo "Build finished; the LaTeX files are in $(BUILD_DOC_DIR)/latex." @@ -144,33 +153,33 @@ latex: verify-virtualenv "(use \`make latexpdf' here to do that automatically)." .PHONY: latexpdf -latexpdf: verify-virtualenv +latexpdf: regen-api-doc verify-virtualenv $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILD_DOC_DIR)/latex @echo "Running LaTeX files through pdflatex..." $(MAKE) -C $(BUILD_DOC_DIR)/latex all-pdf @echo "pdflatex finished; the PDF files are in $(BUILD_DOC_DIR)/latex." .PHONY: latexpdfja -latexpdfja: verify-virtualenv +latexpdfja: regen-api-doc verify-virtualenv $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILD_DOC_DIR)/latex @echo "Running LaTeX files through platex and dvipdfmx..." $(MAKE) -C $(BUILD_DOC_DIR)/latex all-pdf-ja @echo "pdflatex finished; the PDF files are in $(BUILD_DOC_DIR)/latex." .PHONY: text -text: verify-virtualenv +text: regen-api-doc verify-virtualenv $(SPHINXBUILD) -b text $(ALLSPHINXOPTS) $(BUILD_DOC_DIR)/text @echo @echo "Build finished. The text files are in $(BUILD_DOC_DIR)/text." .PHONY: man -man: verify-virtualenv +man: regen-api-doc verify-virtualenv $(SPHINXBUILD) -b man $(ALLSPHINXOPTS) $(BUILD_DOC_DIR)/man @echo @echo "Build finished. The manual pages are in $(BUILD_DOC_DIR)/man." .PHONY: texinfo -texinfo: verify-virtualenv +texinfo: regen-api-doc verify-virtualenv $(SPHINXBUILD) -b texinfo $(ALLSPHINXOPTS) $(BUILD_DOC_DIR)/texinfo @echo @echo "Build finished. The Texinfo files are in $(BUILD_DOC_DIR)/texinfo." @@ -178,57 +187,57 @@ texinfo: verify-virtualenv "(use \`make info' here to do that automatically)." .PHONY: info -info: verify-virtualenv +info: regen-api-doc verify-virtualenv $(SPHINXBUILD) -b texinfo $(ALLSPHINXOPTS) $(BUILD_DOC_DIR)/texinfo @echo "Running Texinfo files through makeinfo..." make -C $(BUILD_DOC_DIR)/texinfo info @echo "makeinfo finished; the Info files are in $(BUILD_DOC_DIR)/texinfo." .PHONY: gettext -gettext: verify-virtualenv +gettext: regen-api-doc verify-virtualenv $(SPHINXBUILD) -b gettext $(I18NSPHINXOPTS) $(BUILD_DOC_DIR)/locale @echo @echo "Build finished. The message catalogs are in $(BUILD_DOC_DIR)/locale." .PHONY: changes -changes: verify-virtualenv +changes: regen-api-doc verify-virtualenv $(SPHINXBUILD) -b changes $(ALLSPHINXOPTS) $(BUILD_DOC_DIR)/changes @echo @echo "The overview file is in $(BUILD_DOC_DIR)/changes." .PHONY: linkcheck -linkcheck: verify-virtualenv +linkcheck: regen-api-doc verify-virtualenv $(SPHINXBUILD) -b linkcheck $(ALLSPHINXOPTS) $(BUILD_DOC_DIR)/linkcheck @echo @echo "Link check complete; look for any errors in the above output " \ "or in $(BUILD_DOC_DIR)/linkcheck/output.txt." .PHONY: doctest -doctest: verify-virtualenv +doctest: regen-api-doc verify-virtualenv $(SPHINXBUILD) -b doctest $(ALLSPHINXOPTS) $(BUILD_DOC_DIR)/doctest @echo "Testing of doctests in the sources finished, look at the " \ "results in $(BUILD_DOC_DIR)/doctest/output.txt." .PHONY: coverage -coverage: verify-virtualenv +coverage: regen-api-doc verify-virtualenv $(SPHINXBUILD) -b coverage $(ALLSPHINXOPTS) $(BUILD_DOC_DIR)/coverage @echo "Testing of coverage in the sources finished, look at the " \ "results in $(BUILD_DOC_DIR)/coverage/python.txt." .PHONY: xml -xml: verify-virtualenv +xml: regen-api-doc verify-virtualenv $(SPHINXBUILD) -b xml $(ALLSPHINXOPTS) $(BUILD_DOC_DIR)/xml @echo @echo "Build finished. The XML files are in $(BUILD_DOC_DIR)/xml." .PHONY: pseudoxml -pseudoxml: verify-virtualenv +pseudoxml: regen-api-doc verify-virtualenv $(SPHINXBUILD) -b pseudoxml $(ALLSPHINXOPTS) $(BUILD_DOC_DIR)/pseudoxml @echo @echo "Build finished. The pseudo-XML files are in $(BUILD_DOC_DIR)/pseudoxml." .PHONY: dummy -dummy: verify-virtualenv +dummy: regen-api-doc verify-virtualenv $(SPHINXBUILD) -b dummy $(ALLSPHINXOPTS) $(BUILD_DOC_DIR)/dummy @echo @echo "Build finished. Dummy builder generates no files." diff --git a/test/doc/framework.rst b/test/doc/framework.rst deleted file mode 100644 index ce5e16e2..00000000 --- a/test/doc/framework.rst +++ /dev/null @@ -1,7 +0,0 @@ -framework module -================ - -.. automodule:: framework - :members: - :undoc-members: - :show-inheritance: diff --git a/test/doc/hook.rst b/test/doc/hook.rst deleted file mode 100644 index f856e519..00000000 --- a/test/doc/hook.rst +++ /dev/null @@ -1,7 +0,0 @@ -hook module -=========== - -.. automodule:: hook - :members: - :undoc-members: - :show-inheritance: diff --git a/test/doc/index.rst b/test/doc/index.rst index 323b608f..8cbe961f 100644 --- a/test/doc/index.rst +++ b/test/doc/index.rst @@ -22,7 +22,7 @@ Anatomy of a test case ###################### Python's unittest_ is used as the base framework upon which the VPP test -framework is built. A test suite in the |vtf| constists of multiple classes +framework is built. A test suite in the |vtf| consists of multiple classes derived from `VppTestCase`, which is itself derived from TestCase_. The test class defines one or more test functions, which act as test cases. @@ -44,11 +44,11 @@ Function flow when running a test case is: The tearDown function is called after each test function with the purpose of doing partial cleanup. 5. `tearDownClass `: - Method called once after running all of the test funnctions to perform + Method called once after running all of the test functions to perform the final cleanup. Test temporary directory and VPP life cycle -################################################ +########################################### Test separation is achieved by separating the test files and vpp instances. Each test creates a temporary directory and it's name is used to create @@ -60,7 +60,7 @@ are stored in this temporary test directory. Virtual environment ################### -Virtualenv_ is a python module which provides a means to crate an environment +Virtualenv_ is a python module which provides a means to create an environment containing the dependencies required by the |vtf|, allowing a separation from any existing system-wide packages. |vtf|'s Makefile automatically creates a virtualenv_ inside build-root and installs the required packages @@ -72,7 +72,7 @@ Naming conventions Most unit tests do some kind of packet manipulation - sending and receiving packets between VPP and virtual hosts connected to the VPP. Referring -to the sides, addresses, etc.. is always done as if looking from the VPP side, +to the sides, addresses, etc. is always done as if looking from the VPP side, thus: * *local_* prefix is used for the VPP side. diff --git a/test/doc/log.rst b/test/doc/log.rst deleted file mode 100644 index 5965d5ad..00000000 --- a/test/doc/log.rst +++ /dev/null @@ -1,7 +0,0 @@ -log module -========== - -.. automodule:: log - :members: - :undoc-members: - :show-inheritance: diff --git a/test/doc/modules.rst b/test/doc/modules.rst deleted file mode 100644 index 773283e3..00000000 --- a/test/doc/modules.rst +++ /dev/null @@ -1,24 +0,0 @@ -test -==== - -.. toctree:: - :maxdepth: 4 - - framework - hook - log - run_tests - scapy_handlers - template_bd - test_ip4 - test_ip6 - test_l2bd - test_l2xc - test_lb - test_mpls - test_vxlan - util - vpp_interface - vpp_papi_provider - vpp_pg_interface - vpp_sub_interface diff --git a/test/doc/run_tests.rst b/test/doc/run_tests.rst deleted file mode 100644 index 30cec4e3..00000000 --- a/test/doc/run_tests.rst +++ /dev/null @@ -1,7 +0,0 @@ -run_tests module -================ - -.. automodule:: run_tests - :members: - :undoc-members: - :show-inheritance: diff --git a/test/doc/scapy_handlers.rst b/test/doc/scapy_handlers.rst deleted file mode 100644 index 6717326a..00000000 --- a/test/doc/scapy_handlers.rst +++ /dev/null @@ -1,22 +0,0 @@ -scapy_handlers package -====================== - -Submodules ----------- - -scapy_handlers.vxlan module ---------------------------- - -.. automodule:: scapy_handlers.vxlan - :members: - :undoc-members: - :show-inheritance: - - -Module contents ---------------- - -.. automodule:: scapy_handlers - :members: - :undoc-members: - :show-inheritance: diff --git a/test/doc/template_bd.rst b/test/doc/template_bd.rst deleted file mode 100644 index e173d27a..00000000 --- a/test/doc/template_bd.rst +++ /dev/null @@ -1,7 +0,0 @@ -template_bd module -================== - -.. automodule:: template_bd - :members: - :undoc-members: - :show-inheritance: diff --git a/test/doc/test_ip4.rst b/test/doc/test_ip4.rst deleted file mode 100644 index c3c3a23a..00000000 --- a/test/doc/test_ip4.rst +++ /dev/null @@ -1,7 +0,0 @@ -test_ip module -============== - -.. automodule:: test_ip4 - :members: - :undoc-members: - :show-inheritance: diff --git a/test/doc/test_ip6.rst b/test/doc/test_ip6.rst deleted file mode 100644 index 63461c3c..00000000 --- a/test/doc/test_ip6.rst +++ /dev/null @@ -1,7 +0,0 @@ -test_ip6 module -=============== - -.. automodule:: test_ip6 - :members: - :undoc-members: - :show-inheritance: diff --git a/test/doc/test_l2bd.rst b/test/doc/test_l2bd.rst deleted file mode 100644 index 8a80dfe6..00000000 --- a/test/doc/test_l2bd.rst +++ /dev/null @@ -1,7 +0,0 @@ -test_l2bd module -================ - -.. automodule:: test_l2bd - :members: - :undoc-members: - :show-inheritance: diff --git a/test/doc/test_l2xc.rst b/test/doc/test_l2xc.rst deleted file mode 100644 index caf99bad..00000000 --- a/test/doc/test_l2xc.rst +++ /dev/null @@ -1,7 +0,0 @@ -test_l2xc module -================ - -.. automodule:: test_l2xc - :members: - :undoc-members: - :show-inheritance: diff --git a/test/doc/test_lb.rst b/test/doc/test_lb.rst deleted file mode 100644 index e134b29c..00000000 --- a/test/doc/test_lb.rst +++ /dev/null @@ -1,7 +0,0 @@ -test_lb module -============== - -.. automodule:: test_lb - :members: - :undoc-members: - :show-inheritance: diff --git a/test/doc/test_mpls.rst b/test/doc/test_mpls.rst deleted file mode 100644 index e4c8ce0e..00000000 --- a/test/doc/test_mpls.rst +++ /dev/null @@ -1,7 +0,0 @@ -test_mpls module -================ - -.. automodule:: test_mpls - :members: - :undoc-members: - :show-inheritance: diff --git a/test/doc/test_vxlan.rst b/test/doc/test_vxlan.rst deleted file mode 100644 index 7f40c319..00000000 --- a/test/doc/test_vxlan.rst +++ /dev/null @@ -1,7 +0,0 @@ -test_vxlan module -================= - -.. automodule:: test_vxlan - :members: - :undoc-members: - :show-inheritance: diff --git a/test/doc/util.rst b/test/doc/util.rst deleted file mode 100644 index d8ad0d14..00000000 --- a/test/doc/util.rst +++ /dev/null @@ -1,7 +0,0 @@ -util module -=========== - -.. automodule:: util - :members: - :undoc-members: - :show-inheritance: diff --git a/test/doc/vpp_interface.rst b/test/doc/vpp_interface.rst deleted file mode 100644 index 32bb009b..00000000 --- a/test/doc/vpp_interface.rst +++ /dev/null @@ -1,7 +0,0 @@ -vpp_interface module -==================== - -.. automodule:: vpp_interface - :members: - :undoc-members: - :show-inheritance: diff --git a/test/doc/vpp_papi_provider.rst b/test/doc/vpp_papi_provider.rst deleted file mode 100644 index c9efe85a..00000000 --- a/test/doc/vpp_papi_provider.rst +++ /dev/null @@ -1,7 +0,0 @@ -vpp_papi_provider module -======================== - -.. automodule:: vpp_papi_provider - :members: - :undoc-members: - :show-inheritance: diff --git a/test/doc/vpp_pg_interface.rst b/test/doc/vpp_pg_interface.rst deleted file mode 100644 index 85f54f62..00000000 --- a/test/doc/vpp_pg_interface.rst +++ /dev/null @@ -1,7 +0,0 @@ -vpp_pg_interface module -======================= - -.. automodule:: vpp_pg_interface - :members: - :undoc-members: - :show-inheritance: diff --git a/test/doc/vpp_sub_interface.rst b/test/doc/vpp_sub_interface.rst deleted file mode 100644 index 5ff5300b..00000000 --- a/test/doc/vpp_sub_interface.rst +++ /dev/null @@ -1,7 +0,0 @@ -vpp_sub_interface module -======================== - -.. automodule:: vpp_sub_interface - :members: - :undoc-members: - :show-inheritance: diff --git a/test/framework.py b/test/framework.py index fdb600c4..227428ef 100644 --- a/test/framework.py +++ b/test/framework.py @@ -29,22 +29,15 @@ class _PacketInfo(object): Help process information about the next packet. Set variables to default values. - @property index - Integer variable to store the index of the packet. - @property src - Integer variable to store the index of the source packet generator - interface of the packet. - @property dst - Integer variable to store the index of the destination packet generator - interface of the packet. - @property data - Object variable to store the copy of the former packet. - - """ + #: Store the index of the packet. index = -1 + #: Store the index of the source packet generator interface of the packet. src = -1 + #: Store the index of the destination packet generator interface + #: of the packet. dst = -1 + #: Store the copy of the former packet. data = None @@ -54,12 +47,8 @@ def pump_output(out, queue): class VppTestCase(unittest.TestCase): - """ - Subclass of the python unittest.TestCase class. - - This subclass is a base class for test cases that are implemented as classes - It provides methods to create and run test case. - + """This subclass is a base class for VPP test cases that are implemented as + classes. It provides methods to create and run test case. """ @property @@ -189,7 +178,7 @@ class VppTestCase(unittest.TestCase): cls.packet_infos = {} cls.verbose = 0 print(double_line_delim) - print(colorize(getdoc(cls), YELLOW)) + print(colorize(getdoc(cls).splitlines()[0], YELLOW)) print(double_line_delim) # need to catch exceptions here because if we raise, then the cleanup # doesn't get called and we might end with a zombie vpp diff --git a/test/test_ip4.py b/test/test_ip4.py index 48155a5a..36a907a6 100644 --- a/test/test_ip4.py +++ b/test/test_ip4.py @@ -2,24 +2,38 @@ import unittest import socket -from logging import * from framework import VppTestCase, VppTestRunner +from vpp_interface import VppInterface from vpp_sub_interface import VppSubInterface, VppDot1QSubint, VppDot1ADSubint from scapy.packet import Raw -from scapy.layers.l2 import Ether, Dot1Q, ARP +from scapy.layers.l2 import Ether, Dot1Q from scapy.layers.inet import IP, UDP class TestIPv4(VppTestCase): """ IPv4 Test Case """ - @classmethod - def setUpClass(cls): - super(TestIPv4, cls).setUpClass() - def setUp(self): + """ + Perform test setup before test case. + + **Config:** + - create 3 pg interfaces + - untagged pg0 interface + - Dot1Q subinterface on pg1 + - Dot1AD subinterface on pg2 + - setup interfaces: + - put it into UP state + - set IPv4 addresses + - resolve neighbor address using ARP + - configure 200 fib entries + + :ivar list interfaces: pg interfaces and subinterfaces. + :ivar dict flows: IPv4 packet flows in test. + :ivar list pg_if_packet_sizes: packet sizes in test. + """ super(TestIPv4, self).setUp() # create 3 pg interfaces @@ -49,16 +63,26 @@ class TestIPv4(VppTestCase): i.config_ip4() i.resolve_arp() - # config 2M FIB enries + # config 2M FIB entries self.config_fib_entries(200) def tearDown(self): + """Run standard test teardown and log ``show ip arp``.""" super(TestIPv4, self).tearDown() if not self.vpp_dead: - info(self.vapi.cli("show ip arp")) + self.logger.info(self.vapi.cli("show ip arp")) # info(self.vapi.cli("show ip fib")) # many entries def config_fib_entries(self, count): + """For each interface add to the FIB table *count* routes to + "10.0.0.1/32" destination with interface's local address as next-hop + address. + + :param int count: Number of FIB entries. + + - *TODO:* check if the next-hop address shouldn't be remote address + instead of local address. + """ n_int = len(self.interfaces) percent = 0 counter = 0.0 @@ -69,13 +93,18 @@ class TestIPv4(VppTestCase): for j in range(count / n_int): self.vapi.ip_add_del_route( dest_addr, dest_addr_len, next_hop_address) - counter = counter + 1 + counter += 1 if counter / count * 100 > percent: - info("Configure %d FIB entries .. %d%% done" % - (count, percent)) - percent = percent + 1 + self.logger.info("Configure %d FIB entries .. %d%% done" % + (count, percent)) + percent += 1 def create_stream(self, src_if, packet_sizes): + """Create input packet stream for defined interface. + + :param VppInterface src_if: Interface to create packet stream for. + :param list packet_sizes: Required packet sizes. + """ pkts = [] for i in range(0, 257): dst_if = self.flows[src_if][i % 2] @@ -95,7 +124,13 @@ class TestIPv4(VppTestCase): return pkts def verify_capture(self, dst_if, capture): - info("Verifying capture on interface %s" % dst_if.name) + """Verify captured input packet stream for defined interface. + + :param VppInterface dst_if: Interface to verify captured packet stream + for. + :param list capture: Captured packet stream. + """ + self.logger.info("Verifying capture on interface %s" % dst_if.name) last_info = dict() for i in self.interfaces: last_info[i.sw_if_index] = None @@ -114,8 +149,8 @@ class TestIPv4(VppTestCase): payload_info = self.payload_to_info(str(packet[Raw])) packet_index = payload_info.index self.assertEqual(payload_info.dst, dst_sw_if_index) - debug("Got packet on port %s: src=%u (id=%u)" % - (dst_if.name, payload_info.src, packet_index)) + self.logger.debug("Got packet on port %s: src=%u (id=%u)" % + (dst_if.name, payload_info.src, packet_index)) next_info = self.get_next_packet_info_for_interface2( payload_info.src, dst_sw_if_index, last_info[payload_info.src]) @@ -129,8 +164,8 @@ class TestIPv4(VppTestCase): self.assertEqual(udp.sport, saved_packet[UDP].sport) self.assertEqual(udp.dport, saved_packet[UDP].dport) except: - error("Unexpected or invalid packet:") - error(packet.show()) + self.logger.error("Unexpected or invalid packet:") + self.logger.error(packet.show()) raise for i in self.interfaces: remaining_packet = self.get_next_packet_info_for_interface2( @@ -141,7 +176,14 @@ class TestIPv4(VppTestCase): (dst_if.name, i.name)) def test_fib(self): - """ IPv4 FIB test """ + """ IPv4 FIB test + + Test scenario: + + - Create IPv4 stream for pg0 interface + - Create IPv4 tagged streams for pg1's and pg2's subinterface. + - Send and verify received packets on each interface. + """ pkts = self.create_stream(self.pg0, self.pg_if_packet_sizes) self.pg0.add_stream(pkts) diff --git a/test/test_ip4_irb.py b/test/test_ip4_irb.py index 412575db..cf2bb150 100644 --- a/test/test_ip4_irb.py +++ b/test/test_ip4_irb.py @@ -1,39 +1,51 @@ #!/usr/bin/env python +"""IRB Test Case HLD: + +**config** + - L2 MAC learning enabled in l2bd + - 2 routed interfaces untagged, bvi (Bridge Virtual Interface) + - 2 bridged interfaces in l2bd with bvi + +**test** + - sending ip4 eth pkts between routed interfaces + - 2 routed interfaces + - 2 bridged interfaces + + - 64B, 512B, 1518B, 9200B (ether_size) + + - burst of pkts per interface + - 257pkts per burst + - routed pkts hitting different FIB entries + - bridged pkts hitting different MAC entries + +**verify** + - all packets received correctly + +""" + import unittest -from random import choice, randint +from random import choice from scapy.packet import Raw from scapy.layers.l2 import Ether from scapy.layers.inet import IP, UDP -from logging import * from framework import VppTestCase, VppTestRunner -""" IRB Test Case - -config - L2 MAC learning enabled in l2bd - 2 routed interfaces untagged, bvi - 2 bridged interfaces in l2bd with bvi -test - sending ip4 eth pkts between routed interfaces - 2 routed interfaces - 2 bridged interfaces - 64B, 512B, 1518B, 9200B (ether_size) - burst of pkts per interface - 257pkts per burst - routed pkts hitting different FIB entries - bridged pkts hitting different MAC entries -verify - all packets received correctly -""" - class TestIpIrb(VppTestCase): - """ IRB Test Case """ + """IRB Test Case""" @classmethod def setUpClass(cls): + """ + #. Create BD with MAC learning enabled and put interfaces to this BD. + #. Configure IPv4 addresses on loopback interface and routed interface. + #. Configure MAC address binding to IPv4 neighbors on loop0. + #. Configure MAC address on pg2. + #. Loopback BVI interface has remote hosts, one half of hosts are + behind pg0 second behind pg1. + """ super(TestIpIrb, cls).setUpClass() cls.pg_if_packet_sizes = [64, 512, 1518, 9018] # packet sizes @@ -58,27 +70,34 @@ class TestIpIrb(VppTestCase): cls.vapi.sw_interface_set_l2_bridge( cls.pg1.sw_if_index, bd_id=cls.bd_id) + # Configure IPv4 addresses on loopback interface and routed interface cls.loop0.config_ip4() cls.pg2.config_ip4() - # configure MAC address binding to IPv4 neighbors on loop0 + # Configure MAC address binding to IPv4 neighbors on loop0 cls.loop0.generate_remote_hosts(cls.remote_hosts_count) - cls.loop0.configure_extend_ipv4_mac_binding() + cls.loop0.configure_ipv4_neighbors() # configure MAC address on pg2 cls.pg2.resolve_arp() - # one half of hosts are behind pg0 second behind pg1 + # Loopback BVI interface has remote hosts, one half of hosts are behind + # pg0 second behind pg1 half = cls.remote_hosts_count // 2 cls.pg0.remote_hosts = cls.loop0.remote_hosts[:half] cls.pg1.remote_hosts = cls.loop0.remote_hosts[half:] def tearDown(self): + """Run standard test teardown and log ``show l2patch``, + ``show l2fib verbose``,``show bridge-domain detail``, + ``show ip arp``. + """ super(TestIpIrb, self).tearDown() if not self.vpp_dead: - info(self.vapi.cli("show l2patch")) - info(self.vapi.cli("show l2fib verbose")) - info(self.vapi.cli("show bridge-domain %s detail" % self.bd_id)) - info(self.vapi.cli("show ip arp")) + self.logger.info(self.vapi.cli("show l2patch")) + self.logger.info(self.vapi.cli("show l2fib verbose")) + self.logger.info(self.vapi.cli("show bridge-domain %s detail" % + self.bd_id)) + self.logger.info(self.vapi.cli("show ip arp")) def create_stream(self, src_ip_if, dst_ip_if, packet_sizes): pkts = [] @@ -200,8 +219,8 @@ class TestIpIrb(VppTestCase): """ IPv4 IRB test 1 Test scenario: - ip traffic from pg2 interface must ends in both pg0 and pg1 - - arp entry present in loop0 interface for dst IP + - ip traffic from pg2 interface must ends in both pg0 and pg1 + - arp entry present in loop0 interface for destination IP - no l2 entree configured, pg0 and pg1 are same """ @@ -224,7 +243,7 @@ class TestIpIrb(VppTestCase): """ IPv4 IRB test 2 Test scenario: - ip traffic from pg0 and pg1 ends on pg2 + - ip traffic from pg0 and pg1 ends on pg2 """ stream1 = self.create_stream_l2_to_ip( diff --git a/test/test_ip6.py b/test/test_ip6.py index 92bb350d..bff829b7 100644 --- a/test/test_ip6.py +++ b/test/test_ip6.py @@ -2,14 +2,13 @@ import unittest import socket -from logging import * from framework import VppTestCase, VppTestRunner -from vpp_sub_interface import VppSubInterface, VppDot1QSubint, VppDot1ADSubint +from vpp_sub_interface import VppSubInterface, VppDot1QSubint from scapy.packet import Raw from scapy.layers.l2 import Ether, Dot1Q -from scapy.layers.inet6 import ICMPv6ND_NS, IPv6, UDP +from scapy.layers.inet6 import IPv6, UDP class TestIPv6(VppTestCase): @@ -20,6 +19,26 @@ class TestIPv6(VppTestCase): super(TestIPv6, cls).setUpClass() def setUp(self): + """ + Perform test setup before test case. + + **Config:** + - create 3 pg interfaces + - untagged pg0 interface + - Dot1Q subinterface on pg1 + - Dot1AD subinterface on pg2 + - setup interfaces: + - put it into UP state + - set IPv6 addresses + - resolve neighbor address using NDP + - configure 200 fib entries + + :ivar list interfaces: pg interfaces and subinterfaces. + :ivar dict flows: IPv4 packet flows in test. + :ivar list pg_if_packet_sizes: packet sizes in test. + + *TODO:* Create AD sub interface + """ super(TestIPv6, self).setUp() # create 3 pg interfaces @@ -28,8 +47,9 @@ class TestIPv6(VppTestCase): # create 2 subinterfaces for p1 and pg2 self.sub_interfaces = [ VppDot1QSubint(self, self.pg1, 100), - VppDot1QSubint(self, self.pg2, 200)] + VppDot1QSubint(self, self.pg2, 200) # TODO: VppDot1ADSubint(self, self.pg2, 200, 300, 400) + ] # packet flows mapping pg0 -> pg1.sub, pg2.sub, etc. self.flows = dict() @@ -50,16 +70,26 @@ class TestIPv6(VppTestCase): i.config_ip6() i.resolve_ndp() - # config 2M FIB enries + # config 2M FIB entries self.config_fib_entries(200) def tearDown(self): + """Run standard test teardown and log ``show ip6 neighbors``.""" super(TestIPv6, self).tearDown() if not self.vpp_dead: - info(self.vapi.cli("show ip6 neighbors")) + self.logger.info(self.vapi.cli("show ip6 neighbors")) # info(self.vapi.cli("show ip6 fib")) # many entries def config_fib_entries(self, count): + """For each interface add to the FIB table *count* routes to + "fd02::1/128" destination with interface's local address as next-hop + address. + + :param int count: Number of FIB entries. + + - *TODO:* check if the next-hop address shouldn't be remote address + instead of local address. + """ n_int = len(self.interfaces) percent = 0 counter = 0.0 @@ -70,13 +100,18 @@ class TestIPv6(VppTestCase): for j in range(count / n_int): self.vapi.ip_add_del_route( dest_addr, dest_addr_len, next_hop_address, is_ipv6=1) - counter = counter + 1 + counter += 1 if counter / count * 100 > percent: - info("Configure %d FIB entries .. %d%% done" % + self.logger.info("Configure %d FIB entries .. %d%% done" % (count, percent)) - percent = percent + 1 + percent += 1 def create_stream(self, src_if, packet_sizes): + """Create input packet stream for defined interface. + + :param VppInterface src_if: Interface to create packet stream for. + :param list packet_sizes: Required packet sizes. + """ pkts = [] for i in range(0, 257): dst_if = self.flows[src_if][i % 2] @@ -96,7 +131,13 @@ class TestIPv6(VppTestCase): return pkts def verify_capture(self, dst_if, capture): - info("Verifying capture on interface %s" % dst_if.name) + """Verify captured input packet stream for defined interface. + + :param VppInterface dst_if: Interface to verify captured packet stream + for. + :param list capture: Captured packet stream. + """ + self.logger.info("Verifying capture on interface %s" % dst_if.name) last_info = dict() for i in self.interfaces: last_info[i.sw_if_index] = None @@ -115,8 +156,8 @@ class TestIPv6(VppTestCase): payload_info = self.payload_to_info(str(packet[Raw])) packet_index = payload_info.index self.assertEqual(payload_info.dst, dst_sw_if_index) - debug("Got packet on port %s: src=%u (id=%u)" % - (dst_if.name, payload_info.src, packet_index)) + self.logger.debug("Got packet on port %s: src=%u (id=%u)" % + (dst_if.name, payload_info.src, packet_index)) next_info = self.get_next_packet_info_for_interface2( payload_info.src, dst_sw_if_index, last_info[payload_info.src]) @@ -130,8 +171,8 @@ class TestIPv6(VppTestCase): self.assertEqual(udp.sport, saved_packet[UDP].sport) self.assertEqual(udp.dport, saved_packet[UDP].dport) except: - error("Unexpected or invalid packet:") - error(packet.show()) + self.logger.error("Unexpected or invalid packet:") + self.logger.error(packet.show()) raise for i in self.interfaces: remaining_packet = self.get_next_packet_info_for_interface2( @@ -142,7 +183,13 @@ class TestIPv6(VppTestCase): (dst_if.name, i.name)) def test_fib(self): - """ IPv6 FIB test """ + """ IPv6 FIB test + + Test scenario: + - Create IPv6 stream for pg0 interface + - Create IPv6 tagged streams for pg1's and pg2's subinterface. + - Send and verify received packets on each interface. + """ pkts = self.create_stream(self.pg0, self.pg_if_packet_sizes) self.pg0.add_stream(pkts) diff --git a/test/vpp_interface.py b/test/vpp_interface.py index d74248a3..30ef8ae7 100644 --- a/test/vpp_interface.py +++ b/test/vpp_interface.py @@ -6,59 +6,57 @@ from util import Host class VppInterface(object): - """ - Generic VPP interface - """ + """Generic VPP interface.""" __metaclass__ = ABCMeta @property def sw_if_index(self): - """Interface index assigned by VPP""" + """Interface index assigned by VPP.""" return self._sw_if_index @property def remote_mac(self): - """MAC-address of the remote interface "connected" to this interface""" + """MAC-address of the remote interface "connected" to this interface.""" return self._remote_hosts[0].mac @property def local_mac(self): - """MAC-address of the VPP interface""" + """MAC-address of the VPP interface.""" return self._local_mac @property def local_ip4(self): - """Local IPv4 address on VPP interface (string)""" + """Local IPv4 address on VPP interface (string).""" return self._local_ip4 @property def local_ip4n(self): - """Local IPv4 address - raw, suitable as API parameter""" + """Local IPv4 address - raw, suitable as API parameter.""" return socket.inet_pton(socket.AF_INET, self._local_ip4) @property def remote_ip4(self): - """IPv4 address of remote peer "connected" to this interface""" + """IPv4 address of remote peer "connected" to this interface.""" return self._remote_hosts[0].ip4 @property def remote_ip4n(self): - """IPv4 address of remote peer - raw, suitable as API parameter""" + """IPv4 address of remote peer - raw, suitable as API parameter.""" return socket.inet_pton(socket.AF_INET, self.remote_ip4) @property def local_ip6(self): - """Local IPv6 address on VPP interface (string)""" + """Local IPv6 address on VPP interface (string).""" return self._local_ip6 @property def local_ip6n(self): - """Local IPv6 address - raw, suitable as API parameter""" + """Local IPv6 address - raw, suitable as API parameter.""" return socket.inet_pton(socket.AF_INET6, self.local_ip6) @property def remote_ip6(self): - """IPv6 address of remote peer "connected" to this interface""" + """IPv6 address of remote peer "connected" to this interface.""" return self._remote_hosts[0].ip6 @property @@ -68,17 +66,17 @@ class VppInterface(object): @property def name(self): - """Name of the interface""" + """Name of the interface.""" return self._name @property def dump(self): - """Raw result of sw_interface_dump for this interface""" + """RAW result of sw_interface_dump for this interface.""" return self._dump @property def test(self): - """Test case creating this interface""" + """Test case creating this interface.""" return self._test @property @@ -88,20 +86,44 @@ class VppInterface(object): @remote_hosts.setter def remote_hosts(self, value): + """ + :param list value: List of remote hosts. + """ self._remote_hosts = value - #TODO: set hosts_by dicts + self._hosts_by_mac = {} + self._hosts_by_ip4 = {} + self._hosts_by_ip6 = {} + for host in self._remote_hosts: + self._hosts_by_mac[host.mac] = host + self._hosts_by_ip4[host.ip4] = host + self._hosts_by_ip6[host.ip6] = host def host_by_mac(self, mac): + """ + :param ip: MAC address to find host by. + :return: Host object assigned to interface. + """ return self._hosts_by_mac[mac] def host_by_ip4(self, ip): + """ + :param ip: IPv4 address to find host by. + :return: Host object assigned to interface. + """ return self._hosts_by_ip4[ip] def host_by_ip6(self, ip): + """ + :param ip: IPv6 address to find host by. + :return: Host object assigned to interface. + """ return self._hosts_by_ip6[ip] def generate_remote_hosts(self, count=1): - """Generate and add remote hosts for the interface.""" + """Generate and add remote hosts for the interface. + + :param int count: Number of generated remote hosts. + """ self._remote_hosts = [] self._hosts_by_mac = {} self._hosts_by_ip4 = {} @@ -117,7 +139,7 @@ class VppInterface(object): self._hosts_by_ip6[ip6] = host def post_init_setup(self): - """Additional setup run after creating an interface object""" + """Additional setup run after creating an interface object.""" self.generate_remote_hosts() @@ -149,21 +171,21 @@ class VppInterface(object): (self.__name__, self.remote_mac, self.remote_ip4, self.local_ip4)) def config_ip4(self): - """Configure IPv4 address on the VPP interface""" + """Configure IPv4 address on the VPP interface.""" addr = self.local_ip4n addr_len = 24 self.test.vapi.sw_interface_add_del_address( self.sw_if_index, addr, addr_len) - def configure_extend_ipv4_mac_binding(self): - """Configure neighbor MAC to IPv4 addresses.""" + def configure_ipv4_neighbors(self): + """For every remote host assign neighbor's MAC to IPv4 addresses.""" for host in self._remote_hosts: macn = host.mac.replace(":", "").decode('hex') ipn = host.ip4n self.test.vapi.ip_neighbor_add_del(self.sw_if_index, macn, ipn) def config_ip6(self): - """Configure IPv6 address on the VPP interface""" + """Configure IPv6 address on the VPP interface.""" addr = self._local_ip6n addr_len = 64 self.test.vapi.sw_interface_add_del_address( @@ -171,30 +193,31 @@ class VppInterface(object): def set_table_ip4(self, table_id): """Set the interface in a IPv4 Table. - Must be called before configuring IP4 addresses""" + + .. note:: Must be called before configuring IP4 addresses.""" self.test.vapi.sw_interface_set_table( self.sw_if_index, 0, table_id) def set_table_ip6(self, table_id): """Set the interface in a IPv6 Table. - Must be called before configuring IP6 addresses""" + + .. note:: Must be called before configuring IP6 addresses. + """ self.test.vapi.sw_interface_set_table( self.sw_if_index, 1, table_id) def disable_ipv6_ra(self): - """Configure IPv6 RA suppress on the VPP interface""" + """Configure IPv6 RA suppress on the VPP interface.""" self.test.vapi.sw_interface_ra_suppress(self.sw_if_index) def admin_up(self): - """ Put interface ADMIN-UP """ + """Put interface ADMIN-UP.""" self.test.vapi.sw_interface_set_flags(self.sw_if_index, admin_up_down=1) def add_sub_if(self, sub_if): - """ - Register a sub-interface with this interface + """Register a sub-interface with this interface. :param sub_if: sub-interface - """ if not hasattr(self, 'sub_if'): self.sub_if = sub_if @@ -205,6 +228,6 @@ class VppInterface(object): self.sub_if = sub_if def enable_mpls(self): - """Enable MPLS on the VPP interface""" + """Enable MPLS on the VPP interface.""" self.test.vapi.sw_interface_enable_disable_mpls( self.sw_if_index) diff --git a/test/vpp_lo_interface.py b/test/vpp_lo_interface.py index ed9ac725..ef815251 100644 --- a/test/vpp_lo_interface.py +++ b/test/vpp_lo_interface.py @@ -3,9 +3,7 @@ from vpp_interface import VppInterface class VppLoInterface(VppInterface): - """ - VPP loopback interface - """ + """VPP loopback interface.""" def __init__(self, test, lo_index): """ Create VPP loopback interface """ -- cgit 1.2.3-korg From 177bbdcd8fa4e7621c5bdd3afd8c6e74b603e096 Mon Sep 17 00:00:00 2001 From: Neale Ranns Date: Tue, 15 Nov 2016 09:46:51 +0000 Subject: GRE tests and fixes Change-Id: I234240e9bdd4b69ad64a17b1449ae1e81c0edaca Signed-off-by: Neale Ranns --- .gitignore | 3 + test/Makefile | 30 +- test/framework.py | 2 +- test/patches/scapy-2.3.3/gre-layers.patch | 25 ++ test/test_gre.py | 700 ++++++++++++++++++++++++++++++ test/vpp_gre_interface.py | 34 ++ test/vpp_interface.py | 31 ++ test/vpp_ip_route.py | 46 ++ test/vpp_papi_provider.py | 51 +++ test/vpp_sub_interface.py | 3 + vnet/vnet/adj/adj.c | 10 +- vnet/vnet/adj/adj_internal.h | 3 +- vnet/vnet/adj/adj_nbr.c | 4 +- vnet/vnet/ethernet/arp.c | 2 +- vnet/vnet/gre/gre.c | 43 +- vnet/vnet/gre/gre.h | 77 +++- vnet/vnet/gre/interface.c | 51 ++- vnet/vnet/gre/node.c | 21 +- vnet/vnet/interface_output.c | 29 +- vnet/vnet/ip/ip6_forward.c | 2 + vnet/vnet/ip/ip6_neighbor.c | 2 - vpp/vpp-api/api.c | 2 +- 22 files changed, 1095 insertions(+), 76 deletions(-) create mode 100644 test/patches/scapy-2.3.3/gre-layers.patch create mode 100644 test/test_gre.py create mode 100644 test/vpp_gre_interface.py create mode 100644 test/vpp_ip_route.py (limited to 'test/vpp_interface.py') diff --git a/.gitignore b/.gitignore index 5a8384a8..cec432c5 100644 --- a/.gitignore +++ b/.gitignore @@ -84,3 +84,6 @@ GTAGS /vpp-api/python/vpp_papi.egg-info /vpp-api/python/vpp_papi/memclnt.py /vpp-api/python/vpp_papi/vpe.py + +# Build files in the test directory +/test/*.ok diff --git a/test/Makefile b/test/Makefile index aac637d3..de6aaa7a 100644 --- a/test/Makefile +++ b/test/Makefile @@ -6,23 +6,45 @@ ifndef VPP_PYTHON_PREFIX endif PYTHON_VENV_PATH=$(VPP_PYTHON_PREFIX)/virtualenv -PYTHON_DEPENDS=scapy pexpect +PYTHON_DEPENDS=scapy==2.3.3 pexpect +SCAPY_SOURCE=$(WS_ROOT)/build-root/python/virtualenv/lib/python2.7/site-packages/ -test: wipe verify-python-path + +.pip-install.ok: @virtualenv $(PYTHON_VENV_PATH) @bash -c "source $(PYTHON_VENV_PATH)/bin/activate && pip install $(PYTHON_DEPENDS)" + @touch $@ + +.pip-patch.ok: .pip-install.ok + @echo --- patching --- + for f in $(CURDIR)/patches/scapy-2.3.3/*.patch ; do \ + echo Applying patch: $$(basename $$f) ; \ + patch -p1 -d $(SCAPY_SOURCE) < $$f ; \ + done + @touch $@ + +.install.ok: .pip-patch.ok @bash -c "source $(PYTHON_VENV_PATH)/bin/activate && cd $(WS_ROOT)/vpp-api/python && python setup.py install" + @touch $@ + +PHONIES=.install.ok .pip-patch.ok .pip-install.ok +.PHONY: $(PHONIES) + +test: reset verify-python-path .install.ok @bash -c "source $(PYTHON_VENV_PATH)/bin/activate && python run_tests.py discover -p test_$(TEST)\"*.py\"" -retest: wipe verify-python-path +retest: reset verify-python-path @bash -c "source $(PYTHON_VENV_PATH)/bin/activate && python run_tests.py discover -p test_$(TEST)\"*.py\"" .PHONY: wipe doc -wipe: +reset: @rm -f /dev/shm/vpp-unittest-* @rm -rf /tmp/vpp-unittest-* +wipe: reset + @rm -f $(PHONIES) + doc: verify-python-path @virtualenv $(PYTHON_VENV_PATH) @bash -c "source $(PYTHON_VENV_PATH)/bin/activate && pip install $(PYTHON_DEPENDS) sphinx" diff --git a/test/framework.py b/test/framework.py index 227428ef..1375f076 100644 --- a/test/framework.py +++ b/test/framework.py @@ -265,8 +265,8 @@ class VppTestCase(unittest.TestCase): def tearDown(self): """ Show various debug prints after each test """ if not self.vpp_dead: - self.logger.info(self.vapi.ppcli("show int")) self.logger.debug(self.vapi.cli("show trace")) + self.logger.info(self.vapi.ppcli("show int")) self.logger.info(self.vapi.ppcli("show hardware")) self.logger.info(self.vapi.ppcli("show error")) self.logger.info(self.vapi.ppcli("show run")) diff --git a/test/patches/scapy-2.3.3/gre-layers.patch b/test/patches/scapy-2.3.3/gre-layers.patch new file mode 100644 index 00000000..605a705b --- /dev/null +++ b/test/patches/scapy-2.3.3/gre-layers.patch @@ -0,0 +1,25 @@ +diff --git a/scapy/layers/inet6.py b/scapy/layers/inet6.py +index 03b80ec..a7e1e0f 100644 +--- a/scapy/layers/inet6.py ++++ b/scapy/layers/inet6.py +@@ -3722,6 +3722,7 @@ conf.l2types.register(31, IPv6) + + bind_layers(Ether, IPv6, type = 0x86dd ) + bind_layers(CookedLinux, IPv6, proto = 0x86dd ) ++bind_layers(GRE, IPv6, proto = 0x86dd ) + bind_layers(IPerror6, TCPerror, nh = socket.IPPROTO_TCP ) + bind_layers(IPerror6, UDPerror, nh = socket.IPPROTO_UDP ) + bind_layers(IPv6, TCP, nh = socket.IPPROTO_TCP ) +diff --git a/scapy/layers/l2.py b/scapy/layers/l2.py +index 4f491d2..661a5da 100644 +--- a/scapy/layers/l2.py ++++ b/scapy/layers/l2.py +@@ -628,7 +628,7 @@ bind_layers( CookedLinux, EAPOL, proto=34958) + bind_layers( GRE, LLC, proto=122) + bind_layers( GRE, Dot1Q, proto=33024) + bind_layers( GRE, Dot1AD, type=0x88a8) +-bind_layers( GRE, Ether, proto=1) ++bind_layers( GRE, Ether, proto=0x6558) + bind_layers( GRE, ARP, proto=2054) + bind_layers( GRE, EAPOL, proto=34958) + bind_layers( GRE, GRErouting, { "routing_present" : 1 } ) diff --git a/test/test_gre.py b/test/test_gre.py new file mode 100644 index 00000000..b5a1e346 --- /dev/null +++ b/test/test_gre.py @@ -0,0 +1,700 @@ +#!/usr/bin/env python + +import unittest +import socket +from logging import * + +from framework import VppTestCase, VppTestRunner +from vpp_sub_interface import VppSubInterface, VppDot1QSubint, VppDot1ADSubint +from vpp_gre_interface import VppGreInterface +from vpp_ip_route import IpRoute, IpPath +from vpp_papi_provider import L2_VTR_OP + +from scapy.packet import Raw +from scapy.layers.l2 import Ether, Dot1Q, ARP, GRE +from scapy.layers.inet import IP, UDP +from scapy.layers.inet6 import ICMPv6ND_NS, IPv6, UDP +from scapy.contrib.mpls import MPLS +from scapy.volatile import RandMAC, RandIP + + +class TestGRE(VppTestCase): + """ GRE Test Case """ + + @classmethod + def setUpClass(cls): + super(TestGRE, cls).setUpClass() + + def setUp(self): + super(TestGRE, self).setUp() + + # create 2 pg interfaces - set one in a non-default table. + self.create_pg_interfaces(range(2)) + + self.pg1.set_table_ip4(1) + for i in self.pg_interfaces: + i.admin_up() + i.config_ip4() + i.resolve_arp() + + def tearDown(self): + super(TestGRE, self).tearDown() + + def create_stream_ip4(self, src_if, src_ip, dst_ip): + pkts = [] + for i in range(0, 257): + info = self.create_packet_info(src_if.sw_if_index, + src_if.sw_if_index) + 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_tunnel_stream_4o4(self, src_if, + tunnel_src, tunnel_dst, + src_ip, dst_ip): + pkts = [] + for i in range(0, 257): + info = self.create_packet_info(src_if.sw_if_index, + src_if.sw_if_index) + payload = self.info_to_payload(info) + p = (Ether(dst=src_if.local_mac, src=src_if.remote_mac) / + IP(src=tunnel_src, dst=tunnel_dst) / + GRE() / + 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_tunnel_stream_6o4(self, src_if, + tunnel_src, tunnel_dst, + src_ip, dst_ip): + pkts = [] + for i in range(0, 257): + info = self.create_packet_info(src_if.sw_if_index, + src_if.sw_if_index) + payload = self.info_to_payload(info) + p = (Ether(dst=src_if.local_mac, src=src_if.remote_mac) / + IP(src=tunnel_src, dst=tunnel_dst) / + GRE() / + IPv6(src=src_ip, dst=dst_ip) / + UDP(sport=1234, dport=1234) / + Raw(payload)) + info.data = p.copy() + pkts.append(p) + return pkts + + def create_tunnel_stream_l2o4(self, src_if, + tunnel_src, tunnel_dst): + pkts = [] + for i in range(0, 257): + info = self.create_packet_info(src_if.sw_if_index, + src_if.sw_if_index) + payload = self.info_to_payload(info) + p = (Ether(dst=src_if.local_mac, src=src_if.remote_mac) / + IP(src=tunnel_src, dst=tunnel_dst) / + GRE() / + Ether(dst=RandMAC('*:*:*:*:*:*'), + src=RandMAC('*:*:*:*:*:*')) / + IP(src=str(RandIP()), dst=str(RandIP())) / + UDP(sport=1234, dport=1234) / + Raw(payload)) + info.data = p.copy() + pkts.append(p) + return pkts + + def create_tunnel_stream_vlano4(self, src_if, + tunnel_src, tunnel_dst, vlan): + pkts = [] + for i in range(0, 257): + info = self.create_packet_info(src_if.sw_if_index, + src_if.sw_if_index) + payload = self.info_to_payload(info) + p = (Ether(dst=src_if.local_mac, src=src_if.remote_mac) / + IP(src=tunnel_src, dst=tunnel_dst) / + GRE() / + Ether(dst=RandMAC('*:*:*:*:*:*'), + src=RandMAC('*:*:*:*:*:*')) / + Dot1Q(vlan=vlan) / + IP(src=str(RandIP()), dst=str(RandIP())) / + 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(ICMPv6ND_RA)): + capture.remove(p) + return capture + + def verify_tunneled_4o4(self, src_if, capture, sent, + tunnel_src, tunnel_dst): + + capture = self.verify_filter(capture, sent) + self.assertEqual(len(capture), len(sent)) + + for i in range(len(capture)): + try: + tx = sent[i] + rx = capture[i] + + tx_ip = tx[IP] + rx_ip = rx[IP] + + self.assertEqual(rx_ip.src, tunnel_src) + self.assertEqual(rx_ip.dst, tunnel_dst) + + rx_gre = rx[GRE] + rx_ip = rx_gre[IP] + + 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: + rx.show() + tx.show() + raise + + def verify_tunneled_l2o4(self, src_if, capture, sent, + tunnel_src, tunnel_dst): + capture = self.verify_filter(capture, sent) + self.assertEqual(len(capture), len(sent)) + + for i in range(len(capture)): + try: + tx = sent[i] + rx = capture[i] + + tx_ip = tx[IP] + rx_ip = rx[IP] + + self.assertEqual(rx_ip.src, tunnel_src) + self.assertEqual(rx_ip.dst, tunnel_dst) + + rx_gre = rx[GRE] + rx_l2 = rx_gre[Ether] + rx_ip = rx_l2[IP] + tx_gre = tx[GRE] + tx_l2 = tx_gre[Ether] + tx_ip = tx_l2[IP] + + self.assertEqual(rx_ip.src, tx_ip.src) + self.assertEqual(rx_ip.dst, tx_ip.dst) + # bridged, not L3 forwarded, so no TTL decrement + self.assertEqual(rx_ip.ttl, tx_ip.ttl) + + except: + rx.show() + tx.show() + raise + + def verify_tunneled_vlano4(self, src_if, capture, sent, + tunnel_src, tunnel_dst, vlan): + try: + capture = self.verify_filter(capture, sent) + self.assertEqual(len(capture), len(sent)) + except: + capture.show() + raise + + for i in range(len(capture)): + try: + tx = sent[i] + rx = capture[i] + + tx_ip = tx[IP] + rx_ip = rx[IP] + + self.assertEqual(rx_ip.src, tunnel_src) + self.assertEqual(rx_ip.dst, tunnel_dst) + + rx_gre = rx[GRE] + rx_l2 = rx_gre[Ether] + rx_vlan = rx_l2[Dot1Q] + rx_ip = rx_l2[IP] + + self.assertEqual(rx_vlan.vlan, vlan) + + tx_gre = tx[GRE] + tx_l2 = tx_gre[Ether] + tx_ip = tx_l2[IP] + + self.assertEqual(rx_ip.src, tx_ip.src) + self.assertEqual(rx_ip.dst, tx_ip.dst) + # bridged, not L3 forwarded, so no TTL decrement + self.assertEqual(rx_ip.ttl, tx_ip.ttl) + + except: + rx.show() + tx.show() + raise + + def verify_decapped_4o4(self, src_if, capture, sent): + capture = self.verify_filter(capture, sent) + self.assertEqual(len(capture), len(sent)) + + for i in range(len(capture)): + try: + tx = sent[i] + rx = capture[i] + + tx_ip = tx[IP] + rx_ip = rx[IP] + tx_gre = tx[GRE] + tx_ip = tx_gre[IP] + + 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: + rx.show() + tx.show() + raise + + def verify_decapped_6o4(self, src_if, capture, sent): + capture = self.verify_filter(capture, sent) + self.assertEqual(len(capture), len(sent)) + + for i in range(len(capture)): + try: + tx = sent[i] + rx = capture[i] + + tx_ip = tx[IP] + rx_ip = rx[IPv6] + tx_gre = tx[GRE] + tx_ip = tx_gre[IPv6] + + self.assertEqual(rx_ip.src, tx_ip.src) + self.assertEqual(rx_ip.dst, tx_ip.dst) + self.assertEqual(rx_ip.hlim + 1, tx_ip.hlim) + + except: + rx.show() + tx.show() + raise + + def test_gre(self): + """ GRE tunnel Tests """ + + # + # Create an L3 GRE tunnel. + # - set it admin up + # - assign an IP Addres + # - Add a route via the tunnel + # + gre_if = VppGreInterface(self, + self.pg0.local_ip4, + "1.1.1.2") + gre_if.add_vpp_config() + + # + # The double create (create the same tunnel twice) should fail, + # and we should still be able to use the original + # + try: + gre_if.add_vpp_config() + except Exception: + pass + else: + self.fail("Double GRE tunnel add does not fail") + + gre_if.admin_up() + gre_if.config_ip4() + + route_via_tun = IpRoute(self, "4.4.4.4", 32, + [IpPath("0.0.0.0", gre_if.sw_if_index)]) + + route_via_tun.add_vpp_config() + + # + # Send a packet stream that is routed into the tunnel + # - they are all dropped since the tunnel's desintation IP + # is unresolved - or resolves via the default route - which + # which is a drop. + # + tx = self.create_stream_ip4(self.pg0, "5.5.5.5", "4.4.4.4") + self.pg0.add_stream(tx) + + self.pg_enable_capture(self.pg_interfaces) + self.pg_start() + + rx = self.pg0.get_capture() + + try: + self.assertEqual(0, len(rx)) + except: + error("GRE packets forwarded without DIP resolved") + error(rx.show()) + raise + + # + # Add a route that resolves the tunnel's destination + # + route_tun_dst = IpRoute(self, "1.1.1.2", 32, + [IpPath(self.pg0.remote_ip4, self.pg0.sw_if_index)]) + route_tun_dst.add_vpp_config() + + # + # Send a packet stream that is routed into the tunnel + # - packets are GRE encapped + # + self.vapi.cli("clear trace") + tx = self.create_stream_ip4(self.pg0, "5.5.5.5", "4.4.4.4") + self.pg0.add_stream(tx) + + self.pg_enable_capture(self.pg_interfaces) + self.pg_start() + + rx = self.pg0.get_capture() + self.verify_tunneled_4o4(self.pg0, rx, tx, + self.pg0.local_ip4, "1.1.1.2") + + # + # Send tunneled packets that match the created tunnel and + # are decapped and forwarded + # + self.vapi.cli("clear trace") + tx = self.create_tunnel_stream_4o4(self.pg0, + "1.1.1.2", + self.pg0.local_ip4, + self.pg0.local_ip4, + self.pg0.remote_ip4) + self.pg0.add_stream(tx) + + self.pg_enable_capture(self.pg_interfaces) + self.pg_start() + + rx = self.pg0.get_capture() + self.verify_decapped_4o4(self.pg0, rx, tx) + + # + # Send tunneled packets that do not match the tunnel's src + # + self.vapi.cli("clear trace") + tx = self.create_tunnel_stream_4o4(self.pg0, + "1.1.1.3", + self.pg0.local_ip4, + self.pg0.local_ip4, + self.pg0.remote_ip4) + self.pg0.add_stream(tx) + + self.pg_enable_capture(self.pg_interfaces) + self.pg_start() + + rx = self.pg0.get_capture() + try: + self.assertEqual(0, len(rx)) + except: + error("GRE packets forwarded despite no SRC address match") + error(rx.show()) + raise + + # + # Configure IPv6 on the PG interface so we can route IPv6 + # packets + # + self.pg0.config_ip6() + self.pg0.resolve_ndp() + + # + # Send IPv6 tunnel encapslated packets + # - dropped since IPv6 is not enabled on the tunnel + # + self.vapi.cli("clear trace") + tx = self.create_tunnel_stream_6o4(self.pg0, + "1.1.1.2", + self.pg0.local_ip4, + self.pg0.local_ip6, + self.pg0.remote_ip6) + self.pg0.add_stream(tx) + + self.pg_enable_capture(self.pg_interfaces) + self.pg_start() + + rx = self.pg0.get_capture() + try: + self.assertEqual(0, len(rx)) + except: + error("IPv6 GRE packets forwarded despite IPv6 not enabled on tunnel") + error(rx.show()) + raise + + # + # Enable IPv6 on the tunnel + # + gre_if.config_ip6() + + # + # Send IPv6 tunnel encapslated packets + # - forwarded since IPv6 is enabled on the tunnel + # + self.vapi.cli("clear trace") + tx = self.create_tunnel_stream_6o4(self.pg0, + "1.1.1.2", + self.pg0.local_ip4, + self.pg0.local_ip6, + self.pg0.remote_ip6) + self.pg0.add_stream(tx) + + self.pg_enable_capture(self.pg_interfaces) + self.pg_start() + + rx = self.pg0.get_capture() + self.verify_decapped_6o4(self.pg0, rx, tx) + + # + # test case cleanup + # + route_tun_dst.remove_vpp_config() + route_via_tun.remove_vpp_config() + gre_if.remove_vpp_config() + + self.pg0.unconfig_ip6() + + def test_gre_vrf(self): + """ GRE tunnel VRF Tests """ + + # + # Create an L3 GRE tunnel whose destination is in the non-default + # table. The underlay is thus non-default - the overlay is still + # the default. + # - set it admin up + # - assign an IP Addres + # + gre_if = VppGreInterface(self, self.pg1.local_ip4, + "2.2.2.2", + outer_fib_id=1) + gre_if.add_vpp_config() + gre_if.admin_up() + gre_if.config_ip4() + + # + # Add a route via the tunnel - in the overlay + # + route_via_tun = IpRoute(self, "9.9.9.9", 32, + [IpPath("0.0.0.0", gre_if.sw_if_index)]) + route_via_tun.add_vpp_config() + + # + # Add a route that resolves the tunnel's destination - in the + # underlay table + # + route_tun_dst = IpRoute(self, "2.2.2.2", 32, table_id=1, + paths=[IpPath(self.pg1.remote_ip4, + self.pg1.sw_if_index)]) + route_tun_dst.add_vpp_config() + + # + # Send a packet stream that is routed into the tunnel + # packets are sent in on pg0 which is in the default table + # - packets are GRE encapped + # + self.vapi.cli("clear trace") + tx = self.create_stream_ip4(self.pg0, "5.5.5.5", "9.9.9.9") + self.pg0.add_stream(tx) + + self.pg_enable_capture(self.pg_interfaces) + self.pg_start() + + rx = self.pg1.get_capture() + self.verify_tunneled_4o4(self.pg1, rx, tx, + self.pg1.local_ip4, "2.2.2.2") + + # + # Send tunneled packets that match the created tunnel and + # are decapped and forwarded. This tests the decap lookup + # does not happen in the encap table + # + self.vapi.cli("clear trace") + tx = self.create_tunnel_stream_4o4(self.pg1, + "2.2.2.2", + self.pg1.local_ip4, + self.pg0.local_ip4, + self.pg0.remote_ip4) + self.pg1.add_stream(tx) + + self.pg_enable_capture(self.pg_interfaces) + self.pg_start() + + rx = self.pg0.get_capture() + self.verify_decapped_4o4(self.pg0, rx, tx) + + # + # test case cleanup + # + route_tun_dst.remove_vpp_config() + route_via_tun.remove_vpp_config() + gre_if.remove_vpp_config() + + def test_gre_l2(self): + """ GRE tunnel L2 Tests """ + + # + # Add routes to resolve the tunnel destinations + # + route_tun1_dst = IpRoute(self, "2.2.2.2", 32, + [IpPath(self.pg0.remote_ip4, + self.pg0.sw_if_index)]) + route_tun2_dst = IpRoute(self, "2.2.2.3", 32, + [IpPath(self.pg0.remote_ip4, + self.pg0.sw_if_index)]) + + route_tun1_dst.add_vpp_config() + route_tun2_dst.add_vpp_config() + + # + # Create 2 L2 GRE tunnels and x-connect them + # + gre_if1 = VppGreInterface(self, self.pg0.local_ip4, + "2.2.2.2", + is_teb=1) + gre_if2 = VppGreInterface(self, self.pg0.local_ip4, + "2.2.2.3", + is_teb=1) + gre_if1.add_vpp_config() + gre_if2.add_vpp_config() + + gre_if1.admin_up() + gre_if2.admin_up() + + self.vapi.sw_interface_set_l2_xconnect(gre_if1.sw_if_index, + gre_if2.sw_if_index, + enable=1) + self.vapi.sw_interface_set_l2_xconnect(gre_if2.sw_if_index, + gre_if1.sw_if_index, + enable=1) + + # + # Send in tunnel encapped L2. expect out tunnel encapped L2 + # in both directions + # + self.vapi.cli("clear trace") + tx = self.create_tunnel_stream_l2o4(self.pg0, + "2.2.2.2", + self.pg0.local_ip4) + self.pg0.add_stream(tx) + + self.pg_enable_capture(self.pg_interfaces) + self.pg_start() + + rx = self.pg0.get_capture() + self.verify_tunneled_l2o4(self.pg0, rx, tx, + self.pg0.local_ip4, + "2.2.2.3") + + self.vapi.cli("clear trace") + tx = self.create_tunnel_stream_l2o4(self.pg0, + "2.2.2.3", + self.pg0.local_ip4) + self.pg0.add_stream(tx) + + self.pg_enable_capture(self.pg_interfaces) + self.pg_start() + + rx = self.pg0.get_capture() + self.verify_tunneled_l2o4(self.pg0, rx, tx, + self.pg0.local_ip4, + "2.2.2.2") + + self.vapi.sw_interface_set_l2_xconnect(gre_if1.sw_if_index, + gre_if2.sw_if_index, + enable=0) + self.vapi.sw_interface_set_l2_xconnect(gre_if2.sw_if_index, + gre_if1.sw_if_index, + enable=0) + + # + # Create a VLAN sub-interfaces on the GRE TEB interfaces + # then x-connect them + # + gre_if_11 = VppDot1QSubint(self, gre_if1, 11) + gre_if_12 = VppDot1QSubint(self, gre_if2, 12) + + # gre_if_11.add_vpp_config() + # gre_if_12.add_vpp_config() + + gre_if_11.admin_up() + gre_if_12.admin_up() + + self.vapi.sw_interface_set_l2_xconnect(gre_if_11.sw_if_index, + gre_if_12.sw_if_index, + enable=1) + self.vapi.sw_interface_set_l2_xconnect(gre_if_12.sw_if_index, + gre_if_11.sw_if_index, + enable=1) + + # + # Configure both to pop thier respective VLAN tags, + # so that during the x-coonect they will subsequently push + # + self.vapi.sw_interface_set_l2_tag_rewrite(gre_if_12.sw_if_index, + L2_VTR_OP.L2_POP_1, + 12) + self.vapi.sw_interface_set_l2_tag_rewrite(gre_if_11.sw_if_index, + L2_VTR_OP.L2_POP_1, + 11) + + # + # Send traffic in both directiond - expect the VLAN tags to + # be swapped. + # + self.vapi.cli("clear trace") + tx = self.create_tunnel_stream_vlano4(self.pg0, + "2.2.2.2", + self.pg0.local_ip4, + 11) + self.pg0.add_stream(tx) + + self.pg_enable_capture(self.pg_interfaces) + self.pg_start() + + rx = self.pg0.get_capture() + self.verify_tunneled_vlano4(self.pg0, rx, tx, + self.pg0.local_ip4, + "2.2.2.3", + 12) + + self.vapi.cli("clear trace") + tx = self.create_tunnel_stream_vlano4(self.pg0, + "2.2.2.3", + self.pg0.local_ip4, + 12) + self.pg0.add_stream(tx) + + self.pg_enable_capture(self.pg_interfaces) + self.pg_start() + + rx = self.pg0.get_capture() + self.verify_tunneled_vlano4(self.pg0, rx, tx, + self.pg0.local_ip4, + "2.2.2.2", + 11) + + # + # Cleanup Test resources + # + gre_if_11.remove_vpp_config() + gre_if_12.remove_vpp_config() + gre_if1.remove_vpp_config() + gre_if2.remove_vpp_config() + route_tun1_dst.add_vpp_config() + route_tun2_dst.add_vpp_config() + + +if __name__ == '__main__': + unittest.main(testRunner=VppTestRunner) diff --git a/test/vpp_gre_interface.py b/test/vpp_gre_interface.py new file mode 100644 index 00000000..b6a66332 --- /dev/null +++ b/test/vpp_gre_interface.py @@ -0,0 +1,34 @@ + +from vpp_interface import VppInterface +import socket + + +class VppGreInterface(VppInterface): + """ + VPP GRE interface + """ + + def __init__(self, test, src_ip, dst_ip, outer_fib_id=0, is_teb=0): + """ Create VPP loopback interface """ + self._test = test + self.t_src = src_ip + self.t_dst = dst_ip + self.t_outer_fib = outer_fib_id + self.t_is_teb = is_teb + + def add_vpp_config(self): + s = socket.inet_pton(socket.AF_INET, self.t_src) + d = socket.inet_pton(socket.AF_INET, self.t_dst) + r = self.test.vapi.gre_tunnel_add_del(s, d, + outer_fib_id=self.t_outer_fib, + is_teb=self.t_is_teb) + self._sw_if_index = r.sw_if_index + self.post_init_setup() + + def remove_vpp_config(self): + s = socket.inet_pton(socket.AF_INET, self.t_src) + d = socket.inet_pton(socket.AF_INET, self.t_dst) + self.unconfig() + r = self.test.vapi.gre_tunnel_add_del(s, d, + outer_fib_id=self.t_outer_fib, + is_add=0) diff --git a/test/vpp_interface.py b/test/vpp_interface.py index 30ef8ae7..511cf4bc 100644 --- a/test/vpp_interface.py +++ b/test/vpp_interface.py @@ -176,6 +176,19 @@ class VppInterface(object): addr_len = 24 self.test.vapi.sw_interface_add_del_address( self.sw_if_index, addr, addr_len) + self.has_ip4_config = True + + def unconfig_ip4(self): + """Remove IPv4 address on the VPP interface""" + try: + if (self.has_ip4_config): + self.test.vapi.sw_interface_add_del_address( + self.sw_if_index, + self.local_ip4n, + 24, is_add=0) + except AttributeError: + self.has_ip4_config = False + self.has_ip4_config = False def configure_ipv4_neighbors(self): """For every remote host assign neighbor's MAC to IPv4 addresses.""" @@ -190,6 +203,24 @@ class VppInterface(object): addr_len = 64 self.test.vapi.sw_interface_add_del_address( self.sw_if_index, addr, addr_len, is_ipv6=1) + self.has_ip6_config = True + + def unconfig_ip6(self): + """Remove IPv6 address on the VPP interface""" + try: + if (self.has_ip6_config): + self.test.vapi.sw_interface_add_del_address( + self.sw_if_index, + self.local_ip6n, + 64, is_ipv6=1, is_add=0) + except AttributeError: + self.has_ip6_config = False + self.has_ip6_config = False + + def unconfig(self): + """Unconfigure IPv6 and IPv4 address on the VPP interface""" + self.unconfig_ip4() + self.unconfig_ip6() def set_table_ip4(self, table_id): """Set the interface in a IPv4 Table. diff --git a/test/vpp_ip_route.py b/test/vpp_ip_route.py new file mode 100644 index 00000000..78e6aaa2 --- /dev/null +++ b/test/vpp_ip_route.py @@ -0,0 +1,46 @@ +""" + IP Routes + + object abstractions for representing IP routes in VPP +""" + +import socket + + +class IpPath: + + def __init__(self, nh_addr, nh_sw_if_index, nh_table_id=0): + self.nh_addr = socket.inet_pton(socket.AF_INET, nh_addr) + self.nh_itf = nh_sw_if_index + self.nh_table_id = nh_table_id + + +class IpRoute: + """ + IP Route + """ + + def __init__(self, test, dest_addr, + dest_addr_len, paths, table_id=0): + self._test = test + self.paths = paths + self.dest_addr = socket.inet_pton(socket.AF_INET, dest_addr) + self.dest_addr_len = dest_addr_len + self.table_id = table_id + + def add_vpp_config(self): + for path in self.paths: + self._test.vapi.ip_add_del_route(self.dest_addr, + self.dest_addr_len, + path.nh_addr, + path.nh_itf, + table_id=self.table_id) + + def remove_vpp_config(self): + for path in self.paths: + self._test.vapi.ip_add_del_route(self.dest_addr, + self.dest_addr_len, + path.nh_addr, + path.nh_itf, + table_id=self.table_id, + is_add=0) diff --git a/test/vpp_papi_provider.py b/test/vpp_papi_provider.py index 2148e94b..23a108d9 100644 --- a/test/vpp_papi_provider.py +++ b/test/vpp_papi_provider.py @@ -18,6 +18,9 @@ if do_import: MPLS_IETF_MAX_LABEL = 0xfffff MPLS_LABEL_INVALID = MPLS_IETF_MAX_LABEL + 1 +class L2_VTR_OP: + L2_POP_1 = 3 + class VppPapiProvider(object): """VPP-api provider using vpp-papi @@ -230,6 +233,20 @@ class VppPapiProvider(object): return self.api(vpp_papi.sw_interface_set_l2_xconnect, (rx_sw_if_index, tx_sw_if_index, enable)) + def sw_interface_set_l2_tag_rewrite(self, sw_if_index, vtr_oper, push=0, tag1=0, tag2=0): + """L2 interface vlan tag rewrite configure request + :param client_index - opaque cookie to identify the sender + :param context - sender context, to match reply w/ request + :param sw_if_index - interface the operation is applied to + :param vtr_op - Choose from l2_vtr_op_t enum values + :param push_dot1q - first pushed flag dot1q id set, else dot1ad + :param tag1 - Needed for any push or translate vtr op + :param tag2 - Needed for any push 2 or translate x-2 vtr ops + + """ + return self.api(vpp_papi.l2_interface_vlan_tag_rewrite, + (sw_if_index, vtr_oper, push, tag1, tag2)) + def sw_interface_set_flags(self, sw_if_index, admin_up_down, link_up_down=0, deleted=0): """ @@ -278,6 +295,13 @@ class VppPapiProvider(object): outer_vlan, inner_vlan)) + def delete_subif(self, sw_if_index): + """Delete subinterface + + :param sw_if_index: + """ + return self.api(vpp_papi.delete_subif, ([sw_if_index])) + def create_vlan_subif(self, sw_if_index, vlan): """ @@ -411,3 +435,30 @@ class VppPapiProvider(object): """ return self.api(vpp_papi.sw_interface_span_enable_disable, (sw_if_index_from, sw_if_index_to, enable )) + + def gre_tunnel_add_del(self, + src_address, + dst_address, + outer_fib_id=0, + is_teb=0, + is_add=1, + is_ip6=0): + """ Add a GRE tunnel + + :param src_address: + :param dst_address: + :param outer_fib_id: (Default value = 0) + :param is_add: (Default value = 1) + :param is_ipv6: (Default value = 0) + :param is_teb: (Default value = 0) + """ + + return self.api( + vpp_papi.gre_add_del_tunnel, + (is_add, + is_ip6, + is_teb, + src_address, + dst_address, + outer_fib_id) + ) diff --git a/test/vpp_sub_interface.py b/test/vpp_sub_interface.py index b387d27b..027a24b2 100644 --- a/test/vpp_sub_interface.py +++ b/test/vpp_sub_interface.py @@ -41,6 +41,9 @@ class VppSubInterface(VppPGInterface): def add_dot1_layer(self, pkt): pass + def remove_vpp_config(self): + self.test.vapi.delete_subif(self._sw_if_index) + class VppDot1QSubint(VppSubInterface): diff --git a/vnet/vnet/adj/adj.c b/vnet/vnet/adj/adj.c index 24f7662d..2741c885 100644 --- a/vnet/vnet/adj/adj.c +++ b/vnet/vnet/adj/adj.c @@ -61,6 +61,7 @@ adj_alloc (fib_protocol_t proto) adj->mcast_group_index = ~0; adj->saved_lookup_next_index = 0; adj->n_adj = 1; + adj->lookup_next_index = 0; fib_node_init(&adj->ia_node, FIB_NODE_TYPE_ADJ); @@ -163,7 +164,8 @@ adj_last_lock_gone (ip_adjacency_t *adj) /* * complete and incomplete nbr adjs */ - adj_nbr_remove(adj->ia_nh_proto, + adj_nbr_remove(adj_get_index(adj), + adj->ia_nh_proto, adj->ia_link, &adj->sub_type.nbr.next_hop, adj->rewrite_header.sw_if_index); @@ -376,6 +378,12 @@ adj_show (vlib_main_t * vm, if (ADJ_INDEX_INVALID != ai) { + if (pool_is_free_index(adj_pool, ai)) + { + vlib_cli_output (vm, "adjacency %d invalid", ai); + return 0; + } + vlib_cli_output (vm, "[@%d] %U", ai, format_ip_adjacency, ai, diff --git a/vnet/vnet/adj/adj_internal.h b/vnet/vnet/adj/adj_internal.h index e3e0e04c..833bc7c9 100644 --- a/vnet/vnet/adj/adj_internal.h +++ b/vnet/vnet/adj/adj_internal.h @@ -93,7 +93,8 @@ extern void adj_nbr_update_rewrite_internal (ip_adjacency_t *adj, extern ip_adjacency_t * adj_alloc(fib_protocol_t proto); -extern void adj_nbr_remove(fib_protocol_t nh_proto, +extern void adj_nbr_remove(adj_index_t ai, + fib_protocol_t nh_proto, vnet_link_t link_type, const ip46_address_t *nh_addr, u32 sw_if_index); diff --git a/vnet/vnet/adj/adj_nbr.c b/vnet/vnet/adj/adj_nbr.c index 1a78ecbc..003e18e8 100644 --- a/vnet/vnet/adj/adj_nbr.c +++ b/vnet/vnet/adj/adj_nbr.c @@ -76,7 +76,8 @@ adj_nbr_insert (fib_protocol_t nh_proto, } void -adj_nbr_remove (fib_protocol_t nh_proto, +adj_nbr_remove (adj_index_t ai, + fib_protocol_t nh_proto, vnet_link_t link_type, const ip46_address_t *nh_addr, u32 sw_if_index) @@ -87,6 +88,7 @@ adj_nbr_remove (fib_protocol_t nh_proto, return; ADJ_NBR_SET_KEY(kv, link_type, nh_addr); + kv.value = ai; BV(clib_bihash_add_del) (adj_nbr_tables[nh_proto][sw_if_index], &kv, 0); } diff --git a/vnet/vnet/ethernet/arp.c b/vnet/vnet/ethernet/arp.c index eeaac4d3..c6dbbc68 100644 --- a/vnet/vnet/ethernet/arp.c +++ b/vnet/vnet/ethernet/arp.c @@ -1509,7 +1509,7 @@ arp_add_del_interface_address (ip4_main_t * im, ethernet_arp_main_t *am = ðernet_arp_main; ethernet_arp_ip4_entry_t *e; - if (vec_len (am->ethernet_arp_by_sw_if_index) < sw_if_index) + if (vec_len (am->ethernet_arp_by_sw_if_index) <= sw_if_index) return; if (is_del) diff --git a/vnet/vnet/gre/gre.c b/vnet/vnet/gre/gre.c index a4b3f9fc..0faed13e 100644 --- a/vnet/vnet/gre/gre.c +++ b/vnet/vnet/gre/gre.c @@ -250,9 +250,9 @@ gre_update_adj (vnet_main_t * vnm, * @brief TX function. Only called L2. L3 traffic uses the adj-midchains */ static uword -gre_interface_tx (vlib_main_t * vm, - vlib_node_runtime_t * node, - vlib_frame_t * frame) +gre_interface_tx_inline (vlib_main_t * vm, + vlib_node_runtime_t * node, + vlib_frame_t * frame) { gre_main_t * gm = &gre_main; u32 next_index; @@ -318,12 +318,34 @@ gre_interface_tx (vlib_main_t * vm, return frame->n_vectors; } +static uword +gre_interface_tx (vlib_main_t * vm, + vlib_node_runtime_t * node, + vlib_frame_t * frame) +{ + return (gre_interface_tx_inline (vm, node, frame)); +} + +static uword +gre_teb_interface_tx (vlib_main_t * vm, + vlib_node_runtime_t * node, + vlib_frame_t * frame) +{ + return (gre_interface_tx_inline (vm, node, frame)); +} + static u8 * format_gre_tunnel_name (u8 * s, va_list * args) { u32 dev_instance = va_arg (*args, u32); return format (s, "gre%d", dev_instance); } +static u8 * format_gre_tunnel_teb_name (u8 * s, va_list * args) +{ + u32 dev_instance = va_arg (*args, u32); + return format (s, "teb-gre%d", dev_instance); +} + static u8 * format_gre_device (u8 * s, va_list * args) { u32 dev_instance = va_arg (*args, u32); @@ -348,6 +370,21 @@ VNET_DEVICE_CLASS (gre_device_class) = { VLIB_DEVICE_TX_FUNCTION_MULTIARCH (gre_device_class, gre_interface_tx) +VNET_DEVICE_CLASS (gre_device_teb_class) = { + .name = "GRE TEB tunnel device", + .format_device_name = format_gre_tunnel_teb_name, + .format_device = format_gre_device, + .format_tx_trace = format_gre_tx_trace, + .tx_function = gre_teb_interface_tx, + .admin_up_down_function = gre_interface_admin_up_down, +#ifdef SOON + .clear counter = 0; +#endif +}; + +VLIB_DEVICE_TX_FUNCTION_MULTIARCH (gre_device_teb_class, + gre_teb_interface_tx) + VNET_HW_INTERFACE_CLASS (gre_hw_interface_class) = { .name = "GRE", .format_header = format_gre_header_with_length, diff --git a/vnet/vnet/gre/gre.h b/vnet/vnet/gre/gre.h index a0ee9ad2..b6544b9b 100644 --- a/vnet/vnet/gre/gre.h +++ b/vnet/vnet/gre/gre.h @@ -36,20 +36,48 @@ typedef enum { GRE_N_ERROR, } gre_error_t; +/** + * A GRE payload protocol registration + */ typedef struct { - /* Name (a c string). */ + /** Name (a c string). */ char * name; - /* GRE protocol type in host byte order. */ + /** GRE protocol type in host byte order. */ gre_protocol_t protocol; - /* Node which handles this type. */ + /** Node which handles this type. */ u32 node_index; - /* Next index for this type. */ + /** Next index for this type. */ u32 next_index; } gre_protocol_info_t; +/** + * @brief The GRE tunnel type + */ +typedef enum gre_tunnel_tyoe_t_ +{ + /** + * L3 GRE (i.e. this tunnel is in L3 mode) + */ + GRE_TUNNEL_TYPE_L3, + /** + * Transparent Ethernet Bridging - the tunnel is in L2 mode + */ + GRE_TUNNEL_TYPE_TEB, +} gre_tunnel_type_t; + +#define GRE_TUNNEL_TYPE_NAMES { \ + [GRE_TUNNEL_TYPE_L3] = "L3", \ + [GRE_TUNNEL_TYPE_TEB] = "TEB", \ +} + +#define GRE_TUNNEL_N_TYPES ((gre_tunnel_type_t)GRE_TUNNEL_TYPE_TEB+1) + +/** + * @brief A representation of a GRE tunnel + */ typedef struct { /** * Linkage into the FIB object graph @@ -70,7 +98,7 @@ typedef struct { u32 outer_fib_index; u32 hw_if_index; u32 sw_if_index; - u8 teb; + gre_tunnel_type_t type; /** * The FIB entry sourced by the tunnel for its destination prefix @@ -96,21 +124,39 @@ typedef struct { adj_index_t l2_adj_index; } gre_tunnel_t; +/** + * @brief GRE related global data + */ typedef struct { - /* pool of tunnel instances */ + /** + * pool of tunnel instances + */ gre_tunnel_t *tunnels; + /** + * GRE payload protocol registrations + */ gre_protocol_info_t * protocol_infos; - /* Hash tables mapping name/protocol to protocol info index. */ + /** + * Hash tables mapping name/protocol to protocol info index. + */ uword * protocol_info_by_name, * protocol_info_by_protocol; - /* Hash mapping src/dst addr pair to tunnel */ + /** + * Hash mapping src/dst addr pair to tunnel + */ uword * tunnel_by_key; - /* Free vlib hw_if_indices */ - u32 * free_gre_tunnel_hw_if_indices; + /** + * Free vlib hw_if_indices. + * A free list per-tunnel type since the interfaces ctreated are fo different + * types and we cannot change the type. + */ + u32 * free_gre_tunnel_hw_if_indices[GRE_TUNNEL_N_TYPES]; - /* Mapping from sw_if_index to tunnel index */ + /** + * Mapping from sw_if_index to tunnel index + */ u32 * tunnel_index_by_sw_if_index; /* convenience */ @@ -120,8 +166,7 @@ typedef struct { /** * @brief IPv4 and GRE header. - * -*/ + */ typedef CLIB_PACKED (struct { ip4_header_t ip4; gre_header_t gre; @@ -148,8 +193,8 @@ extern clib_error_t * gre_interface_admin_up_down (vnet_main_t * vnm, extern void gre_tunnel_stack (adj_index_t ai); extern void gre_update_adj (vnet_main_t * vnm, - u32 sw_if_index, - adj_index_t ai); + u32 sw_if_index, + adj_index_t ai); format_function_t format_gre_protocol; format_function_t format_gre_header; @@ -157,7 +202,7 @@ format_function_t format_gre_header_with_length; extern vlib_node_registration_t gre_input_node; extern vnet_device_class_t gre_device_class; -extern vnet_device_class_t gre_l2_device_class; +extern vnet_device_class_t gre_device_teb_class; /* Parse gre protocol as 0xXXXX or protocol name. In either host or network byte order. */ diff --git a/vnet/vnet/gre/interface.c b/vnet/vnet/gre/interface.c index 3234de09..7adc5268 100644 --- a/vnet/vnet/gre/interface.c +++ b/vnet/vnet/gre/interface.c @@ -24,6 +24,8 @@ #include #include +static const char *gre_tunnel_type_names[] = GRE_TUNNEL_TYPE_NAMES; + static inline u64 gre_mk_key (const ip4_address_t *src, const ip4_address_t *dst, @@ -33,6 +35,14 @@ gre_mk_key (const ip4_address_t *src, return ((u64)src->as_u32 << 32 | (u64)dst->as_u32); } +static u8 * +format_gre_tunnel_type (u8 * s, va_list * args) +{ + gre_tunnel_type_t type = va_arg (*args, gre_tunnel_type_t); + + return (format(s, "%s", gre_tunnel_type_names[type])); +} + static u8 * format_gre_tunnel (u8 * s, va_list * args) { @@ -40,11 +50,11 @@ format_gre_tunnel (u8 * s, va_list * args) gre_main_t * gm = &gre_main; s = format (s, - "[%d] %U (src) %U (dst) payload %s outer_fib_index %d", + "[%d] %U (src) %U (dst) payload %U outer_fib_index %d", t - gm->tunnels, format_ip4_address, &t->tunnel_src, format_ip4_address, &t->tunnel_dst, - (t->teb ? "teb" : "ip"), + format_gre_tunnel_type, t->type, t->outer_fib_index); return s; @@ -248,12 +258,17 @@ vnet_gre_tunnel_add (vnet_gre_add_del_tunnel_args_t *a, memset (t, 0, sizeof (*t)); fib_node_init(&t->node, FIB_NODE_TYPE_GRE_TUNNEL); - if (vec_len (gm->free_gre_tunnel_hw_if_indices) > 0) { + if (a->teb) + t->type = GRE_TUNNEL_TYPE_TEB; + else + t->type = GRE_TUNNEL_TYPE_L3; + + if (vec_len (gm->free_gre_tunnel_hw_if_indices[t->type]) > 0) { vnet_interface_main_t * im = &vnm->interface_main; - hw_if_index = gm->free_gre_tunnel_hw_if_indices - [vec_len (gm->free_gre_tunnel_hw_if_indices)-1]; - _vec_len (gm->free_gre_tunnel_hw_if_indices) -= 1; + hw_if_index = gm->free_gre_tunnel_hw_if_indices[t->type] + [vec_len (gm->free_gre_tunnel_hw_if_indices[t->type])-1]; + _vec_len (gm->free_gre_tunnel_hw_if_indices[t->type]) -= 1; hi = vnet_get_hw_interface (vnm, hw_if_index); hi->dev_instance = t - gm->tunnels; @@ -269,14 +284,14 @@ vnet_gre_tunnel_add (vnet_gre_add_del_tunnel_args_t *a, vlib_zero_simple_counter (&im->sw_if_counters[VNET_INTERFACE_COUNTER_DROP], sw_if_index); vnet_interface_counter_unlock(im); - if (a->teb) + if (GRE_TUNNEL_TYPE_TEB == t->type) { - t->l2_tx_arc = vlib_node_add_named_next(vlib_get_main(), - hi->tx_node_index, - "adj-l2-midchain"); + t->l2_tx_arc = vlib_node_add_named_next(vlib_get_main(), + hi->tx_node_index, + "adj-l2-midchain"); } } else { - if (a->teb) + if (GRE_TUNNEL_TYPE_TEB == t->type) { /* Default MAC address (d00b:eed0:0000 + sw_if_index) */ memset (address, 0, sizeof (address)); @@ -287,7 +302,7 @@ vnet_gre_tunnel_add (vnet_gre_add_del_tunnel_args_t *a, address[4] = t - gm->tunnels; error = ethernet_register_interface(vnm, - gre_device_class.index, + gre_device_teb_class.index, t - gm->tunnels, address, &hw_if_index, 0); @@ -316,14 +331,11 @@ vnet_gre_tunnel_add (vnet_gre_add_del_tunnel_args_t *a, t->hw_if_index = hw_if_index; t->outer_fib_index = outer_fib_index; t->sw_if_index = sw_if_index; - t->teb = a->teb; vec_validate_init_empty (gm->tunnel_index_by_sw_if_index, sw_if_index, ~0); gm->tunnel_index_by_sw_if_index[sw_if_index] = t - gm->tunnels; vec_validate (im->fib_index_by_sw_if_index, sw_if_index); - im->fib_index_by_sw_if_index[sw_if_index] = t->outer_fib_index; - ip4_sw_interface_enable_disable(sw_if_index, 1); hi->min_packet_bytes = 64 + sizeof (gre_header_t) + sizeof (ip4_header_t); hi->per_packet_overhead_bytes = @@ -365,13 +377,12 @@ vnet_gre_tunnel_add (vnet_gre_add_del_tunnel_args_t *a, clib_memcpy (&t->tunnel_src, &a->src, sizeof (t->tunnel_src)); clib_memcpy (&t->tunnel_dst, &a->dst, sizeof (t->tunnel_dst)); - if (t->teb) + if (GRE_TUNNEL_TYPE_TEB == t->type) { t->l2_adj_index = adj_nbr_add_or_lock(FIB_PROTOCOL_IP4, VNET_LINK_ETHERNET, &zero_addr, sw_if_index); - gre_update_adj(vnm, t->sw_if_index, t->l2_adj_index); } @@ -399,9 +410,11 @@ vnet_gre_tunnel_delete (vnet_gre_add_del_tunnel_args_t *a, vnet_sw_interface_set_flags (vnm, sw_if_index, 0 /* down */); /* make sure tunnel is removed from l2 bd or xconnect */ set_int_l2_mode(gm->vlib_main, vnm, MODE_L3, sw_if_index, 0, 0, 0, 0); - vec_add1 (gm->free_gre_tunnel_hw_if_indices, t->hw_if_index); + vec_add1 (gm->free_gre_tunnel_hw_if_indices[t->type], t->hw_if_index); gm->tunnel_index_by_sw_if_index[sw_if_index] = ~0; - ip4_sw_interface_enable_disable(sw_if_index, 0); + + if (GRE_TUNNEL_TYPE_TEB == t->type) + adj_unlock(t->l2_adj_index); fib_entry_child_remove(t->fib_entry_index, t->sibling_index); diff --git a/vnet/vnet/gre/node.c b/vnet/vnet/gre/node.c index 556f1a81..86f7a6ee 100644 --- a/vnet/vnet/gre/node.c +++ b/vnet/vnet/gre/node.c @@ -68,12 +68,10 @@ gre_input (vlib_main_t * vm, vlib_frame_t * from_frame) { gre_main_t * gm = &gre_main; - ip4_main_t * ip4m = &ip4_main; gre_input_runtime_t * rt = (void *) node->runtime_data; __attribute__((unused)) u32 n_left_from, next_index, * from, * to_next; u64 cached_tunnel_key = (u64) ~0; u32 cached_tunnel_sw_if_index = 0, tunnel_sw_if_index = 0; - u32 cached_tunnel_fib_index = 0, tunnel_fib_index; u32 cpu_index = os_get_cpu_number(); u32 len; @@ -193,16 +191,12 @@ gre_input (vlib_main_t * vm, hi = vnet_get_hw_interface (gm->vnet_main, t->hw_if_index); tunnel_sw_if_index = hi->sw_if_index; - tunnel_fib_index = vec_elt (ip4m->fib_index_by_sw_if_index, - tunnel_sw_if_index); cached_tunnel_sw_if_index = tunnel_sw_if_index; - cached_tunnel_fib_index = tunnel_fib_index; } else { tunnel_sw_if_index = cached_tunnel_sw_if_index; - tunnel_fib_index = cached_tunnel_fib_index; } } else @@ -218,7 +212,6 @@ gre_input (vlib_main_t * vm, 1 /* packets */, len /* bytes */); - vnet_buffer(b0)->sw_if_index[VLIB_TX] = tunnel_fib_index; vnet_buffer(b0)->sw_if_index[VLIB_RX] = tunnel_sw_if_index; drop0: @@ -247,16 +240,12 @@ drop0: hi = vnet_get_hw_interface (gm->vnet_main, t->hw_if_index); tunnel_sw_if_index = hi->sw_if_index; - tunnel_fib_index = vec_elt (ip4m->fib_index_by_sw_if_index, - tunnel_sw_if_index); cached_tunnel_sw_if_index = tunnel_sw_if_index; - cached_tunnel_fib_index = tunnel_fib_index; } else { tunnel_sw_if_index = cached_tunnel_sw_if_index; - tunnel_fib_index = cached_tunnel_fib_index; } } else @@ -272,7 +261,6 @@ drop0: 1 /* packets */, len /* bytes */); - vnet_buffer(b1)->sw_if_index[VLIB_TX] = tunnel_fib_index; vnet_buffer(b1)->sw_if_index[VLIB_RX] = tunnel_sw_if_index; drop1: @@ -280,7 +268,7 @@ drop1: { gre_rx_trace_t *tr = vlib_add_trace (vm, node, b0, sizeof (*tr)); - tr->tunnel_id = ~0; + tr->tunnel_id = tunnel_sw_if_index; tr->length = ip0->length; tr->src.as_u32 = ip0->src_address.as_u32; tr->dst.as_u32 = ip0->dst_address.as_u32; @@ -290,7 +278,7 @@ drop1: { gre_rx_trace_t *tr = vlib_add_trace (vm, node, b1, sizeof (*tr)); - tr->tunnel_id = ~0; + tr->tunnel_id = tunnel_sw_if_index; tr->length = ip1->length; tr->src.as_u32 = ip1->src_address.as_u32; tr->dst.as_u32 = ip1->dst_address.as_u32; @@ -374,16 +362,12 @@ drop1: hi = vnet_get_hw_interface (gm->vnet_main, t->hw_if_index); tunnel_sw_if_index = hi->sw_if_index; - tunnel_fib_index = vec_elt (ip4m->fib_index_by_sw_if_index, - tunnel_sw_if_index); cached_tunnel_sw_if_index = tunnel_sw_if_index; - cached_tunnel_fib_index = tunnel_fib_index; } else { tunnel_sw_if_index = cached_tunnel_sw_if_index; - tunnel_fib_index = cached_tunnel_fib_index; } } else @@ -399,7 +383,6 @@ drop1: 1 /* packets */, len /* bytes */); - vnet_buffer(b0)->sw_if_index[VLIB_TX] = tunnel_fib_index; vnet_buffer(b0)->sw_if_index[VLIB_RX] = tunnel_sw_if_index; drop: diff --git a/vnet/vnet/interface_output.c b/vnet/vnet/interface_output.c index 1d1546f6..46e8a98c 100644 --- a/vnet/vnet/interface_output.c +++ b/vnet/vnet/interface_output.c @@ -58,15 +58,30 @@ format_vnet_interface_output_trace (u8 * s, va_list * va) if (t->sw_if_index != (u32) ~ 0) { - si = vnet_get_sw_interface (vnm, t->sw_if_index); indent = format_get_indent (s); - s = format (s, "%U\n%U%U", - format_vnet_sw_interface_name, vnm, si, - format_white_space, indent, - node->format_buffer ? node-> - format_buffer : format_hex_bytes, t->data, - sizeof (t->data)); + if (pool_is_free_index + (vnm->interface_main.sw_interfaces, t->sw_if_index)) + { + /* the interface may have been deleted by the time the trace is printed */ + s = format (s, "sw_if_index: %d\n%U%U", + t->sw_if_index, + format_white_space, indent, + node->format_buffer ? node-> + format_buffer : format_hex_bytes, t->data, + sizeof (t->data)); + } + else + { + si = vnet_get_sw_interface (vnm, t->sw_if_index); + + s = format (s, "%U\n%U%U", + format_vnet_sw_interface_name, vnm, si, + format_white_space, indent, + node->format_buffer ? node-> + format_buffer : format_hex_bytes, t->data, + sizeof (t->data)); + } } return s; } diff --git a/vnet/vnet/ip/ip6_forward.c b/vnet/vnet/ip/ip6_forward.c index bc346786..14dd9dfb 100644 --- a/vnet/vnet/ip/ip6_forward.c +++ b/vnet/vnet/ip/ip6_forward.c @@ -498,6 +498,8 @@ ip6_add_del_interface_address (vlib_main_t * vm, goto done; } + ip6_sw_interface_enable_disable(sw_if_index, !is_del); + if (is_del) ip6_del_interface_routes (im, ip6_af.fib_index, address, address_length); diff --git a/vnet/vnet/ip/ip6_neighbor.c b/vnet/vnet/ip/ip6_neighbor.c index cebe09a9..af852a2b 100644 --- a/vnet/vnet/ip/ip6_neighbor.c +++ b/vnet/vnet/ip/ip6_neighbor.c @@ -1849,7 +1849,6 @@ ip6_neighbor_sw_interface_add_del (vnet_main_t * vnm, pool_put (nm->if_radv_pool, a); nm->if_radv_pool_index_by_sw_if_index[sw_if_index] = ~0; ri = ~0; - ip6_sw_interface_enable_disable(sw_if_index, 0); } } else @@ -1858,7 +1857,6 @@ ip6_neighbor_sw_interface_add_del (vnet_main_t * vnm, { vnet_hw_interface_t * hw_if0; - ip6_sw_interface_enable_disable(sw_if_index, 1); hw_if0 = vnet_get_sup_hw_interface (vnm, sw_if_index); pool_get (nm->if_radv_pool, a); diff --git a/vpp/vpp-api/api.c b/vpp/vpp-api/api.c index 91726e16..676b0c45 100644 --- a/vpp/vpp-api/api.c +++ b/vpp/vpp-api/api.c @@ -5008,7 +5008,7 @@ static void send_gre_tunnel_details clib_memcpy (rmp->src_address, &(t->tunnel_src), 4); clib_memcpy (rmp->dst_address, &(t->tunnel_dst), 4); rmp->outer_fib_id = htonl (im->fibs[t->outer_fib_index].ft_table_id); - rmp->teb = t->teb; + rmp->teb = (GRE_TUNNEL_TYPE_TEB == t->type); rmp->sw_if_index = htonl (t->sw_if_index); rmp->context = context; -- cgit 1.2.3-korg From c5bf07fabe46c175890bb5661a85ed076fbf7f2d Mon Sep 17 00:00:00 2001 From: Matej Klotton Date: Wed, 23 Nov 2016 15:27:17 +0100 Subject: Remove postinit from make-test interfaces Change-Id: I1eb0f735c5d025e6096d5760eb01419a1c58530a Signed-off-by: Matej Klotton --- test/vpp_interface.py | 26 +++++++++++++------------- test/vpp_lo_interface.py | 7 +++---- test/vpp_pg_interface.py | 23 ++++++++++------------- test/vpp_sub_interface.py | 21 +++++++-------------- 4 files changed, 33 insertions(+), 44 deletions(-) (limited to 'test/vpp_interface.py') diff --git a/test/vpp_interface.py b/test/vpp_interface.py index 511cf4bc..a450560e 100644 --- a/test/vpp_interface.py +++ b/test/vpp_interface.py @@ -1,6 +1,5 @@ from abc import abstractmethod, ABCMeta import socket -from logging import info from util import Host @@ -100,7 +99,7 @@ class VppInterface(object): def host_by_mac(self, mac): """ - :param ip: MAC address to find host by. + :param mac: MAC address to find host by. :return: Host object assigned to interface. """ return self._hosts_by_mac[mac] @@ -138,8 +137,14 @@ class VppInterface(object): self._hosts_by_ip4[ip4] = host self._hosts_by_ip6[ip6] = host - def post_init_setup(self): - """Additional setup run after creating an interface object.""" + @abstractmethod + def __init__(self, test): + self._test = test + + self._remote_hosts = [] + self._hosts_by_mac = {} + self._hosts_by_ip4 = {} + self._hosts_by_ip6 = {} self.generate_remote_hosts() @@ -153,8 +158,10 @@ class VppInterface(object): for intf in r: if intf.sw_if_index == self.sw_if_index: self._name = intf.interface_name.split(b'\0', 1)[0] - self._local_mac = ':'.join(intf.l2_address.encode('hex')[i:i + 2] - for i in range(0, 12, 2)) + self._local_mac = ':'.join( + intf.l2_address.encode('hex')[i:i + 2] + for i in range(0, 12, 2) + ) self._dump = intf break else: @@ -163,13 +170,6 @@ class VppInterface(object): "in interface dump %s" % (self.sw_if_index, repr(r))) - @abstractmethod - def __init__(self, test, index): - self._test = test - self.post_init_setup() - info("New %s, MAC=%s, remote_ip4=%s, local_ip4=%s" % - (self.__name__, self.remote_mac, self.remote_ip4, self.local_ip4)) - def config_ip4(self): """Configure IPv4 address on the VPP interface.""" addr = self.local_ip4n diff --git a/test/vpp_lo_interface.py b/test/vpp_lo_interface.py index ef815251..19ee1523 100644 --- a/test/vpp_lo_interface.py +++ b/test/vpp_lo_interface.py @@ -7,8 +7,7 @@ class VppLoInterface(VppInterface): def __init__(self, test, lo_index): """ Create VPP loopback interface """ - self._lo_index = lo_index - self._test = test - r = self.test.vapi.create_loopback() + r = test.vapi.create_loopback() self._sw_if_index = r.sw_if_index - self.post_init_setup() + super(VppLoInterface, self).__init__(test) + self._lo_index = lo_index diff --git a/test/vpp_pg_interface.py b/test/vpp_pg_interface.py index 381dc1da..81a0fba9 100644 --- a/test/vpp_pg_interface.py +++ b/test/vpp_pg_interface.py @@ -58,9 +58,16 @@ class VppPGInterface(VppInterface): self._out_history_counter += 1 return v - def post_init_setup(self): - """ Perform post-init setup for super class and add our own setup """ - super(VppPGInterface, self).post_init_setup() + def __init__(self, test, pg_index): + """ Create VPP packet-generator interface """ + r = test.vapi.pg_create_interface(pg_index) + self._sw_if_index = r.sw_if_index + + super(VppPGInterface, self).__init__(test) + + self._in_history_counter = 0 + self._out_history_counter = 0 + self._pg_index = pg_index self._out_file = "pg%u_out.pcap" % self.pg_index self._out_path = self.test.tempdir + "/" + self._out_file self._in_file = "pg%u_in.pcap" % self.pg_index @@ -71,16 +78,6 @@ class VppPGInterface(VppInterface): self._input_cli = "packet-generator new pcap %s source pg%u name %s" % ( self.in_path, self.pg_index, self.cap_name) - def __init__(self, test, pg_index): - """ Create VPP packet-generator interface """ - self._in_history_counter = 0 - self._out_history_counter = 0 - self._pg_index = pg_index - self._test = test - r = self.test.vapi.pg_create_interface(self.pg_index) - self._sw_if_index = r.sw_if_index - self.post_init_setup() - def enable_capture(self): """ Enable capture on this packet-generator interface""" try: diff --git a/test/vpp_sub_interface.py b/test/vpp_sub_interface.py index 027a24b2..b4c415ca 100644 --- a/test/vpp_sub_interface.py +++ b/test/vpp_sub_interface.py @@ -18,7 +18,7 @@ class VppSubInterface(VppPGInterface): return self._sub_id def __init__(self, test, parent, sub_id): - self._test = test + VppInterface.__init__(self, test) self._parent = parent self._parent.add_sub_if(self) self._sub_id = sub_id @@ -55,11 +55,10 @@ class VppDot1QSubint(VppSubInterface): def __init__(self, test, parent, sub_id, vlan=None): if vlan is None: vlan = sub_id - super(VppDot1QSubint, self).__init__(test, parent, sub_id) self._vlan = vlan - r = self.test.vapi.create_vlan_subif(parent.sw_if_index, self.vlan) + r = test.vapi.create_vlan_subif(parent.sw_if_index, vlan) self._sw_if_index = r.sw_if_index - VppInterface.post_init_setup(self) + super(VppDot1QSubint, self).__init__(test, parent, sub_id) def create_arp_req(self): packet = VppPGInterface.create_arp_req(self) @@ -98,21 +97,15 @@ class VppDot1ADSubint(VppSubInterface): return self._inner_vlan def __init__(self, test, parent, sub_id, outer_vlan, inner_vlan): + r = test.vapi.create_subif(parent.sw_if_index, sub_id, outer_vlan, + inner_vlan, dot1ad=1, two_tags=1, + exact_match=1) + self._sw_if_index = r.sw_if_index super(VppDot1ADSubint, self).__init__(test, parent, sub_id) self.DOT1AD_TYPE = 0x88A8 self.DOT1Q_TYPE = 0x8100 self._outer_vlan = outer_vlan self._inner_vlan = inner_vlan - r = self.test.vapi.create_subif( - parent.sw_if_index, - self.sub_id, - self.outer_vlan, - self.inner_vlan, - dot1ad=1, - two_tags=1, - exact_match=1) - self._sw_if_index = r.sw_if_index - VppInterface.post_init_setup(self) def create_arp_req(self): packet = VppPGInterface.create_arp_req(self) -- cgit 1.2.3-korg From 7bb873a4cc068a6cc3c9d0e1d32987c5f8003904 Mon Sep 17 00:00:00 2001 From: Klement Sekera Date: Fri, 18 Nov 2016 07:38:42 +0100 Subject: make test: fix missing log/packet messages Change-Id: Idb3119792943664748c4abc3829ad723f4156dfe Signed-off-by: Klement Sekera --- test/framework.py | 2 +- test/test_ip4.py | 12 +++++------- test/test_ip6.py | 13 ++++++------- test/test_l2bd.py | 7 +++---- test/test_l2xc.py | 5 ++--- test/test_lb.py | 7 +++---- test/test_mpls.py | 12 ++++++------ test/test_span.py | 49 +++++++++++++++++++++++++++-------------------- test/test_vxlan.py | 3 +-- test/util.py | 14 ++++++++++++++ test/vpp_interface.py | 12 +++++------- test/vpp_papi_provider.py | 11 +++++++---- test/vpp_pg_interface.py | 46 +++++++++++++++++++++++++------------------- 13 files changed, 107 insertions(+), 86 deletions(-) (limited to 'test/vpp_interface.py') diff --git a/test/framework.py b/test/framework.py index 5a9aba2c..b3cbb08a 100644 --- a/test/framework.py +++ b/test/framework.py @@ -193,7 +193,7 @@ class VppTestCase(unittest.TestCase): cls.vpp_stderr_reader_thread = Thread(target=pump_output, args=( cls.vpp.stderr, cls.vpp_stderr_queue)) cls.vpp_stderr_reader_thread.start() - cls.vapi = VppPapiProvider(cls.shm_prefix, cls.shm_prefix) + cls.vapi = VppPapiProvider(cls.shm_prefix, cls.shm_prefix, cls) if cls.step: hook = StepHook(cls) else: diff --git a/test/test_ip4.py b/test/test_ip4.py index 36a907a6..d219ec9f 100644 --- a/test/test_ip4.py +++ b/test/test_ip4.py @@ -4,12 +4,12 @@ import unittest import socket from framework import VppTestCase, VppTestRunner -from vpp_interface import VppInterface from vpp_sub_interface import VppSubInterface, VppDot1QSubint, VppDot1ADSubint from scapy.packet import Raw from scapy.layers.l2 import Ether, Dot1Q from scapy.layers.inet import IP, UDP +from util import ppp class TestIPv4(VppTestCase): @@ -164,16 +164,14 @@ class TestIPv4(VppTestCase): self.assertEqual(udp.sport, saved_packet[UDP].sport) self.assertEqual(udp.dport, saved_packet[UDP].dport) except: - self.logger.error("Unexpected or invalid packet:") - self.logger.error(packet.show()) + self.logger.error(ppp("Unexpected or invalid packet:", packet)) raise for i in self.interfaces: remaining_packet = self.get_next_packet_info_for_interface2( i.sw_if_index, dst_sw_if_index, last_info[i.sw_if_index]) - self.assertTrue( - remaining_packet is None, - "Interface %s: Packet expected from interface %s didn't arrive" % - (dst_if.name, i.name)) + self.assertTrue(remaining_packet is None, + "Interface %s: Packet expected from interface %s " + "didn't arrive" % (dst_if.name, i.name)) def test_fib(self): """ IPv4 FIB test diff --git a/test/test_ip6.py b/test/test_ip6.py index bff829b7..06b15f94 100644 --- a/test/test_ip6.py +++ b/test/test_ip6.py @@ -9,6 +9,7 @@ from vpp_sub_interface import VppSubInterface, VppDot1QSubint from scapy.packet import Raw from scapy.layers.l2 import Ether, Dot1Q from scapy.layers.inet6 import IPv6, UDP +from util import ppp class TestIPv6(VppTestCase): @@ -103,7 +104,7 @@ class TestIPv6(VppTestCase): counter += 1 if counter / count * 100 > percent: self.logger.info("Configure %d FIB entries .. %d%% done" % - (count, percent)) + (count, percent)) percent += 1 def create_stream(self, src_if, packet_sizes): @@ -171,16 +172,14 @@ class TestIPv6(VppTestCase): self.assertEqual(udp.sport, saved_packet[UDP].sport) self.assertEqual(udp.dport, saved_packet[UDP].dport) except: - self.logger.error("Unexpected or invalid packet:") - self.logger.error(packet.show()) + self.logger.error(ppp("Unexpected or invalid packet:", packet)) raise for i in self.interfaces: remaining_packet = self.get_next_packet_info_for_interface2( i.sw_if_index, dst_sw_if_index, last_info[i.sw_if_index]) - self.assertTrue( - remaining_packet is None, - "Interface %s: Packet expected from interface %s didn't arrive" % - (dst_if.name, i.name)) + self.assertTrue(remaining_packet is None, + "Interface %s: Packet expected from interface %s " + "didn't arrive" % (dst_if.name, i.name)) def test_fib(self): """ IPv6 FIB test diff --git a/test/test_l2bd.py b/test/test_l2bd.py index 46ba2e49..50720e64 100644 --- a/test/test_l2bd.py +++ b/test/test_l2bd.py @@ -8,7 +8,7 @@ from scapy.layers.l2 import Ether, Dot1Q from scapy.layers.inet import IP, UDP from framework import VppTestCase, VppTestRunner -from util import Host +from util import Host, ppp from vpp_sub_interface import VppDot1QSubint, VppDot1ADSubint @@ -109,7 +109,7 @@ class TestL2bd(VppTestCase): if not self.vpp_dead: self.logger.info(self.vapi.ppcli("show l2fib verbose")) self.logger.info(self.vapi.ppcli("show bridge-domain %s detail" % - self.bd_id)) + self.bd_id)) @classmethod def create_hosts_and_learn(cls, count): @@ -217,8 +217,7 @@ class TestL2bd(VppTestCase): self.assertEqual(udp.sport, saved_packet[UDP].sport) self.assertEqual(udp.dport, saved_packet[UDP].dport) except: - self.logger.error("Unexpected or invalid packet:") - self.logger.error(packet.show()) + self.logger.error(ppp("Unexpected or invalid packet:", packet)) raise for i in self.pg_interfaces: remaining_packet = self.get_next_packet_info_for_interface2( diff --git a/test/test_l2xc.py b/test/test_l2xc.py index 49ca9968..37893042 100644 --- a/test/test_l2xc.py +++ b/test/test_l2xc.py @@ -8,7 +8,7 @@ from scapy.layers.l2 import Ether from scapy.layers.inet import IP, UDP from framework import VppTestCase, VppTestRunner -from util import Host +from util import Host, ppp class TestL2xc(VppTestCase): @@ -169,8 +169,7 @@ class TestL2xc(VppTestCase): self.assertEqual(udp.sport, saved_packet[UDP].sport) self.assertEqual(udp.dport, saved_packet[UDP].dport) except: - self.logger.error("Unexpected or invalid packet:") - self.logger.error(packet.show()) + self.logger.error(ppp("Unexpected or invalid packet:", packet)) raise for i in self.interfaces: remaining_packet = self.get_next_packet_info_for_interface2( diff --git a/test/test_lb.py b/test/test_lb.py index fa4900d2..3e7f5e13 100644 --- a/test/test_lb.py +++ b/test/test_lb.py @@ -1,5 +1,4 @@ import socket -from logging import * from scapy.layers.inet import IP, UDP from scapy.layers.inet6 import IPv6 @@ -7,6 +6,7 @@ from scapy.layers.l2 import Ether, GRE from scapy.packet import Raw from framework import VppTestCase +from util import ppp """ TestLB is a subclass of VPPTestCase classes. @@ -57,7 +57,7 @@ class TestLB(VppTestCase): def tearDown(self): super(TestLB, self).tearDown() if not self.vpp_dead: - info(self.vapi.cli("show lb vip verbose")) + self.logger.info(self.vapi.cli("show lb vip verbose")) def getIPv4Flow(self, id): return (IP(dst="90.0.%u.%u" % (id / 255, id % 255), @@ -139,8 +139,7 @@ class TestLB(VppTestCase): self.checkInner(gre, isv4) load[asid] += 1 except: - error("Unexpected or invalid packet:") - p.show() + self.logger.error(ppp("Unexpected or invalid packet:", p)) raise # This is just to roughly check that the balancing algorithm diff --git a/test/test_mpls.py b/test/test_mpls.py index d1b1b919..08cd55b7 100644 --- a/test/test_mpls.py +++ b/test/test_mpls.py @@ -1,18 +1,18 @@ #!/usr/bin/env python import unittest -import socket -from logging import * from framework import VppTestCase, VppTestRunner from vpp_sub_interface import VppSubInterface, VppDot1QSubint, VppDot1ADSubint from vpp_ip_route import IpRoute, RoutePath, MplsRoute, MplsIpBind from scapy.packet import Raw -from scapy.layers.l2 import Ether, Dot1Q, ARP +from scapy.layers.l2 import Ether from scapy.layers.inet import IP, UDP -from scapy.layers.inet6 import ICMPv6ND_NS, IPv6, UDP +from scapy.layers.inet6 import IPv6 from scapy.contrib.mpls import MPLS +from util import ppp + class TestMPLS(VppTestCase): @@ -621,8 +621,8 @@ class TestMPLS(VppTestCase): try: self.assertEqual(0, len(rx)) except: - error("MPLS TTL=0 packets forwarded") - error(packet.show()) + self.logger.error("MPLS TTL=0 packets forwarded") + self.logger.error(ppp("", rx)) raise # diff --git a/test/test_span.py b/test/test_span.py index 59ef5efc..e42fbd77 100644 --- a/test/test_span.py +++ b/test/test_span.py @@ -1,15 +1,13 @@ #!/usr/bin/env python import unittest -import random from scapy.packet import Raw from scapy.layers.l2 import Ether from scapy.layers.inet import IP, UDP -from logging import * from framework import VppTestCase, VppTestRunner -from util import Host +from util import Host, ppp class TestSpan(VppTestCase): @@ -34,7 +32,7 @@ class TestSpan(VppTestCase): self.flows[self.pg0] = [self.pg1] # packet sizes - self.pg_if_packet_sizes = [64, 512] #, 1518, 9018] + self.pg_if_packet_sizes = [64, 512] # , 1518, 9018] self.interfaces = list(self.pg_interfaces) @@ -56,7 +54,8 @@ class TestSpan(VppTestCase): i.resolve_arp() # Enable SPAN on pg0 (mirrored to pg2) - self.vapi.sw_interface_span_enable_disable(self.pg0.sw_if_index, self.pg2.sw_if_index) + self.vapi.sw_interface_span_enable_disable( + self.pg0.sw_if_index, self.pg2.sw_if_index) def tearDown(self): super(TestSpan, self).tearDown() @@ -86,8 +85,6 @@ class TestSpan(VppTestCase): pkts = [] for i in range(0, TestSpan.pkts_per_burst): dst_if = self.flows[src_if][0] - dst_host = random.choice(self.hosts_by_pg_idx[dst_if.sw_if_index]) - src_host = random.choice(self.hosts_by_pg_idx[src_if.sw_if_index]) pkt_info = self.create_packet_info( src_if.sw_if_index, dst_if.sw_if_index) payload = self.info_to_payload(pkt_info) @@ -107,8 +104,9 @@ class TestSpan(VppTestCase): last_info[i.sw_if_index] = None dst_sw_if_index = dst_if.sw_if_index if len(capture_pg1) != len(capture_pg2): - error("Diffrent number of outgoing and mirrored packets : %u != %u" - % (len(capture_pg1), len(capture_pg2))) + self.logger.error( + "Different number of outgoing and mirrored packets : %u != %u" % + (len(capture_pg1), len(capture_pg2))) raise for pkt_pg1, pkt_pg2 in zip(capture_pg1, capture_pg2): try: @@ -117,23 +115,28 @@ class TestSpan(VppTestCase): raw1 = pkt_pg1[Raw] if pkt_pg1[Ether] != pkt_pg2[Ether]: - error("Diffrent ethernet header of outgoing and mirrored packet") + self.logger.error("Different ethernet header of " + "outgoing and mirrored packet") raise if ip1 != pkt_pg2[IP]: - error("Diffrent ip header of outgoing and mirrored packet") + self.logger.error( + "Different ip header of outgoing and mirrored packet") raise if udp1 != pkt_pg2[UDP]: - error("Diffrent udp header of outgoing and mirrored packet") + self.logger.error( + "Different udp header of outgoing and mirrored packet") raise if raw1 != pkt_pg2[Raw]: - error("Diffrent raw data of outgoing and mirrored packet") + self.logger.error( + "Different raw data of outgoing and mirrored packet") raise payload_info = self.payload_to_info(str(raw1)) packet_index = payload_info.index self.assertEqual(payload_info.dst, dst_sw_if_index) - debug("Got packet on port %s: src=%u (id=%u)" % - (dst_if.name, payload_info.src, packet_index)) + self.logger.debug( + "Got packet on port %s: src=%u (id=%u)" % + (dst_if.name, payload_info.src, packet_index)) next_info = self.get_next_packet_info_for_interface2( payload_info.src, dst_sw_if_index, last_info[payload_info.src]) @@ -147,9 +150,9 @@ class TestSpan(VppTestCase): self.assertEqual(udp1.sport, saved_packet[UDP].sport) self.assertEqual(udp1.dport, saved_packet[UDP].dport) except: - error("Unexpected or invalid packet:") - pkt_pg1.show() - pkt_pg2.show() + self.logger.error("Unexpected or invalid packets:") + self.logger.error(ppp("pg1 packet:", pkt_pg1)) + self.logger.error(ppp("pg2 packet:", pkt_pg2)) raise for i in self.interfaces: remaining_packet = self.get_next_packet_info_for_interface2( @@ -164,7 +167,8 @@ class TestSpan(VppTestCase): Test scenario: 1. config 3 interfaces, pg0 l2xconnected with pg1 - 2. sending l2 eth packets between 2 interfaces (pg0, pg1) and mirrored to pg2 + 2. sending l2 eth packets between 2 interfaces (pg0, pg1) and + mirrored to pg2 64B, 512B, 1518B, 9018B (ether_size) burst of packets per interface """ @@ -178,8 +182,11 @@ class TestSpan(VppTestCase): self.pg_start() # Verify packets outgoing packet streams on mirrored interface (pg2) - info("Verifying capture on interfaces %s and %s" % (self.pg1.name, self.pg2.name)) - self.verify_capture(self.pg1, self.pg1.get_capture(), self.pg2.get_capture()) + self.logger.info("Verifying capture on interfaces %s and %s" % + (self.pg1.name, self.pg2.name)) + self.verify_capture(self.pg1, + self.pg1.get_capture(), + self.pg2.get_capture()) if __name__ == '__main__': diff --git a/test/test_vxlan.py b/test/test_vxlan.py index cb7e7acf..ac435852 100644 --- a/test/test_vxlan.py +++ b/test/test_vxlan.py @@ -1,7 +1,6 @@ #!/usr/bin/env python import unittest -from logging import * from framework import VppTestCase, VppTestRunner from template_bd import BridgeDomain @@ -94,7 +93,7 @@ class TestVxlan(BridgeDomain, VppTestCase): def tearDown(self): super(TestVxlan, self).tearDown() if not self.vpp_dead: - info(self.vapi.cli("show bridge-domain 1 detail")) + self.logger.info(self.vapi.cli("show bridge-domain 1 detail")) if __name__ == '__main__': unittest.main(testRunner=VppTestRunner) diff --git a/test/util.py b/test/util.py index 6e7e275c..643377f5 100644 --- a/test/util.py +++ b/test/util.py @@ -1,4 +1,18 @@ import socket +import sys +from cStringIO import StringIO + + +def ppp(headline, packet): + """ Return string containing the output of scapy packet.show() call. """ + o = StringIO() + old_stdout = sys.stdout + sys.stdout = o + print(headline) + packet.show() + sys.stdout = old_stdout + return o.getvalue() + class Host(object): """ Generic test host "connected" to VPPs interface. """ diff --git a/test/vpp_interface.py b/test/vpp_interface.py index a450560e..024aeb5f 100644 --- a/test/vpp_interface.py +++ b/test/vpp_interface.py @@ -1,8 +1,6 @@ from abc import abstractmethod, ABCMeta import socket -from util import Host - class VppInterface(object): """Generic VPP interface.""" @@ -127,7 +125,8 @@ class VppInterface(object): self._hosts_by_mac = {} self._hosts_by_ip4 = {} self._hosts_by_ip6 = {} - for i in range(2, count+2): # 0: network address, 1: local vpp address + for i in range( + 2, count + 2): # 0: network address, 1: local vpp address mac = "02:%02x:00:00:ff:%02x" % (self.sw_if_index, i) ip4 = "172.16.%u.%u" % (self.sw_if_index, i) ip6 = "fd01:%04x::%04x" % (self.sw_if_index, i) @@ -158,10 +157,9 @@ class VppInterface(object): for intf in r: if intf.sw_if_index == self.sw_if_index: self._name = intf.interface_name.split(b'\0', 1)[0] - self._local_mac = ':'.join( - intf.l2_address.encode('hex')[i:i + 2] - for i in range(0, 12, 2) - ) + self._local_mac =\ + ':'.join(intf.l2_address.encode('hex')[i:i + 2] + for i in range(0, 12, 2)) self._dump = intf break else: diff --git a/test/vpp_papi_provider.py b/test/vpp_papi_provider.py index dc90289e..51cc20ba 100644 --- a/test/vpp_papi_provider.py +++ b/test/vpp_papi_provider.py @@ -1,6 +1,5 @@ import os import array -from logging import error from hook import Hook do_import = True @@ -32,10 +31,11 @@ class VppPapiProvider(object): """ - def __init__(self, name, shm_prefix): + def __init__(self, name, shm_prefix, test_class): self.hook = Hook("vpp-papi-provider") self.name = name self.shm_prefix = shm_prefix + self.test_class = test_class def register_hook(self, hook): """Replace hook registration with new hook @@ -68,7 +68,7 @@ class VppPapiProvider(object): if hasattr(reply, 'retval') and reply.retval != expected_retval: msg = "API call failed, expected retval == %d, got %s" % ( expected_retval, repr(reply)) - error(msg) + self.test_class.test_instance.logger.error(msg) raise Exception(msg) self.hook.after_api(api_fn.__name__, api_args) return reply @@ -497,7 +497,8 @@ class VppPapiProvider(object): ) ) - def sw_interface_span_enable_disable(self, sw_if_index_from, sw_if_index_to, enable=1): + def sw_interface_span_enable_disable( + self, sw_if_index_from, sw_if_index_to, enable=1): """ :param sw_if_index_from: @@ -683,3 +684,5 @@ class VppPapiProvider(object): next_hop_table_id, stack)) + return self.api(vpp_papi.sw_interface_span_enable_disable, + (sw_if_index_from, sw_if_index_to, enable)) diff --git a/test/vpp_pg_interface.py b/test/vpp_pg_interface.py index 81a0fba9..533c4603 100644 --- a/test/vpp_pg_interface.py +++ b/test/vpp_pg_interface.py @@ -1,12 +1,12 @@ import os import time -from logging import error, info from scapy.utils import wrpcap, rdpcap from vpp_interface import VppInterface from scapy.layers.l2 import Ether, ARP from scapy.layers.inet6 import IPv6, ICMPv6ND_NS, ICMPv6ND_NA, \ ICMPv6NDOptSrcLLAddr, ICMPv6NDOptDstLLAddr +from util import ppp class VppPGInterface(VppInterface): @@ -127,8 +127,8 @@ class VppPGInterface(VppInterface): try: output = rdpcap(self.out_path) except IOError: # TODO - error("File %s does not exist, probably because no" - " packets arrived" % self.out_path) + self.test.logger.error("File %s does not exist, probably because no" + " packets arrived" % self.out_path) return [] return output @@ -154,16 +154,18 @@ class VppPGInterface(VppInterface): """ if pg_interface is None: pg_interface = self - info("Sending ARP request for %s on port %s" % - (self.local_ip4, pg_interface.name)) + self.test.logger.info("Sending ARP request for %s on port %s" % + (self.local_ip4, pg_interface.name)) arp_req = self.create_arp_req() pg_interface.add_stream(arp_req) pg_interface.enable_capture() self.test.pg_start() - info(self.test.vapi.cli("show trace")) + self.test.logger.info(self.test.vapi.cli("show trace")) arp_reply = pg_interface.get_capture() if arp_reply is None or len(arp_reply) == 0: - info("No ARP received on port %s" % pg_interface.name) + self.test.logger.info( + "No ARP received on port %s" % + pg_interface.name) return arp_reply = arp_reply[0] # Make Dot1AD packet content recognizable to scapy @@ -172,14 +174,16 @@ class VppPGInterface(VppInterface): arp_reply = Ether(str(arp_reply)) try: if arp_reply[ARP].op == ARP.is_at: - info("VPP %s MAC address is %s " % - (self.name, arp_reply[ARP].hwsrc)) + self.test.logger.info("VPP %s MAC address is %s " % + (self.name, arp_reply[ARP].hwsrc)) self._local_mac = arp_reply[ARP].hwsrc else: - info("No ARP received on port %s" % pg_interface.name) + self.test.logger.info( + "No ARP received on port %s" % + pg_interface.name) except: - error("Unexpected response to ARP request:") - error(arp_reply.show()) + self.test.logger.error( + ppp("Unexpected response to ARP request:", arp_reply)) raise def resolve_ndp(self, pg_interface=None): @@ -191,16 +195,18 @@ class VppPGInterface(VppInterface): """ if pg_interface is None: pg_interface = self - info("Sending NDP request for %s on port %s" % - (self.local_ip6, pg_interface.name)) + self.test.logger.info("Sending NDP request for %s on port %s" % + (self.local_ip6, pg_interface.name)) ndp_req = self.create_ndp_req() pg_interface.add_stream(ndp_req) pg_interface.enable_capture() self.test.pg_start() - info(self.test.vapi.cli("show trace")) + self.test.logger.info(self.test.vapi.cli("show trace")) ndp_reply = pg_interface.get_capture() if ndp_reply is None or len(ndp_reply) == 0: - info("No NDP received on port %s" % pg_interface.name) + self.test.logger.info( + "No NDP received on port %s" % + pg_interface.name) return ndp_reply = ndp_reply[0] # Make Dot1AD packet content recognizable to scapy @@ -210,10 +216,10 @@ class VppPGInterface(VppInterface): try: ndp_na = ndp_reply[ICMPv6ND_NA] opt = ndp_na[ICMPv6NDOptDstLLAddr] - info("VPP %s MAC address is %s " % - (self.name, opt.lladdr)) + self.test.logger.info("VPP %s MAC address is %s " % + (self.name, opt.lladdr)) self._local_mac = opt.lladdr except: - error("Unexpected response to NDP request:") - error(ndp_reply.show()) + self.test.logger.error( + ppp("Unexpected response to NDP request:", ndp_reply)) raise -- cgit 1.2.3-korg From 65209ed18c0bdc4e8d3d4a2ebbcd6cf34b68bb43 Mon Sep 17 00:00:00 2001 From: Jan Date: Mon, 5 Dec 2016 23:29:17 +0100 Subject: test: l2bd instance multi-context correction - small correction of docstrings - fix of create_pg_interface Change-Id: I1958bd5ddaddaa2f7e6cbb18b0076e59e86d1e68 Signed-off-by: Jan Gelety --- test/test_l2bd_multi_instance.py | 50 +++++++++++++++++----------------------- test/vpp_interface.py | 2 ++ 2 files changed, 23 insertions(+), 29 deletions(-) (limited to 'test/vpp_interface.py') diff --git a/test/test_l2bd_multi_instance.py b/test/test_l2bd_multi_instance.py index 56e66342..5f489ca0 100644 --- a/test/test_l2bd_multi_instance.py +++ b/test/test_l2bd_multi_instance.py @@ -4,8 +4,8 @@ **NOTES:** - higher number of pg-l2 interfaces causes problems => only 15 pg-l2 \ interfaces in 5 bridge domains are tested - - more then 1 host per pg-l2 interface in configuration with 15 l2-pg \ - interfaces leads to problems too + - jumbo packets in configuration with 14 l2-pg interfaces leads to \ + problems too **config 1** - add 15 pg-l2 interfaces @@ -105,10 +105,10 @@ class TestL2bdMultiInst(VppTestCase): cls.hosts_by_pg_idx[pg_if.sw_if_index] = [] # Create test host entries - cls.create_hosts(15) + cls.create_hosts(75) - # Packet sizes - cls.pg_if_packet_sizes = [64, 512, 1518, 9018] + # Packet sizes - jumbo packet (9018 bytes) skipped + cls.pg_if_packet_sizes = [64, 512, 1518] # Set up all interfaces for i in cls.pg_interfaces: @@ -171,7 +171,7 @@ class TestL2bdMultiInst(VppTestCase): hosts.append(host) def create_bd_and_mac_learn(self, count, start=1): - """" + """ Create required number of bridge domains with MAC learning enabled, put 3 l2-pg interfaces to every bridge domain and send MAC learning packets. @@ -206,7 +206,7 @@ class TestL2bdMultiInst(VppTestCase): self.logger.info(self.vapi.ppcli("show l2fib")) def delete_bd(self, count, start=1): - """" + """ Delete required number of bridge domains. :param int count: Number of bridge domains to be created. @@ -306,13 +306,9 @@ class TestL2bdMultiInst(VppTestCase): Enable/disable defined feature(s) of the bridge domain. :param int bd_id: Bridge domain ID. - :param list args: List of feature/status pairs. Allowed features: - - learn, - - forward, - - flood, - - uu_flood and - - arp_term - Status False means disable, status True means enable the feature. + :param list args: List of feature/status pairs. Allowed features: \ + learn, forward, flood, uu_flood and arp_term. Status False means \ + disable, status True means enable the feature. :raise: ValueError in case of unknown feature in the input. """ for flag in args: @@ -338,13 +334,9 @@ class TestL2bdMultiInst(VppTestCase): of listed features. :param int bd_id: Bridge domain ID. - :param list args: List of feature/status pairs. Allowed features: - - learn, - - forward, - - flood, - - uu_flood and - - arp_term - Status False means disable, status True means enable the feature. + :param list args: List of feature/status pairs. Allowed features: \ + learn, forward, flood, uu_flood and arp_term. Status False means \ + disable, status True means enable the feature. :return: 1 if bridge domain is configured, otherwise return 0. :raise: ValueError in case of unknown feature in the input. """ @@ -376,14 +368,14 @@ class TestL2bdMultiInst(VppTestCase): """ Create packet streams for all configured l2-pg interfaces, send all prepared packet streams and verify that: - - all packets received correctly on all pg-l2 interfaces assigned to - bridge domains - - no packet received on all pg-l2 interfaces not assigned to bridge - domains - - :raise: RuntimeError if no packet captured on l2-pg interface assigned - to the bridge domain or if any packet is captured on l2-pg interface - not assigned to the bridge domain. + - all packets received correctly on all pg-l2 interfaces assigned \ + to bridge domains + - no packet received on all pg-l2 interfaces not assigned to \ + bridge domains + + :raise: RuntimeError if no packet captured on l2-pg interface assigned \ + to the bridge domain or if any packet is captured on l2-pg interface \ + not assigned to the bridge domain. """ # Test # Create incoming packet streams for packet-generator interfaces diff --git a/test/vpp_interface.py b/test/vpp_interface.py index 024aeb5f..78865108 100644 --- a/test/vpp_interface.py +++ b/test/vpp_interface.py @@ -1,6 +1,8 @@ from abc import abstractmethod, ABCMeta import socket +from util import Host + class VppInterface(object): """Generic VPP interface.""" -- cgit 1.2.3-korg From 8d8a1da52907246c3218ea7c60ab8ca0569d0a83 Mon Sep 17 00:00:00 2001 From: Matej Klotton Date: Thu, 22 Dec 2016 11:06:56 +0100 Subject: make test: Loopback interface CRUD test Change-Id: I0581da7a682bfe4dd6520ecf1b2ea6bd8c20b1b3 Signed-off-by: Matej Klotton --- test/framework.py | 11 ++-- test/test_interface_crud.py | 151 ++++++++++++++++++++++++++++++++++++++++++++ test/vpp_interface.py | 54 +++++++++++----- test/vpp_lo_interface.py | 30 +++++++-- test/vpp_papi_provider.py | 8 ++- 5 files changed, 226 insertions(+), 28 deletions(-) create mode 100644 test/test_interface_crud.py (limited to 'test/vpp_interface.py') diff --git a/test/framework.py b/test/framework.py index 324a64ce..e364a8f5 100644 --- a/test/framework.py +++ b/test/framework.py @@ -355,9 +355,10 @@ class VppTestCase(unittest.TestCase): @classmethod def create_pg_interfaces(cls, interfaces): """ - Create packet-generator interfaces + Create packet-generator interfaces. - :param interfaces: iterable indexes of the interfaces + :param interfaces: iterable indexes of the interfaces. + :returns: List of created interfaces. """ result = [] @@ -371,10 +372,10 @@ class VppTestCase(unittest.TestCase): @classmethod def create_loopback_interfaces(cls, interfaces): """ - Create loopback interfaces - - :param interfaces: iterable indexes of the interfaces + Create loopback interfaces. + :param interfaces: iterable indexes of the interfaces. + :returns: List of created interfaces. """ result = [] for i in interfaces: diff --git a/test/test_interface_crud.py b/test/test_interface_crud.py new file mode 100644 index 00000000..63917047 --- /dev/null +++ b/test/test_interface_crud.py @@ -0,0 +1,151 @@ +#!/usr/bin/env python +"""CRUD tests of APIs (Create, Read, Update, Delete) HLD: + +- interface up/down/add/delete - interface type: + - pg (TBD) + - loopback + - vhostuser (TBD) + - af_packet (TBD) + - netmap (TBD) + - tuntap (root privileges needed) + - vxlan (TBD) +""" + +import unittest + +from scapy.layers.inet import IP, ICMP +from scapy.layers.l2 import Ether + +from framework import VppTestCase, VppTestRunner + + +class TestLoopbackInterfaceCRUD(VppTestCase): + """CRUD Loopback + + """ + + @classmethod + def setUpClass(cls): + super(TestLoopbackInterfaceCRUD, cls).setUpClass() + try: + cls.create_pg_interfaces(range(1)) + for i in cls.pg_interfaces: + i.config_ip4() + i.resolve_arp() + except: + cls.tearDownClass() + raise + + @staticmethod + def create_icmp_stream(src_if, dst_ifs): + """ + + :param VppInterface src_if: Packets are send to this interface, + using this interfaces remote host. + :param list dst_ifs: IPv4 ICMP requests are send to interfaces + addresses. + :return: List of generated packets. + """ + pkts = [] + for i in dst_ifs: + p = (Ether(dst=src_if.local_mac, src=src_if.remote_mac) / + IP(src=src_if.remote_ip4, dst=i.local_ip4) / + ICMP(id=i.sw_if_index, type='echo-request')) + pkts.append(p) + return pkts + + def verify_icmp(self, capture, request_src_if, dst_ifs): + """ + + :param capture: Capture to verify. + :param VppInterface request_src_if: Interface where was send packets. + :param list dst_ifs: Interfaces where was generated IPv4 ICMP requests. + """ + rcvd_icmp_pkts = [] + for pkt in capture: + try: + ip = pkt[IP] + icmp = pkt[ICMP] + except IndexError: + pass + else: + info = (ip.src, ip.dst, icmp.type, icmp.id) + rcvd_icmp_pkts.append(info) + + for i in dst_ifs: + # 0 - icmp echo response + info = (i.local_ip4, request_src_if.remote_ip4, 0, i.sw_if_index) + self.assertIn(info, rcvd_icmp_pkts) + + def test_crud(self): + # create + loopbacks = self.create_loopback_interfaces(range(20)) + for i in loopbacks: + i.local_ip4_prefix_len = 32 + i.config_ip4() + i.admin_up() + + # read (check sw if dump, ip4 fib, ip6 fib) + if_dump = self.vapi.sw_interface_dump() + fib4_dump = self.vapi.ip_fib_dump() + for i in loopbacks: + self.assertTrue(i.is_interface_config_in_dump(if_dump)) + self.assertTrue(i.is_ip4_entry_in_fib_dump(fib4_dump)) + + # check ping + stream = self.create_icmp_stream(self.pg0, loopbacks) + self.pg0.add_stream(stream) + self.pg_enable_capture(self.pg_interfaces) + self.pg_start() + capture = self.pg0.get_capture(expected_count=len(stream)) + + self.verify_icmp(capture, self.pg0, loopbacks) + + # delete + for i in loopbacks: + i.remove_vpp_config() + + # read (check not in sw if dump, ip4 fib, ip6 fib) + if_dump = self.vapi.sw_interface_dump() + fib4_dump = self.vapi.ip_fib_dump() + for i in loopbacks: + self.assertFalse(i.is_interface_config_in_dump(if_dump)) + self.assertFalse(i.is_ip4_entry_in_fib_dump(fib4_dump)) + + # check not ping + stream = self.create_icmp_stream(self.pg0, loopbacks) + self.pg0.add_stream(stream) + self.pg_enable_capture(self.pg_interfaces) + self.pg_start() + self.pg0.assert_nothing_captured() + + def test_down(self): + # create + loopbacks = self.create_loopback_interfaces(range(20)) + for i in loopbacks: + i.local_ip4_prefix_len = 32 + i.config_ip4() + i.admin_up() + + # disable + for i in loopbacks: + i.admin_down() + i.unconfig_ip4() + + # read (check not in sw if dump, ip4 fib, ip6 fib) + if_dump = self.vapi.sw_interface_dump() + fib4_dump = self.vapi.ip_fib_dump() + for i in loopbacks: + self.assertTrue(i.is_interface_config_in_dump(if_dump)) + self.assertFalse(i.is_ip4_entry_in_fib_dump(fib4_dump)) + + # check not ping + stream = self.create_icmp_stream(self.pg0, loopbacks) + self.pg0.add_stream(stream) + self.pg_enable_capture(self.pg_interfaces) + self.pg_start() + self.pg0.assert_nothing_captured() + + +if __name__ == '__main__': + unittest.main(testRunner=VppTestRunner) diff --git a/test/vpp_interface.py b/test/vpp_interface.py index 78865108..6ccb92bb 100644 --- a/test/vpp_interface.py +++ b/test/vpp_interface.py @@ -151,15 +151,21 @@ class VppInterface(object): self._local_ip4 = "172.16.%u.1" % self.sw_if_index self._local_ip4n = socket.inet_pton(socket.AF_INET, self.local_ip4) + self.local_ip4_prefix_len = 24 + self.has_ip4_config = False + self.ip4_table_id = 0 self._local_ip6 = "fd01:%04x::1" % self.sw_if_index self._local_ip6n = socket.inet_pton(socket.AF_INET6, self.local_ip6) + self.local_ip6_prefix_len = 64 + self.has_ip6_config = False + self.ip6_table_id = 0 r = self.test.vapi.sw_interface_dump() for intf in r: if intf.sw_if_index == self.sw_if_index: self._name = intf.interface_name.split(b'\0', 1)[0] - self._local_mac =\ + self._local_mac = \ ':'.join(intf.l2_address.encode('hex')[i:i + 2] for i in range(0, 12, 2)) self._dump = intf @@ -172,20 +178,19 @@ class VppInterface(object): def config_ip4(self): """Configure IPv4 address on the VPP interface.""" - addr = self.local_ip4n - addr_len = 24 self.test.vapi.sw_interface_add_del_address( - self.sw_if_index, addr, addr_len) + self.sw_if_index, self.local_ip4n, self.local_ip4_prefix_len) self.has_ip4_config = True def unconfig_ip4(self): - """Remove IPv4 address on the VPP interface""" + """Remove IPv4 address on the VPP interface.""" try: - if (self.has_ip4_config): + if self.has_ip4_config: self.test.vapi.sw_interface_add_del_address( self.sw_if_index, self.local_ip4n, - 24, is_add=0) + self.local_ip4_prefix_len, + is_add=0) except AttributeError: self.has_ip4_config = False self.has_ip4_config = False @@ -199,43 +204,46 @@ class VppInterface(object): def config_ip6(self): """Configure IPv6 address on the VPP interface.""" - addr = self._local_ip6n - addr_len = 64 self.test.vapi.sw_interface_add_del_address( - self.sw_if_index, addr, addr_len, is_ipv6=1) + self.sw_if_index, self._local_ip6n, self.local_ip6_prefix_len, + is_ipv6=1) self.has_ip6_config = True def unconfig_ip6(self): - """Remove IPv6 address on the VPP interface""" + """Remove IPv6 address on the VPP interface.""" try: - if (self.has_ip6_config): + if self.has_ip6_config: self.test.vapi.sw_interface_add_del_address( self.sw_if_index, self.local_ip6n, - 64, is_ipv6=1, is_add=0) + self.local_ip6_prefix_len, + is_ipv6=1, is_add=0) except AttributeError: self.has_ip6_config = False self.has_ip6_config = False def unconfig(self): - """Unconfigure IPv6 and IPv4 address on the VPP interface""" + """Unconfigure IPv6 and IPv4 address on the VPP interface.""" self.unconfig_ip4() self.unconfig_ip6() def set_table_ip4(self, table_id): """Set the interface in a IPv4 Table. - .. note:: Must be called before configuring IP4 addresses.""" + .. note:: Must be called before configuring IP4 addresses. + """ + self.ip4_table_id = table_id self.test.vapi.sw_interface_set_table( - self.sw_if_index, 0, table_id) + self.sw_if_index, 0, self.ip4_table_id) def set_table_ip6(self, table_id): """Set the interface in a IPv6 Table. .. note:: Must be called before configuring IP6 addresses. """ + self.ip6_table_id = table_id self.test.vapi.sw_interface_set_table( - self.sw_if_index, 1, table_id) + self.sw_if_index, 1, self.ip6_table_id) def disable_ipv6_ra(self): """Configure IPv6 RA suppress on the VPP interface.""" @@ -245,6 +253,10 @@ class VppInterface(object): """Put interface ADMIN-UP.""" self.test.vapi.sw_interface_set_flags(self.sw_if_index, admin_up_down=1) + def admin_down(self): + """Put interface ADMIN-down.""" + self.test.vapi.sw_interface_set_flags(self.sw_if_index, admin_up_down=0) + def add_sub_if(self, sub_if): """Register a sub-interface with this interface. @@ -262,3 +274,11 @@ class VppInterface(object): """Enable MPLS on the VPP interface.""" self.test.vapi.sw_interface_enable_disable_mpls( self.sw_if_index) + + def is_ip4_entry_in_fib_dump(self, dump): + for i in dump: + if i.address == self.local_ip4n and \ + i.address_length == self.local_ip4_prefix_len and \ + i.table_id == self.ip4_table_id: + return True + return False diff --git a/test/vpp_lo_interface.py b/test/vpp_lo_interface.py index 19ee1523..9493a700 100644 --- a/test/vpp_lo_interface.py +++ b/test/vpp_lo_interface.py @@ -1,13 +1,35 @@ - +from vpp_object import VppObject from vpp_interface import VppInterface -class VppLoInterface(VppInterface): +class VppLoInterface(VppInterface, VppObject): """VPP loopback interface.""" def __init__(self, test, lo_index): """ Create VPP loopback interface """ - r = test.vapi.create_loopback() - self._sw_if_index = r.sw_if_index + self._test = test + self.add_vpp_config() super(VppLoInterface, self).__init__(test) self._lo_index = lo_index + + def add_vpp_config(self): + r = self.test.vapi.create_loopback() + self._sw_if_index = r.sw_if_index + + def remove_vpp_config(self): + self.test.vapi.delete_loopback(self.sw_if_index) + + def query_vpp_config(self): + dump = self.vapi.sw_interface_dump() + return self.is_interface_config_in_dump(dump) + + def is_interface_config_in_dump(self, dump): + for i in dump: + if i.interface_name.rstrip(' \t\r\n\0') == self.name and \ + i.sw_if_index == self.sw_if_index: + return True + else: + return False + + def object_id(self): + return "loopback-%d" % self._sw_if_index diff --git a/test/vpp_papi_provider.py b/test/vpp_papi_provider.py index bdbcc3d2..db530d98 100644 --- a/test/vpp_papi_provider.py +++ b/test/vpp_papi_provider.py @@ -465,6 +465,10 @@ class VppPapiProvider(object): return self.api(self.papi.create_loopback, {'mac_address': mac}) + def delete_loopback(self, sw_if_index): + return self.api(self.papi.delete_loopback, + {'sw_if_index': sw_if_index, }) + def ip_add_del_route( self, dst_address, @@ -904,8 +908,8 @@ class VppPapiProvider(object): """ :param is_add: :param mask: - :param match_n_vectors (Default value = 1): - :param table_index (Default value = 0xFFFFFFFF) + :param match_n_vectors: (Default value = 1): + :param table_index: (Default value = 0xFFFFFFFF) :param nbuckets: (Default value = 2) :param memory_size: (Default value = 2097152) :param skip_n_vectors: (Default value = 0) -- cgit 1.2.3-korg From 75152289284aaf1116d62c6cdef5a3b0c793fa15 Mon Sep 17 00:00:00 2001 From: Neale Ranns Date: Mon, 9 Jan 2017 01:00:45 -0800 Subject: IPv6 NS RS tests and fixes includes Fix for VPP-584 with API change to remove prefix length from LL programming Change-Id: If860751c35e60255fb977f73bc33e8c2649e728e Signed-off-by: Neale Ranns --- src/vat/api_format.c | 5 +- src/vnet/interface_funcs.h | 8 ++ src/vnet/ip/icmp6.h | 4 - src/vnet/ip/ip.api | 2 - src/vnet/ip/ip6.h | 3 +- src/vnet/ip/ip6_forward.c | 6 +- src/vnet/ip/ip6_neighbor.c | 42 ++------ src/vnet/ip/ip_api.c | 3 +- src/vnet/rewrite.c | 7 +- src/vpp/api/custom_dump.c | 3 +- src/vpp/api/test_client.c | 2 - test/test_ip6.py | 237 ++++++++++++++++++++++++++++++++++++++++++++- test/vpp_interface.py | 14 +++ test/vpp_papi_provider.py | 16 +++ test/vpp_pg_interface.py | 5 +- 15 files changed, 300 insertions(+), 57 deletions(-) (limited to 'test/vpp_interface.py') diff --git a/src/vat/api_format.c b/src/vat/api_format.c index c00104de..75148207 100644 --- a/src/vat/api_format.c +++ b/src/vat/api_format.c @@ -7671,7 +7671,6 @@ api_sw_interface_ip6_set_link_local_address (vat_main_t * vam) f64 timeout; u32 sw_if_index; u8 sw_if_index_set = 0; - u32 address_length = 0; u8 v6_address_set = 0; ip6_address_t v6address; @@ -7682,8 +7681,7 @@ api_sw_interface_ip6_set_link_local_address (vat_main_t * vam) sw_if_index_set = 1; else if (unformat (i, "sw_if_index %d", &sw_if_index)) sw_if_index_set = 1; - else if (unformat (i, "%U/%d", - unformat_ip6_address, &v6address, &address_length)) + else if (unformat (i, "%U", unformat_ip6_address, &v6address)) v6_address_set = 1; else break; @@ -7706,7 +7704,6 @@ api_sw_interface_ip6_set_link_local_address (vat_main_t * vam) mp->sw_if_index = ntohl (sw_if_index); clib_memcpy (mp->address, &v6address, sizeof (v6address)); - mp->address_length = address_length; /* send it... */ S; diff --git a/src/vnet/interface_funcs.h b/src/vnet/interface_funcs.h index b84d151c..ab808dfa 100644 --- a/src/vnet/interface_funcs.h +++ b/src/vnet/interface_funcs.h @@ -52,6 +52,14 @@ vnet_get_sw_interface (vnet_main_t * vnm, u32 sw_if_index) return pool_elt_at_index (vnm->interface_main.sw_interfaces, sw_if_index); } +always_inline vnet_sw_interface_t * +vnet_get_sw_interface_safe (vnet_main_t * vnm, u32 sw_if_index) +{ + if (!pool_is_free_index (vnm->interface_main.sw_interfaces, sw_if_index)) + return pool_elt_at_index (vnm->interface_main.sw_interfaces, sw_if_index); + return (NULL); +} + always_inline vnet_sw_interface_t * vnet_get_hw_sw_interface (vnet_main_t * vnm, u32 hw_if_index) { diff --git a/src/vnet/ip/icmp6.h b/src/vnet/ip/icmp6.h index a426512e..9a3487b1 100644 --- a/src/vnet/ip/icmp6.h +++ b/src/vnet/ip/icmp6.h @@ -37,10 +37,6 @@ "neighbor discovery unsupported interface") \ _ (ROUTER_SOLICITATION_RADV_NOT_CONFIG, \ "neighbor discovery not configured") \ - _ (ROUTER_SOLICITATION_DEST_UNKNOWN, \ - "router solicitations for unknown destination") \ - _ (ROUTER_SOLICITATION_SOURCE_UNKNOWN, \ - "router solicitations for unknown source") \ _ (ROUTER_ADVERTISEMENT_SOURCE_NOT_LINK_LOCAL, \ "router advertisement source not link local") \ _ (ROUTER_ADVERTISEMENTS_TX, "router advertisements sent") \ diff --git a/src/vnet/ip/ip.api b/src/vnet/ip/ip.api index c811e465..f2444805 100644 --- a/src/vnet/ip/ip.api +++ b/src/vnet/ip/ip.api @@ -311,7 +311,6 @@ define sw_interface_ip6_enable_disable_reply @param context - sender context, to match reply w/ request @param sw_if_index - interface to set link local on @param address[] - the new link local address - @param address_length - link local address length */ define sw_interface_ip6_set_link_local_address { @@ -319,7 +318,6 @@ define sw_interface_ip6_set_link_local_address u32 context; u32 sw_if_index; u8 address[16]; - u8 address_length; }; /** \brief IPv6 set link local address on interface response diff --git a/src/vnet/ip/ip6.h b/src/vnet/ip/ip6.h index 586b7c1b..f493db01 100644 --- a/src/vnet/ip/ip6.h +++ b/src/vnet/ip/ip6.h @@ -376,8 +376,7 @@ int ip6_interface_enabled (vlib_main_t * vm, u32 sw_if_index); clib_error_t *set_ip6_link_local_address (vlib_main_t * vm, u32 sw_if_index, - ip6_address_t * address, - u8 address_length); + ip6_address_t * address); void vnet_register_ip6_neighbor_resolution_event (vnet_main_t * vnm, void *address_arg, diff --git a/src/vnet/ip/ip6_forward.c b/src/vnet/ip/ip6_forward.c index b5c79552..866a44e6 100644 --- a/src/vnet/ip/ip6_forward.c +++ b/src/vnet/ip/ip6_forward.c @@ -404,8 +404,10 @@ ip6_sw_interface_enable_disable (u32 sw_if_index, u32 is_enable) } else { - ASSERT (im->ip_enabled_by_sw_if_index[sw_if_index] > 0); - if (0 != --im->ip_enabled_by_sw_if_index[sw_if_index]) + /* The ref count is 0 when an address is removed from an interface that has + * no address - this is not a ciritical error */ + if (0 == im->ip_enabled_by_sw_if_index[sw_if_index] || + 0 != --im->ip_enabled_by_sw_if_index[sw_if_index]) return; } diff --git a/src/vnet/ip/ip6_neighbor.c b/src/vnet/ip/ip6_neighbor.c index 5a1c9e86..46c0e316 100644 --- a/src/vnet/ip/ip6_neighbor.c +++ b/src/vnet/ip/ip6_neighbor.c @@ -155,8 +155,6 @@ typedef struct /* Link local address to use (defaults to underlying physical for logical interfaces */ ip6_address_t link_local_address; - u8 link_local_prefix_len; - } ip6_radv_t; typedef struct @@ -1316,7 +1314,8 @@ icmp6_router_solicitation (vlib_main_t * vm, /* for solicited adverts - need to rate limit */ if (is_solicitation) { - if ((now - radv_info->last_radv_time) < + if (0 != radv_info->last_radv_time && + (now - radv_info->last_radv_time) < MIN_DELAY_BETWEEN_RAS) is_dropped = 1; else @@ -1523,16 +1522,6 @@ icmp6_router_solicitation (vlib_main_t * vm, error0 = ICMP6_ERROR_DST_LOOKUP_MISS; else { - ip_adjacency_t *adj0 = - ip_get_adjacency (&im->lookup_main, - adj_index0); - error0 = - ((adj0->rewrite_header.sw_if_index != - sw_if_index0 - || adj0->lookup_next_index != - IP_LOOKUP_NEXT_REWRITE) ? - ICMP6_ERROR_ROUTER_SOLICITATION_DEST_UNKNOWN - : error0); next0 = is_dropped ? next0 : ICMP6_ROUTER_SOLICITATION_NEXT_REPLY_RW; @@ -2022,7 +2011,6 @@ ip6_neighbor_sw_interface_add_del (vnet_main_t * vnm, /* fill in default link-local address (this may be overridden) */ ip6_link_local_address_from_ethernet_address (&a->link_local_address, eth_if0->address); - a->link_local_prefix_len = 64; mhash_init (&a->address_to_prefix_index, sizeof (uword), sizeof (ip6_address_t)); @@ -3266,9 +3254,7 @@ disable_ip6_interface (vlib_main_t * vm, u32 sw_if_index) /* essentially "disables" ipv6 on this interface */ error = ip6_add_del_interface_address (vm, sw_if_index, &radv_info-> - link_local_address, - radv_info-> - link_local_prefix_len, + link_local_address, 128, 1 /* is_del */ ); ip6_neighbor_sw_interface_add_del (vnm, sw_if_index, @@ -3372,7 +3358,6 @@ enable_ip6_interface (vlib_main_t * vm, u32 sw_if_index) else { radv_info->link_local_address = link_local_address; - radv_info->link_local_prefix_len = 64; } } } @@ -3585,8 +3570,7 @@ VLIB_CLI_COMMAND (ip6_nd_command, static) = clib_error_t * set_ip6_link_local_address (vlib_main_t * vm, - u32 sw_if_index, - ip6_address_t * address, u8 address_length) + u32 sw_if_index, ip6_address_t * address) { clib_error_t *error = 0; ip6_neighbor_main_t *nm = &ip6_neighbor_main; @@ -3615,22 +3599,18 @@ set_ip6_link_local_address (vlib_main_t * vm, /* delete the old one */ error = ip6_add_del_interface_address (vm, sw_if_index, &radv_info->link_local_address, - radv_info->link_local_prefix_len - /* address width */ , - 1 /* is_del */ ); + 128, 1 /* is_del */ ); if (!error) { /* add the new one */ error = ip6_add_del_interface_address (vm, sw_if_index, - address, address_length - /* address width */ , + address, 128, 0 /* is_del */ ); if (!error) { radv_info->link_local_address = *address; - radv_info->link_local_prefix_len = address_length; } } } @@ -3652,21 +3632,19 @@ set_ip6_link_local_address_cmd (vlib_main_t * vm, clib_error_t *error = 0; u32 sw_if_index; ip6_address_t ip6_addr; - u32 addr_len = 0; if (unformat_user (input, unformat_vnet_sw_interface, vnm, &sw_if_index)) { /* get the rest of the command */ while (unformat_check_input (input) != UNFORMAT_END_OF_INPUT) { - if (unformat (input, "%U/%d", - unformat_ip6_address, &ip6_addr, &addr_len)) + if (unformat (input, "%U", unformat_ip6_address, &ip6_addr)) break; else return (unformat_parse_error (input)); } } - error = set_ip6_link_local_address (vm, sw_if_index, &ip6_addr, addr_len); + error = set_ip6_link_local_address (vm, sw_if_index, &ip6_addr); return error; } @@ -3678,13 +3656,13 @@ set_ip6_link_local_address_cmd (vlib_main_t * vm, * * @cliexpar * Example of how to assign an IPv6 Link-local address to an interface: - * @cliexcmd{set ip6 link-local address GigabitEthernet2/0/0 FE80::AB8/64} + * @cliexcmd{set ip6 link-local address GigabitEthernet2/0/0 FE80::AB8} ?*/ /* *INDENT-OFF* */ VLIB_CLI_COMMAND (set_ip6_link_local_address_command, static) = { .path = "set ip6 link-local address", - .short_help = "set ip6 link-local address /", + .short_help = "set ip6 link-local address ", .function = set_ip6_link_local_address_cmd, }; /* *INDENT-ON* */ diff --git a/src/vnet/ip/ip_api.c b/src/vnet/ip/ip_api.c index cd9b7397..aafde464 100644 --- a/src/vnet/ip/ip_api.c +++ b/src/vnet/ip/ip_api.c @@ -1132,8 +1132,7 @@ static void error = set_ip6_link_local_address (vm, ntohl (mp->sw_if_index), - (ip6_address_t *) mp->address, - mp->address_length); + (ip6_address_t *) mp->address); if (error) { clib_error_report (error); diff --git a/src/vnet/rewrite.c b/src/vnet/rewrite.c index 53d548bc..8925ad61 100644 --- a/src/vnet/rewrite.c +++ b/src/vnet/rewrite.c @@ -79,8 +79,11 @@ format_vnet_rewrite (u8 * s, va_list * args) if (rw->sw_if_index != ~0) { vnet_sw_interface_t *si; - si = vnet_get_sw_interface (vnm, rw->sw_if_index); - s = format (s, "%U: ", format_vnet_sw_interface_name, vnm, si); + si = vnet_get_sw_interface_safe (vnm, rw->sw_if_index); + if (NULL != si) + s = format (s, "%U: ", format_vnet_sw_interface_name, vnm, si); + else + s = format (s, "DELETED"); } else s = format (s, "%v: ", next->name); diff --git a/src/vpp/api/custom_dump.c b/src/vpp/api/custom_dump.c index c2cd3d15..f14a031d 100644 --- a/src/vpp/api/custom_dump.c +++ b/src/vpp/api/custom_dump.c @@ -911,8 +911,7 @@ static void *vl_api_sw_interface_ip6_set_link_local_address_t_print s = format (s, "sw_if_index %d ", ntohl (mp->sw_if_index)); - s = format (s, "%U/%d ", format_ip6_address, mp->address, - mp->address_length); + s = format (s, "%U ", format_ip6_address, mp->address); FINISH; } diff --git a/src/vpp/api/test_client.c b/src/vpp/api/test_client.c index 5c568950..ceafc357 100644 --- a/src/vpp/api/test_client.c +++ b/src/vpp/api/test_client.c @@ -1196,8 +1196,6 @@ ip6_set_link_local_address (test_main_t * tm) clib_memcpy (mp->address, &tmp[0], 8); clib_memcpy (&mp->address[8], &tmp[1], 8); - mp->address_length = 64; - mp->_vl_msg_id = ntohs (VL_API_SW_INTERFACE_IP6_SET_LINK_LOCAL_ADDRESS); vl_msg_api_send_shmem (tm->vl_input_queue, (u8 *) & mp); diff --git a/test/test_ip6.py b/test/test_ip6.py index e8b12f68..cd9c4b95 100644 --- a/test/test_ip6.py +++ b/test/test_ip6.py @@ -8,8 +8,18 @@ from vpp_sub_interface import VppSubInterface, VppDot1QSubint from scapy.packet import Raw from scapy.layers.l2 import Ether, Dot1Q -from scapy.layers.inet6 import IPv6, UDP +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 +from scapy.utils import inet_pton, inet_ntop + + +def mk_ll_addr(mac): + euid = in6_mactoifaceid(mac) + addr = "fe80::" + euid + return addr class TestIPv6(VppTestCase): @@ -76,6 +86,12 @@ class TestIPv6(VppTestCase): def tearDown(self): """Run standard test teardown and log ``show ip6 neighbors``.""" + for i in self.sub_interfaces: + i.unconfig_ip6() + i.ip6_disable() + i.admin_down() + i.remove_vpp_config() + super(TestIPv6, self).tearDown() if not self.vpp_dead: self.logger.info(self.vapi.cli("show ip6 neighbors")) @@ -206,6 +222,225 @@ class TestIPv6(VppTestCase): pkts = i.parent.get_capture() self.verify_capture(i, pkts) + def send_and_assert_no_replies(self, intf, pkts, remark): + intf.add_stream(pkts) + self.pg_enable_capture(self.pg_interfaces) + self.pg_start() + intf.assert_nothing_captured(remark=remark) + + def test_ns(self): + """ IPv6 Neighbour Soliciatation Exceptions + + Test sceanrio: + - Send an NS Sourced from an address not covered by the link sub-net + - Send an NS to an mcast address the router has not joined + - Send NS for a target address the router does not onn. + """ + + # + # An NS from a non link source address + # + nsma = in6_getnsma(inet_pton(socket.AF_INET6, self.pg0.local_ip6)) + d = inet_ntop(socket.AF_INET6, nsma) + + p = (Ether(dst=in6_getnsmac(nsma)) / + IPv6(dst=d, src="2002::2") / + ICMPv6ND_NS(tgt=self.pg0.local_ip6) / + ICMPv6NDOptSrcLLAddr(lladdr=self.pg0.remote_mac)) + pkts = [p] + + self.send_and_assert_no_replies(self.pg0, pkts, + "No response to NS source by address not on sub-net") + + # + # An NS for sent to a solicited mcast group the router is not a member of + # FAILS + # + if 0: + nsma = in6_getnsma(inet_pton(socket.AF_INET6, "fd::ffff")) + d = inet_ntop(socket.AF_INET6, nsma) + + p = (Ether(dst=in6_getnsmac(nsma)) / + IPv6(dst=d, src=self.pg0.remote_ip6) / + ICMPv6ND_NS(tgt=self.pg0.local_ip6) / + ICMPv6NDOptSrcLLAddr(lladdr=self.pg0.remote_mac)) + pkts = [p] + + self.send_and_assert_no_replies(self.pg0, pkts, + "No response to NS sent to unjoined mcast address") + + # + # An NS whose target address is one the router does not own + # + nsma = in6_getnsma(inet_pton(socket.AF_INET6, self.pg0.local_ip6)) + d = inet_ntop(socket.AF_INET6, nsma) + + p = (Ether(dst=in6_getnsmac(nsma)) / + IPv6(dst=d, src=self.pg0.remote_ip6) / + ICMPv6ND_NS(tgt="fd::ffff") / + ICMPv6NDOptSrcLLAddr(lladdr=self.pg0.remote_mac)) + pkts = [p] + + 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) + + self.assertEqual(len(rx), 1) + rx = rx[0] + + # 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)) + + # 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 test_rs(self): + """ IPv6 Router Soliciatation Exceptions + + Test sceanrio: + """ + + # + # 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 + # + self.pg0.ip6_ra_config(send_unicast=1) + + # + # An RS from a link source address + # - expect an RA in return + # + p = (Ether(dst=self.pg0.local_mac, src=self.pg0.remote_mac) / + IPv6(dst=self.pg0.local_ip6, src=self.pg0.remote_ip6) / + ICMPv6ND_RS()) + pkts = [p] + self.send_and_expect_ra(self.pg0, pkts, "Genuine RS") + + # + # For the next RS sent the RA should be rate limited + # + self.send_and_assert_no_replies(self.pg0, pkts, "RA rate limited") + + # + # When we reconfiure the IPv6 RA config, we reset the RA rate limiting, + # so we need to do this before each test below so as not to drop packets for + # rate limiting reasons. Test this works here. + # + self.pg0.ip6_ra_config(send_unicast=1) + self.send_and_expect_ra(self.pg0, pkts, "Rate limit reset RS") + + # + # An RS sent from a non-link local source + # + 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="2002::ffff") / + ICMPv6ND_RS()) + pkts = [p] + self.send_and_assert_no_replies(self.pg0, pkts, + "RS from non-link source") + + # + # Source an RS from a link local address + # + self.pg0.ip6_ra_config(send_unicast=1) + ll = mk_ll_addr(self.pg0.remote_mac) + p = (Ether(dst=self.pg0.local_mac, src=self.pg0.remote_mac) / + 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) + + # + # Source from the unspecified address ::. This happens when the RS is sent before + # the host has a configured address/sub-net, i.e. auto-config. + # Since the sender has no IP address, the reply comes back mcast - so the + # capture needs to not filter this. + # 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="::") / + 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 Soliciatation Exceptions + + Test sceanrio: + """ + + # + # 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") + + # + # An RS from a non link source address + # + 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") + if __name__ == '__main__': unittest.main(testRunner=VppTestRunner) diff --git a/test/vpp_interface.py b/test/vpp_interface.py index 6ccb92bb..856a5cc2 100644 --- a/test/vpp_interface.py +++ b/test/vpp_interface.py @@ -249,6 +249,12 @@ 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): + """Configure IPv6 RA suppress on the VPP interface.""" + self.test.vapi.ip6_sw_interface_ra_config(self.sw_if_index, + suppress, + send_unicast) + def admin_up(self): """Put interface ADMIN-UP.""" self.test.vapi.sw_interface_set_flags(self.sw_if_index, admin_up_down=1) @@ -257,6 +263,14 @@ class VppInterface(object): """Put interface ADMIN-down.""" self.test.vapi.sw_interface_set_flags(self.sw_if_index, admin_up_down=0) + def ip6_enable(self): + """IPv6 Enable interface""" + self.test.vapi.ip6_sw_interface_enable_disable(self.sw_if_index, enable=1) + + def ip6_disable(self): + """Put interface ADMIN-DOWN.""" + self.test.vapi.ip6_sw_interface_enable_disable(self.sw_if_index, enable=0) + def add_sub_if(self, sub_if): """Register a sub-interface with this interface. diff --git a/test/vpp_papi_provider.py b/test/vpp_papi_provider.py index db530d98..0e4c0cdb 100644 --- a/test/vpp_papi_provider.py +++ b/test/vpp_papi_provider.py @@ -219,6 +219,22 @@ class VppPapiProvider(object): return self.api(self.papi.sw_interface_ip6nd_ra_config, {'sw_if_index': sw_if_index}) + def ip6_sw_interface_ra_config(self, sw_if_index, + suppress, + send_unicast,): + return self.api(self.papi.sw_interface_ip6nd_ra_config, + {'sw_if_index': sw_if_index, + 'suppress' : suppress, + 'send_unicast' : send_unicast}) + + def ip6_sw_interface_enable_disable(self, sw_if_index, enable): + """ + Enable/Disable An interface for IPv6 + """ + return self.api(self.papi.sw_interface_ip6_enable_disable, + {'sw_if_index': sw_if_index, + 'enable': enable}) + def vxlan_add_del_tunnel( self, src_addr, diff --git a/test/vpp_pg_interface.py b/test/vpp_pg_interface.py index aef0052e..b5929a4b 100644 --- a/test/vpp_pg_interface.py +++ b/test/vpp_pg_interface.py @@ -10,13 +10,14 @@ from scapy.layers.inet6 import IPv6, ICMPv6ND_NS, ICMPv6ND_NA,\ ICMPv6NDOptSrcLLAddr, ICMPv6NDOptDstLLAddr, ICMPv6ND_RA, RouterAlert, \ IPv6ExtHdrHopByHop from util import ppp, ppc -from scapy.utils6 import in6_getnsma, in6_getnsmac +from scapy.utils6 import in6_getnsma, in6_getnsmac, in6_ismaddr from scapy.utils import inet_pton, inet_ntop def is_ipv6_misc(p): """ Is packet one of uninteresting IPv6 broadcasts? """ if p.haslayer(ICMPv6ND_RA): - return True + if in6_ismaddr(p[IPv6].dst): + return True if p.haslayer(IPv6ExtHdrHopByHop): for o in p[IPv6ExtHdrHopByHop].options: if isinstance(o, RouterAlert): -- cgit 1.2.3-korg From e546d3b0f739d35ec2e4702181d99ff4190e8b46 Mon Sep 17 00:00:00 2001 From: Jan Date: Thu, 8 Dec 2016 13:10:03 +0100 Subject: test: ip4 vrf instances multi-context test (CSIT-492) - add/delete IPv4 VRF instances and verify results by parsing output of ip_fib_dump API command and by traffic Change-Id: I61ed5013adca29afd00b942f65be7bf964f38d85 Signed-off-by: Jan Gelety --- test/test_ip4_vrf_multi_instance.py | 410 ++++++++++++++++++++++++++++++++++++ test/vpp_interface.py | 10 +- test/vpp_papi_provider.py | 48 +++++ test/vpp_pg_interface.py | 22 +- 4 files changed, 475 insertions(+), 15 deletions(-) create mode 100644 test/test_ip4_vrf_multi_instance.py (limited to 'test/vpp_interface.py') diff --git a/test/test_ip4_vrf_multi_instance.py b/test/test_ip4_vrf_multi_instance.py new file mode 100644 index 00000000..1449ef7d --- /dev/null +++ b/test/test_ip4_vrf_multi_instance.py @@ -0,0 +1,410 @@ +#!/usr/bin/env python +"""IP4 VRF Multi-instance Test Case HLD: + +**NOTES:** + - higher number of pg-ip4 interfaces causes problems => only 15 pg-ip4 \ + interfaces in 5 VRFs are tested + - jumbo packets in configuration with 15 pg-ip4 interfaces leads to \ + problems too + - Reset of FIB table / VRF does not remove routes from IP FIB (see Jira \ + ticket https://jira.fd.io/browse/VPP-560) so checks of reset VRF tables \ + are skipped in tests 2, 3 and 4 + +**config 1** + - add 15 pg-ip4 interfaces + - configure 5 hosts per pg-ip4 interface + - configure 4 VRFs + - add 3 pg-ip4 interfaces per VRF + +**test 1** + - send IP4 packets between all pg-ip4 interfaces in all VRF groups + +**verify 1** + - check VRF data by parsing output of ip_fib_dump API command + - all packets received correctly in case of pg-ip4 interfaces in VRF + - no packet received in case of pg-ip4 interfaces not in VRF + +**config 2** + - delete 2 VRFs + +**test 2** + - send IP4 packets between all pg-ip4 interfaces in all VRF groups + +**verify 2** + - check VRF data by parsing output of ip_fib_dump API command + - all packets received correctly in case of pg-ip4 interfaces in VRF + - no packet received in case of pg-ip4 interfaces not in VRF + +**config 3** + - add 1 of deleted VRFs and 1 new VRF + +**test 3** + - send IP4 packets between all pg-ip4 interfaces in all VRF groups + +**verify 3** + - check VRF data by parsing output of ip_fib_dump API command + - all packets received correctly in case of pg-ip4 interfaces in VRF + - no packet received in case of pg-ip4 interfaces not in VRF + +**config 4** + - delete all VRFs (i.e. no VRF except VRF=0 created) + +**test 4** + - send IP4 packets between all pg-ip4 interfaces in all VRF groups + +**verify 4** + - check VRF data by parsing output of ip_fib_dump API command + - all packets received correctly in case of pg-ip4 interfaces in VRF + - no packet received in case of pg-ip4 interfaces not in VRF +""" + +import unittest +import random + +from scapy.packet import Raw +from scapy.layers.l2 import Ether +from scapy.layers.inet import IP, UDP, ARP + +from framework import VppTestCase, VppTestRunner +from util import ppp + + +def is_ipv4_misc(p): + """ Is packet one of uninteresting IPv4 broadcasts? """ + if p.haslayer(ARP): + return True + return False + + +class TestIp4VrfMultiInst(VppTestCase): + """ IP4 VRF Multi-instance Test Case """ + + @classmethod + def setUpClass(cls): + """ + Perform standard class setup (defined by class method setUpClass in + class VppTestCase) before running the test case, set test case related + variables and configure VPP. + """ + super(TestIp4VrfMultiInst, cls).setUpClass() + + # Test variables + cls.hosts_per_pg = 5 + cls.nr_of_vrfs = 5 + cls.pg_ifs_per_vrf = 3 + + try: + # Create pg interfaces + cls.create_pg_interfaces(range(cls.nr_of_vrfs * cls.pg_ifs_per_vrf)) + + # Packet flows mapping pg0 -> pg1, pg2 etc. + cls.flows = dict() + for i in range(len(cls.pg_interfaces)): + multiplicand = i / cls.pg_ifs_per_vrf + pg_list = [ + cls.pg_interfaces[multiplicand * cls.pg_ifs_per_vrf + j] + for j in range(cls.pg_ifs_per_vrf) + if (multiplicand * cls.pg_ifs_per_vrf + j) != i] + cls.flows[cls.pg_interfaces[i]] = pg_list + + # Packet sizes - jumbo packet (9018 bytes) skipped + cls.pg_if_packet_sizes = [64, 512, 1518] + + # Set up all interfaces + for pg_if in cls.pg_interfaces: + pg_if.admin_up() + pg_if.generate_remote_hosts(cls.hosts_per_pg) + + # Create list of VRFs + cls.vrf_list = list() + + # Create list of deleted VRFs + cls.vrf_deleted_list = list() + + # Create list of pg_interfaces in VRFs + cls.pg_in_vrf = list() + + # Create list of pg_interfaces not in BDs + cls.pg_not_in_vrf = [pg_if for pg_if in cls.pg_interfaces] + + # Create mapping of pg_interfaces to VRF IDs + cls.pg_if_by_vrf_id = dict() + for i in range(cls.nr_of_vrfs): + vrf_id = i + 1 + pg_list = [ + cls.pg_interfaces[i * cls.pg_ifs_per_vrf + j] + for j in range(cls.pg_ifs_per_vrf)] + cls.pg_if_by_vrf_id[vrf_id] = pg_list + + except Exception: + super(TestIp4VrfMultiInst, cls).tearDownClass() + raise + + def setUp(self): + """ip_add_del_route + Clear trace and packet infos before running each test. + """ + super(TestIp4VrfMultiInst, self).setUp() + self.reset_packet_infos() + + def tearDown(self): + """ + Show various debug prints after each test. + """ + super(TestIp4VrfMultiInst, self).tearDown() + if not self.vpp_dead: + self.logger.info(self.vapi.ppcli("show ip fib")) + self.logger.info(self.vapi.ppcli("show ip arp")) + + def create_vrf_and_assign_interfaces(self, count, start=1): + """" + Create required number of FIB tables / VRFs, put 3 l2-pg interfaces + to every FIB table / VRF. + + :param int count: Number of FIB tables / VRFs to be created. + :param int start: Starting number of the FIB table / VRF ID. \ + (Default value = 1) + """ + + for i in range(count): + vrf_id = i + start + pg_if = self.pg_if_by_vrf_id[vrf_id][0] + dest_addr = pg_if.remote_hosts[0].ip4n + dest_addr_len = 24 + self.vapi.ip_add_del_route( + dest_addr, dest_addr_len, pg_if.local_ip4n, + table_id=vrf_id, create_vrf_if_needed=1, is_multipath=1) + self.logger.info("IPv4 VRF ID %d created" % vrf_id) + if vrf_id not in self.vrf_list: + self.vrf_list.append(vrf_id) + if vrf_id in self.vrf_deleted_list: + self.vrf_deleted_list.remove(vrf_id) + for j in range(self.pg_ifs_per_vrf): + pg_if = self.pg_if_by_vrf_id[vrf_id][j] + pg_if.set_table_ip4(vrf_id) + self.logger.info("pg-interface %s added to IPv4 VRF ID %d" + % (pg_if.name, vrf_id)) + if pg_if not in self.pg_in_vrf: + self.pg_in_vrf.append(pg_if) + if pg_if in self.pg_not_in_vrf: + self.pg_not_in_vrf.remove(pg_if) + pg_if.config_ip4() + pg_if.configure_ipv4_neighbors(vrf_id) + self.logger.debug(self.vapi.ppcli("show ip fib")) + self.logger.debug(self.vapi.ppcli("show ip arp")) + + def delete_vrf(self, vrf_id): + """" + Delete required FIB table / VRF. + + :param int vrf_id: The FIB table / VRF ID to be deleted. + """ + # self.vapi.reset_vrf(vrf_id, is_ipv6=0) + self.vapi.reset_fib(vrf_id, is_ipv6=0) + if vrf_id in self.vrf_list: + self.vrf_list.remove(vrf_id) + if vrf_id not in self.vrf_deleted_list: + self.vrf_deleted_list.append(vrf_id) + for j in range(self.pg_ifs_per_vrf): + pg_if = self.pg_if_by_vrf_id[vrf_id][j] + if pg_if in self.pg_in_vrf: + self.pg_in_vrf.remove(pg_if) + if pg_if not in self.pg_not_in_vrf: + self.pg_not_in_vrf.append(pg_if) + self.logger.info("IPv4 VRF ID %d reset" % vrf_id) + self.logger.debug(self.vapi.ppcli("show ip fib")) + self.logger.debug(self.vapi.ppcli("show ip arp")) + + def create_stream(self, src_if, packet_sizes): + """ + Create input packet stream for defined interface using hosts list. + + :param object src_if: Interface to create packet stream for. + :param list packet_sizes: List of required packet sizes. + :return: Stream of packets. + """ + pkts = [] + src_hosts = src_if.remote_hosts + for dst_if in self.flows[src_if]: + for dst_host in dst_if.remote_hosts: + src_host = random.choice(src_hosts) + pkt_info = self.create_packet_info(src_if, dst_if) + payload = self.info_to_payload(pkt_info) + p = (Ether(dst=src_if.local_mac, src=src_host.mac) / + IP(src=src_host.ip4, dst=dst_host.ip4) / + UDP(sport=1234, dport=1234) / + Raw(payload)) + pkt_info.data = p.copy() + size = random.choice(packet_sizes) + self.extend_packet(p, size) + pkts.append(p) + self.logger.debug("Input stream created for port %s. Length: %u pkt(s)" + % (src_if.name, len(pkts))) + return pkts + + def verify_capture(self, pg_if, capture): + """ + Verify captured input packet stream for defined interface. + + :param object pg_if: Interface to verify captured packet stream for. + :param list capture: Captured packet stream. + """ + last_info = dict() + for i in self.pg_interfaces: + last_info[i.sw_if_index] = None + dst_sw_if_index = pg_if.sw_if_index + for packet in capture: + try: + ip = packet[IP] + udp = packet[UDP] + payload_info = self.payload_to_info(str(packet[Raw])) + packet_index = payload_info.index + self.assertEqual(payload_info.dst, dst_sw_if_index) + self.logger.debug("Got packet on port %s: src=%u (id=%u)" % + (pg_if.name, payload_info.src, packet_index)) + next_info = self.get_next_packet_info_for_interface2( + payload_info.src, dst_sw_if_index, + last_info[payload_info.src]) + last_info[payload_info.src] = next_info + self.assertIsNotNone(next_info) + self.assertEqual(packet_index, next_info.index) + saved_packet = next_info.data + # Check standard fields + self.assertEqual(ip.src, saved_packet[IP].src) + self.assertEqual(ip.dst, saved_packet[IP].dst) + self.assertEqual(udp.sport, saved_packet[UDP].sport) + self.assertEqual(udp.dport, saved_packet[UDP].dport) + except: + self.logger.error(ppp("Unexpected or invalid packet:", packet)) + raise + for i in self.pg_interfaces: + remaining_packet = self.get_next_packet_info_for_interface2( + i, dst_sw_if_index, last_info[i.sw_if_index]) + self.assertIsNone( + remaining_packet, + "Port %u: Packet expected from source %u didn't arrive" % + (dst_sw_if_index, i.sw_if_index)) + + def verify_vrf(self, vrf_id): + """ + Check if the FIB table / VRF ID is configured. + + :param int vrf_id: The FIB table / VRF ID to be verified. + :return: 1 if the FIB table / VRF ID is configured, otherwise return 0. + """ + ip_fib_dump = self.vapi.ip_fib_dump() + vrf_count = 0 + for ip_fib_details in ip_fib_dump: + if ip_fib_details[2] == vrf_id: + vrf_count += 1 + if vrf_count == 0: + self.logger.info("IPv4 VRF ID %d is not configured" % vrf_id) + return 0 + else: + self.logger.info("IPv4 VRF ID %d is configured" % vrf_id) + return 1 + + def run_verify_test(self): + """ + Create packet streams for all configured l2-pg interfaces, send all + prepared packet streams and verify that: + - all packets received correctly on all pg-l2 interfaces assigned to + bridge domains + - no packet received on all pg-l2 interfaces not assigned to bridge + domains + + :raise RuntimeError: If no packet captured on l2-pg interface assigned + to the bridge domain or if any packet is captured on l2-pg interface + not assigned to the bridge domain. + """ + # Test + # Create incoming packet streams for packet-generator interfaces + for pg_if in self.pg_interfaces: + pkts = self.create_stream(pg_if, self.pg_if_packet_sizes) + pg_if.add_stream(pkts) + + # Enable packet capture and start packet sending + self.pg_enable_capture(self.pg_interfaces) + self.pg_start() + + # Verify + # Verify outgoing packet streams per packet-generator interface + for pg_if in self.pg_interfaces: + if pg_if in self.pg_in_vrf: + capture = pg_if.get_capture(remark="interface is in VRF") + self.verify_capture(pg_if, capture) + elif pg_if in self.pg_not_in_vrf: + pg_if.assert_nothing_captured(remark="interface is not in VRF", + filter_out_fn=is_ipv4_misc) + self.logger.debug("No capture for interface %s" % pg_if.name) + else: + raise Exception("Unknown interface: %s" % pg_if.name) + + def test_ip4_vrf_01(self): + """ IP4 VRF Multi-instance test 1 - create 5 BDs + """ + # Config 1 + # Create 4 VRFs + self.create_vrf_and_assign_interfaces(4) + + # Verify 1 + for vrf_id in self.vrf_list: + self.assertEqual(self.verify_vrf(vrf_id), 1) + + # Test 1 + self.run_verify_test() + + def test_ip4_vrf_02(self): + """ IP4 VRF Multi-instance test 2 - delete 2 VRFs + """ + # Config 2 + # Delete 2 VRFs + self.delete_vrf(1) + self.delete_vrf(2) + + # Verify 2 + # for vrf_id in self.vrf_deleted_list: + # self.assertEqual(self.verify_vrf(vrf_id), 0) + for vrf_id in self.vrf_list: + self.assertEqual(self.verify_vrf(vrf_id), 1) + + # Test 2 + self.run_verify_test() + + def test_ip4_vrf_03(self): + """ IP4 VRF Multi-instance 3 - add 2 VRFs + """ + # Config 3 + # Add 1 of deleted VRFs and 1 new VRF + self.create_vrf_and_assign_interfaces(1) + self.create_vrf_and_assign_interfaces(1, start=5) + + # Verify 3 + # for vrf_id in self.vrf_deleted_list: + # self.assertEqual(self.verify_vrf(vrf_id), 0) + for vrf_id in self.vrf_list: + self.assertEqual(self.verify_vrf(vrf_id), 1) + + # Test 3 + self.run_verify_test() + + def test_ip4_vrf_04(self): + """ IP4 VRF Multi-instance test 4 - delete 4 VRFs + """ + # Config 4 + # Delete all VRFs (i.e. no VRF except VRF=0 created) + for i in range(len(self.vrf_list)): + self.delete_vrf(self.vrf_list[0]) + + # Verify 4 + # for vrf_id in self.vrf_deleted_list: + # self.assertEqual(self.verify_vrf(vrf_id), 0) + for vrf_id in self.vrf_list: + self.assertEqual(self.verify_vrf(vrf_id), 1) + + # Test 4 + self.run_verify_test() + + +if __name__ == '__main__': + unittest.main(testRunner=VppTestRunner) diff --git a/test/vpp_interface.py b/test/vpp_interface.py index 856a5cc2..9d9712eb 100644 --- a/test/vpp_interface.py +++ b/test/vpp_interface.py @@ -195,12 +195,16 @@ class VppInterface(object): self.has_ip4_config = False self.has_ip4_config = False - def configure_ipv4_neighbors(self): - """For every remote host assign neighbor's MAC to IPv4 addresses.""" + def configure_ipv4_neighbors(self, vrf_id=0): + """For every remote host assign neighbor's MAC to IPv4 addresses. + + :param vrf_id: The FIB table / VRF ID. (Default value = 0) + """ for host in self._remote_hosts: macn = host.mac.replace(":", "").decode('hex') ipn = host.ip4n - self.test.vapi.ip_neighbor_add_del(self.sw_if_index, macn, ipn) + self.test.vapi.ip_neighbor_add_del( + self.sw_if_index, macn, ipn, vrf_id) def config_ip6(self): """Configure IPv6 address on the VPP interface.""" diff --git a/test/vpp_papi_provider.py b/test/vpp_papi_provider.py index 0e4c0cdb..26edd4f2 100644 --- a/test/vpp_papi_provider.py +++ b/test/vpp_papi_provider.py @@ -595,6 +595,54 @@ class VppPapiProvider(object): } ) + def reset_vrf(self, + vrf_id, + is_ipv6=0, + ): + """ Reset VRF (remove all routes etc.) request. + + :param int vrf_id: ID of the FIB table / VRF to reset. + :param int is_ipv6: 1 for IPv6 neighbor, 0 for IPv4. (Default = 0) + """ + + return self.api( + self.papi.reset_vrf, + {'vrf_id': vrf_id, + 'is_ipv6': is_ipv6, + } + ) + + def reset_fib(self, + vrf_id, + is_ipv6=0, + ): + """ Reset VRF (remove all routes etc.) request. + + :param int vrf_id: ID of the FIB table / VRF to reset. + :param int is_ipv6: 1 for IPv6 neighbor, 0 for IPv4. (Default = 0) + """ + + return self.api( + self.papi.reset_fib, + {'vrf_id': vrf_id, + 'is_ipv6': is_ipv6, + } + ) + + def ip_dump(self, + is_ipv6=0, + ): + """ Return IP dump. + + :param int is_ipv6: 1 for IPv6 neighbor, 0 for IPv4. (Default = 0) + """ + + return self.api( + self.papi.ip_dump, + {'is_ipv6': is_ipv6, + } + ) + def sw_interface_span_enable_disable( self, sw_if_index_from, sw_if_index_to, state=1): """ diff --git a/test/vpp_pg_interface.py b/test/vpp_pg_interface.py index b5929a4b..eeb9c1a5 100644 --- a/test/vpp_pg_interface.py +++ b/test/vpp_pg_interface.py @@ -145,7 +145,7 @@ class VppPGInterface(VppInterface): output = rdpcap(self.out_path) self.test.logger.debug("Capture has %s packets" % len(output.res)) except: - self.test.logger.debug("Exception in scapy.rdpcap(%s): %s" % + self.test.logger.debug("Exception in scapy.rdpcap (%s): %s" % (self.out_path, format_exc())) return None before = len(output.res) @@ -182,7 +182,7 @@ class VppPGInterface(VppInterface): if expected_count == 0: raise Exception( "Internal error, expected packet count for %s is 0!" % name) - self.test.logger.debug("Expecting to capture %s(%s) packets on %s" % ( + self.test.logger.debug("Expecting to capture %s (%s) packets on %s" % ( expected_count, based_on, name)) while remaining_time > 0: before = time.time() @@ -213,22 +213,20 @@ class VppPGInterface(VppInterface): try: capture = self.get_capture( 0, remark=remark, filter_out_fn=filter_out_fn) - if capture: - if len(capture.res) == 0: - # junk filtered out, we're good - return - self.test.logger.error( - ppc("Unexpected packets captured:", capture)) + if not capture: + # junk filtered out, we're good + return + self.test.logger.error( + ppc("Unexpected packets captured:", capture)) except: pass if remark: raise AssertionError( - "Non-empty capture file present for interface %s(%s)" % + "Non-empty capture file present for interface %s (%s)" % (self.name, remark)) else: - raise AssertionError( - "Non-empty capture file present for interface %s" % - self.name) + raise AssertionError("Capture file present for interface %s" % + self.name) def wait_for_capture_file(self, timeout=1): """ -- cgit 1.2.3-korg From da505f608e0919c45089dc80f9e3e16330a6551a Mon Sep 17 00:00:00 2001 From: Klement Sekera Date: Wed, 4 Jan 2017 12:58:53 +0100 Subject: make test: improve documentation and PEP8 compliance Change-Id: Ib4f0353aab6112fcc3c3d8f0bcbed5bc4b567b9b Signed-off-by: Klement Sekera --- test/Makefile | 4 +- test/doc/index.rst | 285 ++++++++++++++++++++++++++++++++++-- test/framework.py | 23 +-- test/hook.py | 4 +- test/template_bd.py | 4 +- test/test_classifier.py | 14 +- test/test_flowperpkt.py | 4 +- test/test_ip4.py | 17 ++- test/test_ip4_vrf_multi_instance.py | 11 +- test/test_ip6.py | 57 ++++---- test/test_l2_fib.py | 11 +- test/test_l2bd_multi_instance.py | 13 +- test/test_l2xc_multi_instance.py | 20 ++- test/test_snat.py | 20 ++- test/test_span.py | 11 +- test/vpp_interface.py | 14 +- test/vpp_ip_route.py | 52 ++++--- test/vpp_papi_provider.py | 12 +- test/vpp_pg_interface.py | 48 +++--- 19 files changed, 468 insertions(+), 156 deletions(-) (limited to 'test/vpp_interface.py') diff --git a/test/Makefile b/test/Makefile index 204afd38..543fe913 100644 --- a/test/Makefile +++ b/test/Makefile @@ -17,7 +17,7 @@ PAPI_INSTALL_DONE=$(VPP_PYTHON_PREFIX)/papi-install.done PAPI_INSTALL_FLAGS=$(PIP_INSTALL_DONE) $(PIP_PATCH_DONE) $(PAPI_INSTALL_DONE) $(PIP_INSTALL_DONE): - @virtualenv $(PYTHON_VENV_PATH) + @virtualenv $(PYTHON_VENV_PATH) -p python2.7 @bash -c "source $(PYTHON_VENV_PATH)/bin/activate && pip install $(PYTHON_DEPENDS)" @touch $@ @@ -54,7 +54,7 @@ wipe: reset @rm -f $(PAPI_INSTALL_FLAGS) doc: verify-python-path - @virtualenv $(PYTHON_VENV_PATH) + @virtualenv $(PYTHON_VENV_PATH) -p python2.7 @bash -c "source $(PYTHON_VENV_PATH)/bin/activate && pip install $(PYTHON_DEPENDS) sphinx" @bash -c "source $(PYTHON_VENV_PATH)/bin/activate && make -C doc WS_ROOT=$(WS_ROOT) BR=$(BR) NO_VPP_PAPI=1 html" diff --git a/test/doc/index.rst b/test/doc/index.rst index 8cbe961f..f51d5058 100644 --- a/test/doc/index.rst +++ b/test/doc/index.rst @@ -4,6 +4,7 @@ .. _SkipTest: https://docs.python.org/2/library/unittest.html#unittest.SkipTest .. _virtualenv: http://docs.python-guide.org/en/latest/dev/virtualenvs/ .. _scapy: http://www.secdev.org/projects/scapy/ +.. _logging: https://docs.python.org/2/library/logging.html .. |vtf| replace:: VPP Test Framework @@ -39,7 +40,7 @@ Function flow when running a test case is: 3. *test_*: This is the guts of the test case. It should execute the test scenario and use the various assert functions from the unittest framework to check - necessary. + necessary. Multiple test_ methods can exist in a test case. 4. `tearDown `: The tearDown function is called after each test function with the purpose of doing partial cleanup. @@ -47,16 +48,56 @@ Function flow when running a test case is: Method called once after running all of the test functions to perform the final cleanup. +Logging +####### + +Each test case has a logger automatically created for it, stored in +'logger' property, based on logging_. Use the logger's standard methods +debug(), info(), error(), ... to emit log messages to the logger. + +All the log messages go always into a log file in temporary directory +(see below). + +To control the messages printed to console, specify the V= parameter. + +.. code-block:: shell + + make test # minimum verbosity + make test V=1 # moderate verbosity + make test V=2 # maximum verbosity + Test temporary directory and VPP life cycle ########################################### Test separation is achieved by separating the test files and vpp instances. Each test creates a temporary directory and it's name is used to create a shared memory prefix which is used to run a VPP instance. +The temporary directory name contains the testcase class name for easy +reference, so for testcase named 'TestVxlan' the directory could be named +e.g. vpp-unittest-TestVxlan-UNUP3j. This way, there is no conflict between any other VPP instances running on the box and the test VPP. Any temporary files created by the test case are stored in this temporary test directory. +The test temporary directory holds the following interesting files: + +* log.txt - this contains the logger output on max verbosity +* pg*_in.pcap - last injected packet stream into VPP, named after the interface, + so for pg0, the file will be named pg0_in.pcap +* pg*_out.pcap - last capture file created by VPP for interface, similarly, + named after the interface, so for e.g. pg1, the file will be named + pg1_out.pcap +* history files - whenever the capture is restarted or a new stream is added, + the existing files are rotated and renamed, soo all the pcap files + are always saved for later debugging if needed +* core - if vpp dumps a core, it'll be stored in the temporary directory +* vpp_stdout.txt - file containing output which vpp printed to stdout +* vpp_stderr.txt - file containing output which vpp printed to stderr + +*NOTE*: existing temporary directories named vpp-unittest-* are automatically +removed when invoking 'make test*' or 'make retest*' to keep the temporary +directory clean. + Virtual environment ################### @@ -82,6 +123,37 @@ thus: So e.g. `remote_mac ` address is the MAC address assigned to the virtual host connected to the VPP. +Automatically generated addresses +################################# + +To send packets, one needs to typically provide some addresses, otherwise +the packets will be dropped. The interface objects in |vtf| automatically +provide addresses based on (typically) their indexes, which ensures +there are no conflicts and eases debugging by making the addressing scheme +consistent. + +The developer of a test case typically doesn't need to work with the actual +numbers, rather using the properties of the objects. The addresses typically +come in two flavors: '
' and '
n' - note the 'n' suffix. +The former address is a Python string, while the latter is translated using +socket.inet_pton to raw format in network byte order - this format is suitable +for passing as an argument to VPP APIs. + +e.g. for the IPv4 address assigned to the VPP interface: + +* local_ip4 - Local IPv4 address on VPP interface (string) +* local_ip4n - Local IPv4 address - raw, suitable as API parameter. + +These addresses need to be configured in VPP to be usable using e.g. +`config_ip4` API. Please see the documentation to `VppInterface` for more +details. + +By default, there is one remote address of each kind created for L3: +remote_ip4 and remote_ip6. If the test needs more addresses, because it's +simulating more remote hosts, they can be generated using +`generate_remote_hosts` API and the entries for them inserted into the ARP +table using `configure_ipv4_neighbors` API. + Packet flow in the |vtf| ######################## @@ -93,6 +165,10 @@ using packet-generator interfaces, represented by the `VppPGInterface` class. Packets are written into a temporary .pcap file, which is then read by the VPP and the packets are injected into the VPP world. +To add a list of packets to an interface, call the `add_stream` method on that +interface. Once everything is prepared, call `pg_start` method to start +the packet generator on the VPP side. + VPP -> test framework ~~~~~~~~~~~~~~~~~~~~~ @@ -100,6 +176,72 @@ Similarly, VPP doesn't send any packets to |vtf| directly. Instead, packet capture feature is used to capture and write traffic to a temporary .pcap file, which is then read and analyzed by the |vtf|. +The following APIs are available to the test case for reading pcap files. + +* `get_capture`: this API is suitable for bulk & batch style of test, where + a list of packets is prepared & sent, then the received packets are read + and verified. The API needs the number of packets which are expected to + be captured (ignoring filtered packets - see below) to know when the pcap + file is completely written by the VPP. If using packet infos for verifying + packets, then the counts of the packet infos can be automatically used + by `get_capture` to get the proper count (in this case the default value + None can be supplied as expected_count or ommitted altogether). +* `wait_for_packet`: this API is suitable for interactive style of test, + e.g. when doing session management, three-way handsakes, etc. This API waits + for and returns a single packet, keeping the capture file in place + and remembering context. Repeated invocations return following packets + (or raise Exception if timeout is reached) from the same capture file + (= packets arriving on the same interface). + +*NOTE*: it is not recommended to mix these APIs unless you understand how they +work internally. None of these APIs rotate the pcap capture file, so calling +e.g. `get_capture` after `wait_for_packet` will return already read packets. +It is safe to switch from one API to another after calling `enable_capture` +as that API rotates the capture file. + +Automatic filtering of packets: +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +Both APIs (`get_capture` and `wait_for_packet`) by default filter the packet +capture, removing known uninteresting packets from it - these are IPv6 Router +Advertisments and IPv6 Router Alerts. These packets are unsolicitated +and from the point of |vtf| are random. If a test wants to receive these +packets, it should specify either None or a custom filtering function +as the value to the 'filter_out_fn' argument. + +Common API flow for sending/receiving packets: +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +We will describe a simple scenario, where packets are sent from pg0 to pg1 +interface, assuming that the interfaces were created using +`create_pg_interfaces` API. + +1. Create a list of packets for pg0:: + + packet_count = 10 + packets = create_packets(src=self.pg0, dst=self.pg1, + count=packet_count) + +2. Add that list of packets to the source interface:: + + self.pg0.add_stream(packets) + +3. Enable capture on the destination interface:: + + self.pg1.enable_capture() + +4. Start the packet generator:: + + self.pg_start() + +5. Wait for capture file to appear and read it:: + + capture = self.pg1.get_capture(expected_count=packet_count) + +6. Verify packets match sent packets:: + + self.verify_capture(send=packets, captured=capture) + Test framework objects ###################### @@ -113,8 +255,8 @@ common tasks easily in the test cases. * `VppSubInterface`: VPP sub-interface abstract class, containing common functionality for e.g. `VppDot1QSubint` and `VppDot1ADSubint` classes -How VPP API/CLI is called -######################### +How VPP APIs/CLIs are called +############################ Vpp provides python bindings in a python module called vpp-papi, which the test framework installs in the virtual environment. A shim layer represented by @@ -137,19 +279,144 @@ purposes: more readable. E.g. ip_add_del_route API takes ~25 parameters, of which in the common case, only 3 are needed. +Utility methods +############### + +Some interesting utility methods are: + +* `ppp`: 'Pretty Print Packet' - returns a string containing the same output + as Scapy's packet.show() would print +* `ppc`: 'Pretty Print Capture' - returns a string containing printout of + a capture (with configurable limit on the number of packets printed from it) + using `ppp` + +*NOTE*: Do not use Scapy's packet.show() in the tests, because it prints +the output to stdout. All output should go to the logger associated with +the test case. + Example: how to add a new test ############################## -In this example, we will describe how to add a new test case which tests VPP... +In this example, we will describe how to add a new test case which tests +basic IPv4 forwarding. + +1. Add a new file called test_ip4_fwd.py in the test directory, starting + with a few imports:: + + from framework import VppTestCase + from scapy.layers.l2 import Ether + from scapy.packet import Raw + from scapy.layers.inet import IP, UDP + from random import randint + +2. Create a class inherited from the VppTestCase:: + + class IP4FwdTestCase(VppTestCase): + """ IPv4 simple forwarding test case """ + +2. Add a setUpClass function containing the setup needed for our test to run:: + + @classmethod + def setUpClass(self): + super(IP4FwdTestCase, self).setUpClass() + self.create_pg_interfaces(range(2)) # create pg0 and pg1 + for i in self.pg_interfaces: + i.admin_up() # put the interface up + i.config_ip4() # configure IPv4 address on the interface + i.resolve_arp() # resolve ARP, so that we know VPP MAC + +3. Create a helper method to create the packets to send:: + + def create_stream(self, src_if, dst_if, count): + packets = [] + for i in range(count): + # create packet info stored in the test case instance + info = self.create_packet_info(src_if, dst_if) + # convert the info into packet payload + payload = self.info_to_payload(info) + # create the packet itself + p = (Ether(dst=src_if.local_mac, src=src_if.remote_mac) / + IP(src=src_if.remote_ip4, dst=dst_if.remote_ip4) / + UDP(sport=randint(1000, 2000), dport=5678) / + Raw(payload)) + # store a copy of the packet in the packet info + info.data = p.copy() + # append the packet to the list + packets.append(p) + + # return the created packet list + return packets + +4. Create a helper method to verify the capture:: + + def verify_capture(self, src_if, dst_if, capture): + packet_info = None + for packet in capture: + try: + ip = packet[IP] + udp = packet[UDP] + # convert the payload to packet info object + payload_info = self.payload_to_info(str(packet[Raw])) + # make sure the indexes match + self.assert_equal(payload_info.src, src_if.sw_if_index, + "source sw_if_index") + self.assert_equal(payload_info.dst, dst_if.sw_if_index, + "destination sw_if_index") + packet_info = self.get_next_packet_info_for_interface2( + src_if.sw_if_index, + dst_if.sw_if_index, + packet_info) + # make sure we didn't run out of saved packets + self.assertIsNotNone(packet_info) + self.assert_equal(payload_info.index, packet_info.index, + "packet info index") + saved_packet = packet_info.data # fetch the saved packet + # assert the values match + self.assert_equal(ip.src, saved_packet[IP].src, + "IP source address") + # ... more assertions here + self.assert_equal(udp.sport, saved_packet[UDP].sport, + "UDP source port") + except: + self.logger.error(ppp("Unexpected or invalid packet:", + packet)) + raise + remaining_packet = self.get_next_packet_info_for_interface2( + src_if.sw_if_index, + dst_if.sw_if_index, + packet_info) + self.assertIsNone(remaining_packet, + "Interface %s: Packet expected from interface " + "%s didn't arrive" % (dst_if.name, src_if.name)) + +5. Add the test code to test_basic function:: + + def test_basic(self): + count = 10 + # create the packet stream + packets = self.create_stream(self.pg0, self.pg1, count) + # add the stream to the source interface + self.pg0.add_stream(packets) + # enable capture on both interfaces + self.pg0.enable_capture() + self.pg1.enable_capture() + # start the packet generator + self.pg_start() + # get capture - the proper count of packets was saved by + # create_packet_info() based on dst_if parameter + capture = self.pg1.get_capture() + # assert nothing captured on pg0 (always do this last, so that + # some time has already passed since pg_start()) + self.pg0.assert_nothing_captured() + # verify capture + self.verify_capture(self.pg0, self.pg1, capture) + +6. Run the test by issuing 'make test'. -1. Add a new file called... -2. Add a setUpClass function containing... -3. Add the test code in test... -4. Run the test... |vtf| module documentation ########################## - + .. toctree:: :maxdepth: 2 :glob: diff --git a/test/framework.py b/test/framework.py index 896a1e0d..b2c6b9e4 100644 --- a/test/framework.py +++ b/test/framework.py @@ -117,7 +117,8 @@ class VppTestCase(unittest.TestCase): debug_cli = "" if cls.step or cls.debug_gdb or cls.debug_gdbserver: debug_cli = "cli-listen localhost:5002" - cls.vpp_cmdline = [cls.vpp_bin, "unix", "{", "nodaemon", debug_cli, "}", + cls.vpp_cmdline = [cls.vpp_bin, + "unix", "{", "nodaemon", debug_cli, "}", "api-segment", "{", "prefix", cls.shm_prefix, "}"] if cls.plugin_path is not None: cls.vpp_cmdline.extend(["plugin_path", cls.plugin_path]) @@ -246,8 +247,8 @@ class VppTestCase(unittest.TestCase): print(double_line_delim) print("VPP or GDB server is still running") print(single_line_delim) - raw_input("When done debugging, press ENTER to kill the process" - " and finish running the testcase...") + raw_input("When done debugging, press ENTER to kill the " + "process and finish running the testcase...") if hasattr(cls, 'vpp'): if hasattr(cls, 'vapi'): @@ -563,10 +564,10 @@ class VppTestResult(unittest.TestResult): def __init__(self, stream, descriptions, verbosity): """ - :param stream File descriptor to store where to report test results. Set - to the standard error stream by default. - :param descriptions Boolean variable to store information if to use test - case descriptions. + :param stream File descriptor to store where to report test results. + Set to the standard error stream by default. + :param descriptions Boolean variable to store information if to use + test case descriptions. :param verbosity Integer variable to store required verbosity level. """ unittest.TestResult.__init__(self, stream, descriptions, verbosity) @@ -664,12 +665,12 @@ class VppTestResult(unittest.TestResult): unittest.TestResult.stopTest(self, test) if self.verbosity > 0: self.stream.writeln(single_line_delim) - self.stream.writeln("%-60s%s" % - (self.getDescription(test), self.result_string)) + self.stream.writeln("%-60s%s" % (self.getDescription(test), + self.result_string)) self.stream.writeln(single_line_delim) else: - self.stream.writeln("%-60s%s" % - (self.getDescription(test), self.result_string)) + self.stream.writeln("%-60s%s" % (self.getDescription(test), + self.result_string)) def printErrors(self): """ diff --git a/test/hook.py b/test/hook.py index f3e5f880..247704ec 100644 --- a/test/hook.py +++ b/test/hook.py @@ -105,8 +105,8 @@ class PollHook(Hook): s = signaldict[abs(self.testcase.vpp.returncode)] else: s = "unknown" - msg = "VPP subprocess died unexpectedly with returncode %d [%s]" % ( - self.testcase.vpp.returncode, s) + msg = "VPP subprocess died unexpectedly with returncode %d [%s]" %\ + (self.testcase.vpp.returncode, s) self.logger.critical(msg) core_path = self.testcase.tempdir + '/core' if os.path.isfile(core_path): diff --git a/test/template_bd.py b/test/template_bd.py index 91d5dd71..ae171351 100644 --- a/test/template_bd.py +++ b/test/template_bd.py @@ -75,8 +75,8 @@ class BridgeDomain(object): self.pg_start() - # Pick first received frame and check if it's the - # non-encapsulated frame + # Pick first received frame and check if it's the non-encapsulated + # frame out = self.pg1.get_capture(1) pkt = out[0] self.assert_eq_pkts(pkt, self.frame_request) diff --git a/test/test_classifier.py b/test/test_classifier.py index 302430f8..faa107dc 100644 --- a/test/test_classifier.py +++ b/test/test_classifier.py @@ -114,8 +114,9 @@ class TestClassifier(VppTestCase): payload_info = self.payload_to_info(str(packet[Raw])) packet_index = payload_info.index self.assertEqual(payload_info.dst, dst_sw_if_index) - self.logger.debug("Got packet on port %s: src=%u (id=%u)" % - (dst_if.name, payload_info.src, packet_index)) + self.logger.debug( + "Got packet on port %s: src=%u (id=%u)" % + (dst_if.name, payload_info.src, packet_index)) next_info = self.get_next_packet_info_for_interface2( payload_info.src, dst_sw_if_index, last_info[payload_info.src]) @@ -296,8 +297,9 @@ class TestClassifier(VppTestCase): pkts = self.create_stream(self.pg0, self.pg2, self.pg_if_packet_sizes) self.pg0.add_stream(pkts) - self.create_classify_table( - 'mac', self.build_mac_mask(src_mac='ffffffffffff'), data_offset=-14) + self.create_classify_table('mac', + self.build_mac_mask(src_mac='ffffffffffff'), + data_offset=-14) self.create_classify_session( self.pg0, self.acl_tbl_idx.get('mac'), self.build_mac_match(src_mac=self.pg0.remote_mac)) @@ -326,7 +328,9 @@ class TestClassifier(VppTestCase): pkts = self.create_stream(self.pg0, self.pg3, self.pg_if_packet_sizes) self.pg0.add_stream(pkts) - self.create_classify_table('pbr', self.build_ip_mask(src_ip='ffffffff')) + self.create_classify_table( + 'pbr', self.build_ip_mask( + src_ip='ffffffff')) pbr_option = 1 self.create_classify_session( self.pg0, self.acl_tbl_idx.get('pbr'), diff --git a/test/test_flowperpkt.py b/test/test_flowperpkt.py index 29b3353b..f16bfb7e 100644 --- a/test/test_flowperpkt.py +++ b/test/test_flowperpkt.py @@ -57,8 +57,8 @@ class TestFlowperpkt(VppTestCase): if len(payload) * 2 != len(masked_expected_data): return False - # iterate over pairs: raw byte from payload and ASCII code for that byte - # from masked payload (or XX if masked) + # iterate over pairs: raw byte from payload and ASCII code for that + # byte from masked payload (or XX if masked) for i in range(len(payload)): p = payload[i] m = masked_expected_data[2 * i:2 * i + 2] diff --git a/test/test_ip4.py b/test/test_ip4.py index df93533d..9bd9a458 100644 --- a/test/test_ip4.py +++ b/test/test_ip4.py @@ -148,8 +148,9 @@ class TestIPv4(VppTestCase): payload_info = self.payload_to_info(str(packet[Raw])) packet_index = payload_info.index self.assertEqual(payload_info.dst, dst_sw_if_index) - self.logger.debug("Got packet on port %s: src=%u (id=%u)" % - (dst_if.name, payload_info.src, packet_index)) + self.logger.debug( + "Got packet on port %s: src=%u (id=%u)" % + (dst_if.name, payload_info.src, packet_index)) next_info = self.get_next_packet_info_for_interface2( payload_info.src, dst_sw_if_index, last_info[payload_info.src]) @@ -209,7 +210,7 @@ class TestIPv4FibCrud(VppTestCase): - add new 1k, - del 1.5k - ..note:: Python API is to slow to add many routes, needs C code replacement. + ..note:: Python API is too slow to add many routes, needs replacement. """ def config_fib_many_to_one(self, start_dest_addr, next_hop_addr, count): @@ -221,8 +222,9 @@ class TestIPv4FibCrud(VppTestCase): :return list: added ips with 32 prefix """ added_ips = [] - dest_addr = int( - socket.inet_pton(socket.AF_INET, start_dest_addr).encode('hex'), 16) + dest_addr = int(socket.inet_pton(socket.AF_INET, + start_dest_addr).encode('hex'), + 16) dest_addr_len = 32 n_next_hop_addr = socket.inet_pton(socket.AF_INET, next_hop_addr) for _ in range(count): @@ -236,8 +238,9 @@ class TestIPv4FibCrud(VppTestCase): def unconfig_fib_many_to_one(self, start_dest_addr, next_hop_addr, count): removed_ips = [] - dest_addr = int( - socket.inet_pton(socket.AF_INET, start_dest_addr).encode('hex'), 16) + dest_addr = int(socket.inet_pton(socket.AF_INET, + start_dest_addr).encode('hex'), + 16) dest_addr_len = 32 n_next_hop_addr = socket.inet_pton(socket.AF_INET, next_hop_addr) for _ in range(count): diff --git a/test/test_ip4_vrf_multi_instance.py b/test/test_ip4_vrf_multi_instance.py index 1449ef7d..b4279194 100644 --- a/test/test_ip4_vrf_multi_instance.py +++ b/test/test_ip4_vrf_multi_instance.py @@ -95,7 +95,8 @@ class TestIp4VrfMultiInst(VppTestCase): try: # Create pg interfaces - cls.create_pg_interfaces(range(cls.nr_of_vrfs * cls.pg_ifs_per_vrf)) + cls.create_pg_interfaces( + range(cls.nr_of_vrfs * cls.pg_ifs_per_vrf)) # Packet flows mapping pg0 -> pg1, pg2 etc. cls.flows = dict() @@ -308,14 +309,14 @@ class TestIp4VrfMultiInst(VppTestCase): """ Create packet streams for all configured l2-pg interfaces, send all prepared packet streams and verify that: - - all packets received correctly on all pg-l2 interfaces assigned to - bridge domains + - all packets received correctly on all pg-l2 interfaces assigned + to bridge domains - no packet received on all pg-l2 interfaces not assigned to bridge domains :raise RuntimeError: If no packet captured on l2-pg interface assigned - to the bridge domain or if any packet is captured on l2-pg interface - not assigned to the bridge domain. + to the bridge domain or if any packet is captured on l2-pg + interface not assigned to the bridge domain. """ # Test # Create incoming packet streams for packet-generator interfaces diff --git a/test/test_ip6.py b/test/test_ip6.py index cd9c4b95..ea669b70 100644 --- a/test/test_ip6.py +++ b/test/test_ip6.py @@ -8,8 +8,8 @@ from vpp_sub_interface import VppSubInterface, VppDot1QSubint from scapy.packet import Raw from scapy.layers.l2 import Ether, Dot1Q -from scapy.layers.inet6 import IPv6, UDP, ICMPv6ND_NS, ICMPv6ND_RS, ICMPv6ND_RA, \ - ICMPv6NDOptSrcLLAddr, getmacbyip6, ICMPv6MRD_Solicitation +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 @@ -172,8 +172,9 @@ class TestIPv6(VppTestCase): payload_info = self.payload_to_info(str(packet[Raw])) packet_index = payload_info.index self.assertEqual(payload_info.dst, dst_sw_if_index) - self.logger.debug("Got packet on port %s: src=%u (id=%u)" % - (dst_if.name, payload_info.src, packet_index)) + self.logger.debug( + "Got packet on port %s: src=%u (id=%u)" % + (dst_if.name, payload_info.src, packet_index)) next_info = self.get_next_packet_info_for_interface2( payload_info.src, dst_sw_if_index, last_info[payload_info.src]) @@ -229,9 +230,9 @@ class TestIPv6(VppTestCase): intf.assert_nothing_captured(remark=remark) def test_ns(self): - """ IPv6 Neighbour Soliciatation Exceptions + """ IPv6 Neighbour Solicitation Exceptions - Test sceanrio: + Test scenario: - Send an NS Sourced from an address not covered by the link sub-net - Send an NS to an mcast address the router has not joined - Send NS for a target address the router does not onn. @@ -249,12 +250,13 @@ class TestIPv6(VppTestCase): ICMPv6NDOptSrcLLAddr(lladdr=self.pg0.remote_mac)) pkts = [p] - self.send_and_assert_no_replies(self.pg0, pkts, - "No response to NS source by address not on sub-net") + self.send_and_assert_no_replies( + self.pg0, pkts, + "No response to NS source by address not on sub-net") # - # An NS for sent to a solicited mcast group the router is not a member of - # FAILS + # An NS for sent to a solicited mcast group the router is + # not a member of FAILS # if 0: nsma = in6_getnsma(inet_pton(socket.AF_INET6, "fd::ffff")) @@ -266,8 +268,9 @@ class TestIPv6(VppTestCase): ICMPv6NDOptSrcLLAddr(lladdr=self.pg0.remote_mac)) pkts = [p] - self.send_and_assert_no_replies(self.pg0, pkts, - "No response to NS sent to unjoined mcast address") + self.send_and_assert_no_replies( + self.pg0, pkts, + "No response to NS sent to unjoined mcast address") # # An NS whose target address is one the router does not own @@ -307,15 +310,15 @@ class TestIPv6(VppTestCase): in6_ptop(mk_ll_addr(intf.local_mac))) def test_rs(self): - """ IPv6 Router Soliciatation Exceptions + """ IPv6 Router Solicitation Exceptions - Test sceanrio: + Test scenario: """ # - # 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 + # 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 # self.pg0.ip6_ra_config(send_unicast=1) @@ -336,8 +339,8 @@ class TestIPv6(VppTestCase): # # When we reconfiure the IPv6 RA config, we reset the RA rate limiting, - # so we need to do this before each test below so as not to drop packets for - # rate limiting reasons. Test this works here. + # so we need to do this before each test below so as not to drop + # packets for rate limiting reasons. Test this works here. # self.pg0.ip6_ra_config(send_unicast=1) self.send_and_expect_ra(self.pg0, pkts, "Rate limit reset RS") @@ -366,12 +369,12 @@ class TestIPv6(VppTestCase): self.pg0, pkts, "RS sourced from link-local", src_ip=ll) # - # Source from the unspecified address ::. This happens when the RS is sent before - # the host has a configured address/sub-net, i.e. auto-config. - # Since the sender has no IP address, the reply comes back mcast - so the - # capture needs to not filter this. - # If we happen to pick up the periodic RA at this point then so be it, it's not - # an error. + # Source from the unspecified address ::. This happens when the RS + # is sent before the host has a configured address/sub-net, + # i.e. auto-config. Since the sender has no IP address, the reply + # comes back mcast - so the capture needs to not filter this. + # 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) / @@ -399,9 +402,9 @@ class TestIPv6(VppTestCase): @unittest.skip("Unsupported") def test_mrs(self): - """ IPv6 Multicast Router Soliciatation Exceptions + """ IPv6 Multicast Router Solicitation Exceptions - Test sceanrio: + Test scenario: """ # diff --git a/test/test_l2_fib.py b/test/test_l2_fib.py index d4ef3f4a..3d5d2129 100644 --- a/test/test_l2_fib.py +++ b/test/test_l2_fib.py @@ -106,7 +106,8 @@ class TestL2fib(VppTestCase): # Create BD with MAC learning and unknown unicast flooding disabled # and put interfaces to this BD - cls.vapi.bridge_domain_add_del(bd_id=cls.bd_id, uu_flood=0, learn=0) + cls.vapi.bridge_domain_add_del( + bd_id=cls.bd_id, uu_flood=0, learn=0) for pg_if in cls.pg_interfaces: cls.vapi.sw_interface_set_l2_bridge(pg_if.sw_if_index, bd_id=cls.bd_id) @@ -180,8 +181,8 @@ class TestL2fib(VppTestCase): end_nr = start + count / n_int for j in range(start, end_nr): host = self.hosts_by_pg_idx[pg_if.sw_if_index][j] - self.vapi.l2fib_add_del(host.mac, self.bd_id, pg_if.sw_if_index, - static_mac=1) + self.vapi.l2fib_add_del( + host.mac, self.bd_id, pg_if.sw_if_index, static_mac=1) counter += 1 percentage = counter / count * 100 if percentage > percent: @@ -202,8 +203,8 @@ class TestL2fib(VppTestCase): for pg_if in self.pg_interfaces: for j in range(count / n_int): host = self.hosts_by_pg_idx[pg_if.sw_if_index][0] - self.vapi.l2fib_add_del(host.mac, self.bd_id, pg_if.sw_if_index, - is_add=0) + self.vapi.l2fib_add_del( + host.mac, self.bd_id, pg_if.sw_if_index, is_add=0) self.deleted_hosts_by_pg_idx[pg_if.sw_if_index].append(host) del self.hosts_by_pg_idx[pg_if.sw_if_index][0] counter += 1 diff --git a/test/test_l2bd_multi_instance.py b/test/test_l2bd_multi_instance.py index a1226222..e24a8613 100644 --- a/test/test_l2bd_multi_instance.py +++ b/test/test_l2bd_multi_instance.py @@ -95,10 +95,10 @@ class TestL2bdMultiInst(VppTestCase): for i in range(0, len(cls.pg_interfaces), 3): cls.flows[cls.pg_interfaces[i]] = [cls.pg_interfaces[i + 1], cls.pg_interfaces[i + 2]] - cls.flows[cls.pg_interfaces[i + 1]] = [cls.pg_interfaces[i], - cls.pg_interfaces[i + 2]] - cls.flows[cls.pg_interfaces[i + 2]] = [cls.pg_interfaces[i], - cls.pg_interfaces[i + 1]] + cls.flows[cls.pg_interfaces[i + 1]] = \ + [cls.pg_interfaces[i], cls.pg_interfaces[i + 2]] + cls.flows[cls.pg_interfaces[i + 2]] = \ + [cls.pg_interfaces[i], cls.pg_interfaces[i + 1]] # Mapping between packet-generator index and lists of test hosts cls.hosts_by_pg_idx = dict() @@ -172,8 +172,9 @@ class TestL2bdMultiInst(VppTestCase): def create_bd_and_mac_learn(self, count, start=1): """ - Create required number of bridge domains with MAC learning enabled, put - 3 l2-pg interfaces to every bridge domain and send MAC learning packets. + Create required number of bridge domains with MAC learning enabled, + put 3 l2-pg interfaces to every bridge domain and send MAC learning + packets. :param int count: Number of bridge domains to be created. :param int start: Starting number of the bridge domain ID. diff --git a/test/test_l2xc_multi_instance.py b/test/test_l2xc_multi_instance.py index 6c28cebb..bb26f959 100644 --- a/test/test_l2xc_multi_instance.py +++ b/test/test_l2xc_multi_instance.py @@ -2,10 +2,10 @@ """L2XC Multi-instance Test Case HLD: **NOTES:** - - higher number (more than 15) of pg-l2 interfaces causes problems => only \ - 14 pg-l2 interfaces and 10 cross-connects are tested - - jumbo packets in configuration with 14 l2-pg interfaces leads to \ - problems too + - higher number (more than 15) of pg-l2 interfaces causes problems => only + 14 pg-l2 interfaces and 10 cross-connects are tested + - jumbo packets in configuration with 14 l2-pg interfaces leads to + problems too **config 1** - add 14 pg-l2 interfaces @@ -15,7 +15,8 @@ - send L2 MAC frames between all pairs of pg-l2 interfaces **verify 1** - - all packets received correctly in case of cross-connected l2-pg interfaces + - all packets received correctly in case of cross-connected l2-pg + interfaces - no packet received in case of not cross-connected l2-pg interfaces **config 2** @@ -25,7 +26,8 @@ - send L2 MAC frames between all pairs of pg-l2 interfaces **verify 2** - - all packets received correctly in case of cross-connected l2-pg interfaces + - all packets received correctly in case of cross-connected l2-pg + interfaces - no packet received in case of not cross-connected l2-pg interfaces **config 3** @@ -35,7 +37,8 @@ - send L2 MAC frames between all pairs of pg-l2 interfaces **verify 3** - - all packets received correctly in case of cross-connected l2-pg interfaces + - all packets received correctly in case of cross-connected l2-pg + interfaces - no packet received in case of not cross-connected l2-pg interfaces **config 4** @@ -79,7 +82,8 @@ class TestL2xcMultiInst(VppTestCase): cls.flows = dict() for i in range(len(cls.pg_interfaces)): delta = 1 if i % 2 == 0 else -1 - cls.flows[cls.pg_interfaces[i]] = [cls.pg_interfaces[i + delta]] + cls.flows[cls.pg_interfaces[i]] =\ + [cls.pg_interfaces[i + delta]] # Mapping between packet-generator index and lists of test hosts cls.hosts_by_pg_idx = dict() diff --git a/test/test_snat.py b/test/test_snat.py index ca2e52a7..d23becf5 100644 --- a/test/test_snat.py +++ b/test/test_snat.py @@ -130,13 +130,15 @@ class TestSNAT(VppTestCase): if same_port: self.assertEqual(packet[TCP].sport, self.tcp_port_in) else: - self.assertNotEqual(packet[TCP].sport, self.tcp_port_in) + self.assertNotEqual( + packet[TCP].sport, self.tcp_port_in) self.tcp_port_out = packet[TCP].sport elif packet.haslayer(UDP): if same_port: self.assertEqual(packet[UDP].sport, self.udp_port_in) else: - self.assertNotEqual(packet[UDP].sport, self.udp_port_in) + self.assertNotEqual( + packet[UDP].sport, self.udp_port_in) self.udp_port_out = packet[UDP].sport else: if same_port: @@ -215,8 +217,14 @@ class TestSNAT(VppTestCase): addr_only = 0 l_ip = socket.inet_pton(socket.AF_INET, local_ip) e_ip = socket.inet_pton(socket.AF_INET, external_ip) - self.vapi.snat_add_static_mapping(l_ip, e_ip, local_port, external_port, - addr_only, vrf_id, is_add) + self.vapi.snat_add_static_mapping( + l_ip, + e_ip, + local_port, + external_port, + addr_only, + vrf_id, + is_add) def snat_add_address(self, ip, is_add=1): """ @@ -413,7 +421,9 @@ class TestSNAT(VppTestCase): self.pg3.assert_nothing_captured() def test_multiple_inside_interfaces(self): - """SNAT multiple inside interfaces with non-overlapping address space""" + """ + SNAT multiple inside interfaces with non-overlapping address space + """ self.snat_add_address(self.snat_addr) self.vapi.snat_interface_add_del_feature(self.pg0.sw_if_index) diff --git a/test/test_span.py b/test/test_span.py index 41507092..dc0110db 100644 --- a/test/test_span.py +++ b/test/test_span.py @@ -102,11 +102,12 @@ class TestSpan(VppTestCase): for i in self.interfaces: last_info[i.sw_if_index] = None dst_sw_if_index = dst_if.sw_if_index - if len(capture_pg1) != len(capture_pg2): - self.logger.error( - "Different number of outgoing and mirrored packets : %u != %u" % - (len(capture_pg1), len(capture_pg2))) - raise + self.AssertEqual( + len(capture_pg1), + len(capture_pg2), + "Different number of outgoing and mirrored packets : %u != %u" % + (len(capture_pg1), + len(capture_pg2))) for pkt_pg1, pkt_pg2 in zip(capture_pg1, capture_pg2): try: ip1 = pkt_pg1[IP] diff --git a/test/vpp_interface.py b/test/vpp_interface.py index 9d9712eb..e0a29f94 100644 --- a/test/vpp_interface.py +++ b/test/vpp_interface.py @@ -15,7 +15,7 @@ class VppInterface(object): @property def remote_mac(self): - """MAC-address of the remote interface "connected" to this interface.""" + """MAC-address of the remote interface "connected" to this interface""" return self._remote_hosts[0].mac @property @@ -261,19 +261,23 @@ class VppInterface(object): def admin_up(self): """Put interface ADMIN-UP.""" - self.test.vapi.sw_interface_set_flags(self.sw_if_index, admin_up_down=1) + self.test.vapi.sw_interface_set_flags(self.sw_if_index, + admin_up_down=1) def admin_down(self): """Put interface ADMIN-down.""" - self.test.vapi.sw_interface_set_flags(self.sw_if_index, admin_up_down=0) + self.test.vapi.sw_interface_set_flags(self.sw_if_index, + admin_up_down=0) def ip6_enable(self): """IPv6 Enable interface""" - self.test.vapi.ip6_sw_interface_enable_disable(self.sw_if_index, enable=1) + self.test.vapi.ip6_sw_interface_enable_disable(self.sw_if_index, + enable=1) def ip6_disable(self): """Put interface ADMIN-DOWN.""" - self.test.vapi.ip6_sw_interface_enable_disable(self.sw_if_index, enable=0) + self.test.vapi.ip6_sw_interface_enable_disable(self.sw_if_index, + enable=0) def add_sub_if(self, sub_if): """Register a sub-interface with this interface. diff --git a/test/vpp_ip_route.py b/test/vpp_ip_route.py index 75f400f1..975e3934 100644 --- a/test/vpp_ip_route.py +++ b/test/vpp_ip_route.py @@ -13,7 +13,13 @@ MPLS_LABEL_INVALID = MPLS_IETF_MAX_LABEL + 1 class RoutePath: - def __init__(self, nh_addr, nh_sw_if_index, nh_table_id=0, labels=[], nh_via_label=MPLS_LABEL_INVALID): + def __init__( + self, + nh_addr, + nh_sw_if_index, + nh_table_id=0, + labels=[], + nh_via_label=MPLS_LABEL_INVALID): self.nh_addr = socket.inet_pton(socket.AF_INET, nh_addr) self.nh_itf = nh_sw_if_index self.nh_table_id = nh_table_id @@ -36,15 +42,16 @@ class IpRoute: def add_vpp_config(self): for path in self.paths: - self._test.vapi.ip_add_del_route(self.dest_addr, - self.dest_addr_len, - path.nh_addr, - path.nh_itf, - table_id=self.table_id, - next_hop_out_label_stack=path.nh_labels, - next_hop_n_out_labels=len( - path.nh_labels), - next_hop_via_label=path.nh_via_label) + self._test.vapi.ip_add_del_route( + self.dest_addr, + self.dest_addr_len, + path.nh_addr, + path.nh_itf, + table_id=self.table_id, + next_hop_out_label_stack=path.nh_labels, + next_hop_n_out_labels=len( + path.nh_labels), + next_hop_via_label=path.nh_via_label) def remove_vpp_config(self): for path in self.paths: @@ -61,7 +68,7 @@ class MplsIpBind: MPLS to IP Binding """ - def __init__(self, test, local_label, dest_addr, dest_addr_len): + def __init__(self, test, local_label, dest_addr, dest_addr_len): self._test = test self.dest_addr = socket.inet_pton(socket.AF_INET, dest_addr) self.dest_addr_len = dest_addr_len @@ -93,17 +100,18 @@ class MplsRoute: def add_vpp_config(self): for path in self.paths: - self._test.vapi.mpls_route_add_del(self.local_label, - self.eos_bit, - 1, - path.nh_addr, - path.nh_itf, - table_id=self.table_id, - next_hop_out_label_stack=path.nh_labels, - next_hop_n_out_labels=len( - path.nh_labels), - next_hop_via_label=path.nh_via_label, - next_hop_table_id=path.nh_table_id) + self._test.vapi.mpls_route_add_del( + self.local_label, + self.eos_bit, + 1, + path.nh_addr, + path.nh_itf, + table_id=self.table_id, + next_hop_out_label_stack=path.nh_labels, + next_hop_n_out_labels=len( + path.nh_labels), + next_hop_via_label=path.nh_via_label, + next_hop_table_id=path.nh_table_id) def remove_vpp_config(self): for path in self.paths: diff --git a/test/vpp_papi_provider.py b/test/vpp_papi_provider.py index 26edd4f2..b78e8613 100644 --- a/test/vpp_papi_provider.py +++ b/test/vpp_papi_provider.py @@ -5,9 +5,9 @@ from hook import Hook from collections import deque # Sphinx creates auto-generated documentation by importing the python source -# files and collecting the docstrings from them. The NO_VPP_PAPI flag allows the -# vpp_papi_provider.py file to be importable without having to build the whole -# vpp api if the user only wishes to generate the test documentation. +# files and collecting the docstrings from them. The NO_VPP_PAPI flag allows +# the vpp_papi_provider.py file to be importable without having to build +# the whole vpp api if the user only wishes to generate the test documentation. do_import = True try: no_vpp_papi = os.getenv("NO_VPP_PAPI") @@ -224,8 +224,8 @@ class VppPapiProvider(object): send_unicast,): return self.api(self.papi.sw_interface_ip6nd_ra_config, {'sw_if_index': sw_if_index, - 'suppress' : suppress, - 'send_unicast' : send_unicast}) + 'suppress': suppress, + 'send_unicast': send_unicast}) def ip6_sw_interface_enable_disable(self, sw_if_index, enable): """ @@ -972,7 +972,7 @@ class VppPapiProvider(object): """ :param is_add: :param mask: - :param match_n_vectors: (Default value = 1): + :param match_n_vectors: (Default value = 1) :param table_index: (Default value = 0xFFFFFFFF) :param nbuckets: (Default value = 2) :param memory_size: (Default value = 2097152) diff --git a/test/vpp_pg_interface.py b/test/vpp_pg_interface.py index eeb9c1a5..756f79b5 100644 --- a/test/vpp_pg_interface.py +++ b/test/vpp_pg_interface.py @@ -13,6 +13,7 @@ from util import ppp, ppc from scapy.utils6 import in6_getnsma, in6_getnsmac, in6_ismaddr from scapy.utils import inet_pton, inet_ntop + def is_ipv6_misc(p): """ Is packet one of uninteresting IPv6 broadcasts? """ if p.haslayer(ICMPv6ND_RA): @@ -91,10 +92,12 @@ class VppPGInterface(VppInterface): self._capture_cli = "packet-generator capture pg%u pcap %s" % ( self.pg_index, self.out_path) self._cap_name = "pcap%u" % self.sw_if_index - self._input_cli = "packet-generator new pcap %s source pg%u name %s" % ( - self.in_path, self.pg_index, self.cap_name) + self._input_cli = \ + "packet-generator new pcap %s source pg%u name %s" % ( + self.in_path, self.pg_index, self.cap_name) - def rotate_out_file(self): + def enable_capture(self): + """ Enable capture on this packet-generator interface""" try: if os.path.isfile(self.out_path): os.rename(self.out_path, @@ -106,10 +109,6 @@ class VppPGInterface(VppInterface): self._out_file)) except: pass - - def enable_capture(self): - """ Enable capture on this packet-generator interface""" - self.rotate_out_file() # FIXME this should be an API, but no such exists atm self.test.vapi.cli(self.capture_cli) self._pcap_reader = None @@ -151,7 +150,7 @@ class VppPGInterface(VppInterface): before = len(output.res) if filter_out_fn: output.res = [p for p in output.res if not filter_out_fn(p)] - removed = before - len(output.res) + removed = len(output.res) - before if removed: self.test.logger.debug( "Filtered out %s packets from capture (returning %s)" % @@ -181,9 +180,13 @@ class VppPGInterface(VppInterface): based_on = "based on stored packet_infos" if expected_count == 0: raise Exception( - "Internal error, expected packet count for %s is 0!" % name) + "Internal error, expected packet count for %s is 0!" % + name) self.test.logger.debug("Expecting to capture %s (%s) packets on %s" % ( expected_count, based_on, name)) + if expected_count == 0: + raise Exception( + "Internal error, expected packet count for %s is 0!" % name) while remaining_time > 0: before = time.time() capture = self._get_capture(remaining_time, filter_out_fn) @@ -192,8 +195,6 @@ class VppPGInterface(VppInterface): if len(capture.res) == expected_count: # bingo, got the packets we expected return capture - elif expected_count == 0: - return None remaining_time -= elapsed_time if capture: raise Exception("Captured packets mismatch, captured %s packets, " @@ -241,8 +242,9 @@ class VppPGInterface(VppInterface): self.test.logger.debug("Waiting for capture file %s to appear, " "timeout is %ss" % (self.out_path, timeout)) else: - self.test.logger.debug("Capture file %s already exists" % - self.out_path) + self.test.logger.debug( + "Capture file %s already exists" % + self.out_path) return True while time.time() < limit: if os.path.isfile(self.out_path): @@ -275,8 +277,10 @@ class VppPGInterface(VppInterface): self._pcap_reader = PcapReader(self.out_path) break except: - self.test.logger.debug("Exception in scapy.PcapReader(%s): " - "%s" % (self.out_path, format_exc())) + self.test.logger.debug( + "Exception in scapy.PcapReader(%s): " + "%s" % + (self.out_path, format_exc())) if not self._pcap_reader: raise Exception("Capture file %s did not appear within " "timeout" % self.out_path) @@ -290,8 +294,9 @@ class VppPGInterface(VppInterface): "Packet received after %ss was filtered out" % (time.time() - (deadline - timeout))) else: - self.test.logger.debug("Packet received after %fs" % - (time.time() - (deadline - timeout))) + self.test.logger.debug( + "Packet received after %fs" % + (time.time() - (deadline - timeout))) return p time.sleep(0) # yield self.test.logger.debug("Timeout - no packets received") @@ -335,7 +340,6 @@ class VppPGInterface(VppInterface): self.test.logger.info("No ARP received on port %s" % pg_interface.name) return - self.rotate_out_file() arp_reply = captured_packet.copy() # keep original for exception # Make Dot1AD packet content recognizable to scapy if arp_reply.type == 0x88a8: @@ -380,7 +384,8 @@ class VppPGInterface(VppInterface): captured_packet = pg_interface.wait_for_packet( deadline - now, filter_out_fn=None) except: - self.test.logger.error("Timeout while waiting for NDP response") + self.test.logger.error( + "Timeout while waiting for NDP response") raise ndp_reply = captured_packet.copy() # keep original for exception # Make Dot1AD packet content recognizable to scapy @@ -395,13 +400,12 @@ class VppPGInterface(VppInterface): self._local_mac = opt.lladdr self.test.logger.debug(self.test.vapi.cli("show trace")) # we now have the MAC we've been after - self.rotate_out_file() return except: self.test.logger.info( - ppp("Unexpected response to NDP request:", captured_packet)) + ppp("Unexpected response to NDP request:", + captured_packet)) now = time.time() self.test.logger.debug(self.test.vapi.cli("show trace")) - self.rotate_out_file() raise Exception("Timeout while waiting for NDP response") -- cgit 1.2.3-korg From 46a87adf10d41af4b1b14f06bdab33228cbaae95 Mon Sep 17 00:00:00 2001 From: Klement Sekera Date: Mon, 2 Jan 2017 08:22:23 +0100 Subject: BFD: IPv6 support Change-Id: Iaa9538c7cca500c04cf2704e5bf87480543cfcdf Signed-off-by: Klement Sekera --- src/vnet/bfd/bfd_main.c | 52 +++++----- src/vnet/bfd/bfd_main.h | 2 +- src/vnet/bfd/bfd_udp.c | 245 ++++++++++++++++++++++++++++++++++++++---------- src/vnet/bfd/bfd_udp.h | 6 +- test/bfd.py | 14 ++- test/test_bfd.py | 221 +++++++++++++++++++++++++++---------------- test/util.py | 11 ++- test/vpp_interface.py | 11 ++- 8 files changed, 394 insertions(+), 168 deletions(-) (limited to 'test/vpp_interface.py') diff --git a/src/vnet/bfd/bfd_main.c b/src/vnet/bfd/bfd_main.c index 62be1842..7e1a2ef2 100644 --- a/src/vnet/bfd/bfd_main.c +++ b/src/vnet/bfd/bfd_main.c @@ -34,16 +34,9 @@ bfd_us_to_clocks (bfd_main_t * bm, u64 us) static vlib_node_registration_t bfd_process_node; -typedef enum -{ -#define F(t, n) BFD_OUTPUT_##t, - foreach_bfd_transport (F) -#undef F - BFD_OUTPUT_N_NEXT, -} bfd_output_next_t; - -static u32 bfd_next_index_by_transport[] = { -#define F(t, n) [BFD_TRANSPORT_##t] = BFD_OUTPUT_##t, +/* set to 0 here, real values filled at startup */ +static u32 bfd_node_index_by_transport[] = { +#define F(t, n) [BFD_TRANSPORT_##t] = 0, foreach_bfd_transport (F) #undef F }; @@ -378,7 +371,7 @@ bfd_input_format_trace (u8 * s, va_list * args) clib_net_to_host_u32 (pkt->des_min_tx)); s = format (s, " required min rx interval: %u\n", clib_net_to_host_u32 (pkt->req_min_rx)); - s = format (s, " required min echo rx interval: %u\n", + s = format (s, " required min echo rx interval: %u", clib_net_to_host_u32 (pkt->req_min_echo_rx)); } } @@ -426,10 +419,12 @@ bfd_add_transport_layer (vlib_main_t * vm, vlib_buffer_t * b, switch (bs->transport) { case BFD_TRANSPORT_UDP4: - /* fallthrough */ + BFD_DBG ("Transport bfd via udp4, bs_idx=%u", bs->bs_idx); + bfd_add_udp4_transport (vm, b, &bs->udp); + break; case BFD_TRANSPORT_UDP6: - BFD_DBG ("Transport bfd via udp, bs_idx=%u", bs->bs_idx); - bfd_add_udp_transport (vm, b, &bs->udp); + BFD_DBG ("Transport bfd via udp6, bs_idx=%u", bs->bs_idx); + bfd_add_udp6_transport (vm, b, &bs->udp); break; } } @@ -448,17 +443,14 @@ bfd_create_frame (vlib_main_t * vm, vlib_node_runtime_t * rt, vlib_buffer_t *b = vlib_get_buffer (vm, bi); ASSERT (b->current_data == 0); - u32 *to_next; - u32 n_left_to_next; - - vlib_get_next_frame (vm, rt, bfd_next_index_by_transport[bs->transport], - to_next, n_left_to_next); + vlib_frame_t *f = + vlib_get_frame_to_node (vm, bfd_node_index_by_transport[bs->transport]); + u32 *to_next = vlib_frame_vector_args (f); to_next[0] = bi; - n_left_to_next -= 1; + f->n_vectors = 1; - vlib_put_next_frame (vm, rt, bfd_next_index_by_transport[bs->transport], - n_left_to_next); + vlib_put_frame_to_node (vm, bfd_node_index_by_transport[bs->transport], f); return b; } @@ -680,13 +672,8 @@ VLIB_REGISTER_NODE (bfd_process_node, static) = { .function = bfd_process, .type = VLIB_NODE_TYPE_PROCESS, .name = "bfd-process", - .n_next_nodes = BFD_OUTPUT_N_NEXT, - .next_nodes = - { -#define F(t, n) [BFD_OUTPUT_##t] = n, - foreach_bfd_transport (F) -#undef F - }, + .n_next_nodes = 0, + .next_nodes = {}, }; /* *INDENT-ON* */ @@ -734,6 +721,13 @@ bfd_main_init (vlib_main_t * vm) timing_wheel_init (&bm->wheel, now, bm->cpu_cps); bm->wheel_inaccuracy = 2 << bm->wheel.log2_clocks_per_bin; + vlib_node_t *node = NULL; +#define F(t, n) \ + node = vlib_get_node_by_name (vm, (u8 *)n); \ + bfd_node_index_by_transport[BFD_TRANSPORT_##t] = node->index;\ + BFD_DBG("node '%s' has index %u", n, node->index); + foreach_bfd_transport (F); +#undef F return 0; } diff --git a/src/vnet/bfd/bfd_main.h b/src/vnet/bfd/bfd_main.h index cc82c839..20da381a 100644 --- a/src/vnet/bfd/bfd_main.h +++ b/src/vnet/bfd/bfd_main.h @@ -25,7 +25,7 @@ #include #define foreach_bfd_transport(F) \ - F (UDP4, "ip4-rewrite") \ + F (UDP4, "ip4-rewrite") \ F (UDP6, "ip6-rewrite") typedef enum diff --git a/src/vnet/bfd/bfd_udp.c b/src/vnet/bfd/bfd_udp.c index c1596bf6..fe348404 100644 --- a/src/vnet/bfd/bfd_udp.c +++ b/src/vnet/bfd/bfd_udp.c @@ -31,53 +31,80 @@ static vlib_node_registration_t bfd_udp6_input_node; bfd_udp_main_t bfd_udp_main; -void bfd_udp_transport_to_buffer (vlib_main_t *vm, vlib_buffer_t *b, - bfd_udp_session_t *bus) +void bfd_add_udp4_transport (vlib_main_t *vm, vlib_buffer_t *b, + bfd_udp_session_t *bus) { udp_header_t *udp; - u16 udp_length, ip_length; - bfd_udp_key_t *key = &bus->key; + const bfd_udp_key_t *key = &bus->key; b->flags |= VNET_BUFFER_LOCALLY_ORIGINATED; - if (ip46_address_is_ip4 (&key->local_addr)) - { - ip4_header_t *ip4; - const size_t data_size = sizeof (*ip4) + sizeof (*udp); - vlib_buffer_advance (b, -data_size); - ip4 = vlib_buffer_get_current (b); - udp = (udp_header_t *)(ip4 + 1); - memset (ip4, 0, data_size); - ip4->ip_version_and_header_length = 0x45; - ip4->ttl = 255; - ip4->protocol = IP_PROTOCOL_UDP; - ip4->src_address.as_u32 = key->local_addr.ip4.as_u32; - ip4->dst_address.as_u32 = key->peer_addr.ip4.as_u32; - - udp->src_port = clib_host_to_net_u16 (50000); /* FIXME */ - udp->dst_port = clib_host_to_net_u16 (UDP_DST_PORT_bfd4); - - /* fix ip length, checksum and udp length */ - ip_length = vlib_buffer_length_in_chain (vm, b); - - ip4->length = clib_host_to_net_u16 (ip_length); - ip4->checksum = ip4_header_checksum (ip4); - - udp_length = ip_length - (sizeof (*ip4)); - udp->length = clib_host_to_net_u16 (udp_length); - } - else - { - BFD_ERR ("not implemented"); - abort (); - } + vnet_buffer (b)->ip.adj_index[VLIB_RX] = bus->adj_index; + vnet_buffer (b)->ip.adj_index[VLIB_TX] = bus->adj_index; + ip4_header_t *ip4; + const size_t headers_size = sizeof (*ip4) + sizeof (*udp); + vlib_buffer_advance (b, -headers_size); + ip4 = vlib_buffer_get_current (b); + udp = (udp_header_t *)(ip4 + 1); + memset (ip4, 0, headers_size); + ip4->ip_version_and_header_length = 0x45; + ip4->ttl = 255; + ip4->protocol = IP_PROTOCOL_UDP; + ip4->src_address.as_u32 = key->local_addr.ip4.as_u32; + ip4->dst_address.as_u32 = key->peer_addr.ip4.as_u32; + + udp->src_port = clib_host_to_net_u16 (50000); /* FIXME */ + udp->dst_port = clib_host_to_net_u16 (UDP_DST_PORT_bfd4); + + /* fix ip length, checksum and udp length */ + const u16 ip_length = vlib_buffer_length_in_chain (vm, b); + + ip4->length = clib_host_to_net_u16 (ip_length); + ip4->checksum = ip4_header_checksum (ip4); + + const u16 udp_length = ip_length - (sizeof (*ip4)); + udp->length = clib_host_to_net_u16 (udp_length); } -void bfd_add_udp_transport (vlib_main_t *vm, vlib_buffer_t *b, - bfd_udp_session_t *bus) +void bfd_add_udp6_transport (vlib_main_t *vm, vlib_buffer_t *b, + bfd_udp_session_t *bus) { + udp_header_t *udp; + const bfd_udp_key_t *key = &bus->key; + + b->flags |= VNET_BUFFER_LOCALLY_ORIGINATED; vnet_buffer (b)->ip.adj_index[VLIB_RX] = bus->adj_index; vnet_buffer (b)->ip.adj_index[VLIB_TX] = bus->adj_index; - bfd_udp_transport_to_buffer (vm, b, bus); + ip6_header_t *ip6; + const size_t headers_size = sizeof (*ip6) + sizeof (*udp); + vlib_buffer_advance (b, -headers_size); + ip6 = vlib_buffer_get_current (b); + udp = (udp_header_t *)(ip6 + 1); + memset (ip6, 0, headers_size); + ip6->ip_version_traffic_class_and_flow_label = + clib_host_to_net_u32 (0x6 << 28); + ip6->hop_limit = 255; + ip6->protocol = IP_PROTOCOL_UDP; + clib_memcpy (&ip6->src_address, &key->local_addr.ip6, + sizeof (ip6->src_address)); + clib_memcpy (&ip6->dst_address, &key->peer_addr.ip6, + sizeof (ip6->dst_address)); + + udp->src_port = clib_host_to_net_u16 (50000); /* FIXME */ + udp->dst_port = clib_host_to_net_u16 (UDP_DST_PORT_bfd6); + + /* fix ip payload length and udp length */ + const u16 udp_length = vlib_buffer_length_in_chain (vm, b) - (sizeof (*ip6)); + udp->length = clib_host_to_net_u16 (udp_length); + ip6->payload_length = udp->length; + + /* IPv6 UDP checksum is mandatory */ + int bogus = 0; + udp->checksum = ip6_tcp_udp_icmp_compute_checksum (vm, b, ip6, &bogus); + ASSERT (bogus == 0); + if (udp->checksum == 0) + { + udp->checksum = 0xffff; + } } static bfd_session_t *bfd_lookup_session (bfd_udp_main_t *bum, @@ -345,29 +372,29 @@ static bfd_udp_error_t bfd_udp4_verify_transport (const ip4_header_t *ip4, const bfd_udp_key_t *key = &bus->key; if (ip4->src_address.as_u32 != key->peer_addr.ip4.as_u32) { - BFD_ERR ("IP src addr mismatch, got %U, expected %U", format_ip4_address, - ip4->src_address.as_u32, format_ip4_address, - key->peer_addr.ip4.as_u32); + BFD_ERR ("IPv4 src addr mismatch, got %U, expected %U", + format_ip4_address, ip4->src_address.as_u8, format_ip4_address, + key->peer_addr.ip4.as_u8); return BFD_UDP_ERROR_BAD; } if (ip4->dst_address.as_u32 != key->local_addr.ip4.as_u32) { - BFD_ERR ("IP dst addr mismatch, got %U, expected %U", format_ip4_address, - ip4->dst_address.as_u32, format_ip4_address, - key->local_addr.ip4.as_u32); + BFD_ERR ("IPv4 dst addr mismatch, got %U, expected %U", + format_ip4_address, ip4->dst_address.as_u8, format_ip4_address, + key->local_addr.ip4.as_u8); return BFD_UDP_ERROR_BAD; } const u8 expected_ttl = 255; if (ip4->ttl != expected_ttl) { - BFD_ERR ("IP unexpected TTL value %d, expected %d", ip4->ttl, + BFD_ERR ("IPv4 unexpected TTL value %u, expected %u", ip4->ttl, expected_ttl); return BFD_UDP_ERROR_BAD; } if (clib_net_to_host_u16 (udp->src_port) < 49152 || clib_net_to_host_u16 (udp->src_port) > 65535) { - BFD_ERR ("Invalid UDP src port %d, out of range <49152,65535>", + BFD_ERR ("Invalid UDP src port %u, out of range <49152,65535>", udp->src_port); } return BFD_UDP_ERROR_NONE; @@ -460,10 +487,128 @@ static bfd_udp_error_t bfd_udp4_scan (vlib_main_t *vm, vlib_node_runtime_t *rt, return BFD_UDP_ERROR_NONE; } -static bfd_udp_error_t bfd_udp6_scan (vlib_main_t *vm, vlib_buffer_t *b) +static void bfd_udp6_find_headers (vlib_buffer_t *b, const ip6_header_t **ip6, + const udp_header_t **udp) +{ + /* sanity check first */ + const i32 start = vnet_buffer (b)->ip.start_of_ip_header; + if (start < 0 && start < sizeof (b->pre_data)) + { + BFD_ERR ("Start of ip header is before pre_data, ignoring"); + *ip6 = NULL; + *udp = NULL; + return; + } + *ip6 = (ip6_header_t *)(b->data + start); + if ((u8 *)*ip6 > (u8 *)vlib_buffer_get_current (b)) + { + BFD_ERR ("Start of ip header is beyond current data, ignoring"); + *ip6 = NULL; + *udp = NULL; + return; + } + *udp = (udp_header_t *)((*ip6) + 1); +} + +static bfd_udp_error_t bfd_udp6_verify_transport (const ip6_header_t *ip6, + const udp_header_t *udp, + const bfd_session_t *bs) { - /* TODO */ - return BFD_UDP_ERROR_BAD; + const bfd_udp_session_t *bus = &bs->udp; + const bfd_udp_key_t *key = &bus->key; + if (ip6->src_address.as_u64[0] != key->peer_addr.ip6.as_u64[0] && + ip6->src_address.as_u64[1] != key->peer_addr.ip6.as_u64[1]) + { + BFD_ERR ("IP src addr mismatch, got %U, expected %U", format_ip6_address, + ip6, format_ip6_address, &key->peer_addr.ip6); + return BFD_UDP_ERROR_BAD; + } + if (ip6->dst_address.as_u64[0] != key->local_addr.ip6.as_u64[0] && + ip6->dst_address.as_u64[1] != key->local_addr.ip6.as_u64[1]) + { + BFD_ERR ("IP dst addr mismatch, got %U, expected %U", format_ip6_address, + ip6, format_ip6_address, &key->local_addr.ip6); + return BFD_UDP_ERROR_BAD; + } + const u8 expected_hop_limit = 255; + if (ip6->hop_limit != expected_hop_limit) + { + BFD_ERR ("IPv6 unexpected hop-limit value %u, expected %u", + ip6->hop_limit, expected_hop_limit); + return BFD_UDP_ERROR_BAD; + } + if (clib_net_to_host_u16 (udp->src_port) < 49152 || + clib_net_to_host_u16 (udp->src_port) > 65535) + { + BFD_ERR ("Invalid UDP src port %u, out of range <49152,65535>", + udp->src_port); + } + return BFD_UDP_ERROR_NONE; +} + +static bfd_udp_error_t bfd_udp6_scan (vlib_main_t *vm, vlib_node_runtime_t *rt, + vlib_buffer_t *b, bfd_session_t **bs_out) +{ + const bfd_pkt_t *pkt = vlib_buffer_get_current (b); + if (sizeof (*pkt) > b->current_length) + { + BFD_ERR ( + "Payload size %d too small to hold bfd packet of minimum size %d", + b->current_length, sizeof (*pkt)); + return BFD_UDP_ERROR_BAD; + } + const ip6_header_t *ip6; + const udp_header_t *udp; + bfd_udp6_find_headers (b, &ip6, &udp); + if (!ip6 || !udp) + { + BFD_ERR ("Couldn't find ip6 or udp header"); + return BFD_UDP_ERROR_BAD; + } + if (!bfd_verify_pkt_common (pkt)) + { + return BFD_UDP_ERROR_BAD; + } + bfd_session_t *bs = NULL; + if (pkt->your_disc) + { + BFD_DBG ("Looking up BFD session using discriminator %u", + pkt->your_disc); + bs = bfd_find_session_by_disc (bfd_udp_main.bfd_main, pkt->your_disc); + } + else + { + bfd_udp_key_t key; + memset (&key, 0, sizeof (key)); + key.sw_if_index = vnet_buffer (b)->sw_if_index[VLIB_RX]; + key.local_addr.ip6.as_u64[0] = ip6->dst_address.as_u64[0]; + key.local_addr.ip6.as_u64[1] = ip6->dst_address.as_u64[1]; + key.peer_addr.ip6.as_u64[0] = ip6->src_address.as_u64[0]; + key.peer_addr.ip6.as_u64[1] = ip6->src_address.as_u64[1]; + BFD_DBG ("Looking up BFD session using key (sw_if_index=%u, local=%U, " + "peer=%U)", + key.sw_if_index, format_ip6_address, &key.local_addr, + format_ip6_address, &key.peer_addr); + bs = bfd_lookup_session (&bfd_udp_main, &key); + } + if (!bs) + { + BFD_ERR ("BFD session lookup failed - no session matches BFD pkt"); + return BFD_UDP_ERROR_BAD; + } + BFD_DBG ("BFD session found, bs_idx=%u", bs->bs_idx); + if (!bfd_verify_pkt_session (pkt, b->current_length, bs)) + { + return BFD_UDP_ERROR_BAD; + } + bfd_udp_error_t err; + if (BFD_UDP_ERROR_NONE != (err = bfd_udp6_verify_transport (ip6, udp, bs))) + { + return err; + } + bfd_rpc_update_session (bs->bs_idx, pkt); + *bs_out = bs; + return BFD_UDP_ERROR_NONE; } /* @@ -504,7 +649,7 @@ static uword bfd_udp_input (vlib_main_t *vm, vlib_node_runtime_t *rt, /* scan this bfd pkt. error0 is the counter index to bmp */ if (is_ipv6) { - error0 = bfd_udp6_scan (vm, b0); + error0 = bfd_udp6_scan (vm, rt, b0, &bs); } else { diff --git a/src/vnet/bfd/bfd_udp.h b/src/vnet/bfd/bfd_udp.h index 51f5327b..2cd89ca2 100644 --- a/src/vnet/bfd/bfd_udp.h +++ b/src/vnet/bfd/bfd_udp.h @@ -42,8 +42,10 @@ typedef struct adj_index_t adj_index; } bfd_udp_session_t; -void bfd_add_udp_transport (vlib_main_t * vm, vlib_buffer_t * b, - bfd_udp_session_t * bs); +void bfd_add_udp4_transport (vlib_main_t * vm, vlib_buffer_t * b, + bfd_udp_session_t * bs); +void bfd_add_udp6_transport (vlib_main_t * vm, vlib_buffer_t * b, + bfd_udp_session_t * bs); #endif /* __included_bfd_udp_h__ */ diff --git a/test/bfd.py b/test/bfd.py index 57a5bd86..51716813 100644 --- a/test/bfd.py +++ b/test/bfd.py @@ -111,14 +111,24 @@ class VppBFDUDPSession(VppObject): def local_addr(self): """ BFD session local address (VPP address) """ if self._local_addr is None: - return self._interface.local_ip4 + if self.af == AF_INET: + return self._interface.local_ip4 + elif self.af == AF_INET6: + return self._interface.local_ip6 + else: + raise Exception("Unexpected af %s' % af" % self.af) return self._local_addr @property def local_addr_n(self): """ BFD session local address (VPP address) - raw, suitable for API """ if self._local_addr is None: - return self._interface.local_ip4n + if self.af == AF_INET: + return self._interface.local_ip4n + elif self.af == AF_INET6: + return self._interface.local_ip6n + else: + raise Exception("Unexpected af %s' % af" % self.af) return self._local_addr_n @property diff --git a/test/test_bfd.py b/test/test_bfd.py index b7832247..b56df339 100644 --- a/test/test_bfd.py +++ b/test/test_bfd.py @@ -62,36 +62,14 @@ class BFDAPITestCase(VppTestCase): session1.bs_index) -def create_packet(interface, ttl=255, src_port=50000, **kwargs): - p = (Ether(src=interface.remote_mac, dst=interface.local_mac) / - IP(src=interface.remote_ip4, dst=interface.local_ip4, ttl=ttl) / - UDP(sport=src_port, dport=BFD.udp_dport) / - BFD(*kwargs)) - return p - - -def verify_ip(test, packet, local_ip, remote_ip): - """ Verify correctness of IP layer. """ - ip = packet[IP] - test.assert_equal(ip.src, local_ip, "IP source address") - test.assert_equal(ip.dst, remote_ip, "IP destination address") - test.assert_equal(ip.ttl, 255, "IP TTL") - - -def verify_udp(test, packet): - """ Verify correctness of UDP layer. """ - udp = packet[UDP] - test.assert_equal(udp.dport, BFD.udp_dport, "UDP destination port") - test.assert_in_range(udp.sport, BFD.udp_sport_min, BFD.udp_sport_max, - "UDP source port") - - class BFDTestSession(object): """ BFD session as seen from test framework side """ - def __init__(self, test, interface, detect_mult=3): + def __init__(self, test, interface, af, detect_mult=3): self.test = test + self.af = af self.interface = interface + self.udp_sport = 50000 self.bfd_values = { 'my_discriminator': 0, 'desired_min_tx_interval': 100000, @@ -103,7 +81,22 @@ class BFDTestSession(object): self.bfd_values.update(kwargs) def create_packet(self): - packet = create_packet(self.interface) + if self.af == AF_INET6: + packet = (Ether(src=self.interface.remote_mac, + dst=self.interface.local_mac) / + IPv6(src=self.interface.remote_ip6, + dst=self.interface.local_ip6, + hlim=255) / + UDP(sport=self.udp_sport, dport=BFD.udp_dport) / + BFD()) + else: + packet = (Ether(src=self.interface.remote_mac, + dst=self.interface.local_mac) / + IP(src=self.interface.remote_ip4, + dst=self.interface.local_ip4, + ttl=255) / + UDP(sport=self.udp_sport, dport=BFD.udp_dport) / + BFD()) self.test.logger.debug("BFD: Creating packet") for name, value in self.bfd_values.iteritems(): self.test.logger.debug("BFD: setting packet.%s=%s", name, value) @@ -125,41 +118,52 @@ class BFDTestSession(object): "BFD - your discriminator") -@unittest.skip("") -class BFDTestCase(VppTestCase): - """Bidirectional Forwarding Detection (BFD)""" - - @classmethod - def setUpClass(cls): - super(BFDTestCase, cls).setUpClass() - try: - cls.create_pg_interfaces([0]) - cls.pg0.config_ip4() - cls.pg0.generate_remote_hosts() - cls.pg0.configure_ipv4_neighbors() - cls.pg0.admin_up() - cls.pg0.resolve_arp() - - except Exception: - super(BFDTestCase, cls).tearDownClass() - raise - - def setUp(self): - super(BFDTestCase, self).setUp() - self.vapi.want_bfd_events() - self.vpp_session = VppBFDUDPSession( - self, self.pg0, self.pg0.remote_ip4) - self.vpp_session.add_vpp_config() - self.vpp_session.admin_up() - self.test_session = BFDTestSession(self, self.pg0) - self.test_session.update(required_min_rx_interval=100000) +class BFDCommonCode: + """Common code used by both IPv4 and IPv6 Test Cases""" def tearDown(self): self.vapi.collect_events() # clear the event queue if not self.vpp_dead: self.vapi.want_bfd_events(enable_disable=0) self.vpp_session.remove_vpp_config() - super(BFDTestCase, self).tearDown() + + def bfd_session_up(self): + self.pg_enable_capture([self.pg0]) + self.logger.info("BFD: Waiting for slow hello") + p, timeout = self.wait_for_bfd_packet(2) + self.logger.info("BFD: Sending Init") + self.test_session.update(my_discriminator=randint(0, 40000000), + your_discriminator=p[BFD].my_discriminator, + state=BFDState.init, + required_min_rx_interval=100000) + self.test_session.send_packet() + self.logger.info("BFD: Waiting for event") + e = self.vapi.wait_for_event(1, "bfd_udp_session_details") + self.verify_event(e, expected_state=BFDState.up) + self.logger.info("BFD: Session is Up") + self.test_session.update(state=BFDState.up) + + def verify_ip(self, packet): + """ Verify correctness of IP layer. """ + if self.vpp_session.af == AF_INET6: + ip = packet[IPv6] + local_ip = self.pg0.local_ip6 + remote_ip = self.pg0.remote_ip6 + self.assert_equal(ip.hlim, 255, "IPv6 hop limit") + else: + ip = packet[IP] + local_ip = self.pg0.local_ip4 + remote_ip = self.pg0.remote_ip4 + self.assert_equal(ip.ttl, 255, "IPv4 TTL") + self.assert_equal(ip.src, local_ip, "IP source address") + self.assert_equal(ip.dst, remote_ip, "IP destination address") + + def verify_udp(self, packet): + """ Verify correctness of UDP layer. """ + udp = packet[UDP] + self.assert_equal(udp.dport, BFD.udp_dport, "UDP destination port") + self.assert_in_range(udp.sport, BFD.udp_sport_min, BFD.udp_sport_max, + "UDP source port") def verify_event(self, event, expected_state): """ Verify correctness of event values. """ @@ -198,35 +202,64 @@ class BFDTestCase(VppTestCase): before = time.time() p = self.pg0.wait_for_packet(timeout=timeout) after = time.time() + self.logger.debug(ppp("Got packet:", p)) bfd = p[BFD] if bfd is None: raise Exception(ppp("Unexpected or invalid BFD packet:", p)) if bfd.payload: raise Exception(ppp("Unexpected payload in BFD packet:", bfd)) - verify_ip(self, p, self.pg0.local_ip4, self.pg0.remote_ip4) - verify_udp(self, p) + self.verify_ip(p) + self.verify_udp(p) self.test_session.verify_packet(p) return p, after - before - def bfd_session_up(self): - self.pg_enable_capture([self.pg0]) - self.logger.info("BFD: Waiting for slow hello") - p, ttp = self.wait_for_bfd_packet() - self.logger.info("BFD: Sending Init") - self.test_session.update(my_discriminator=randint(0, 40000000), - your_discriminator=p[BFD].my_discriminator, - state=BFDState.init) - self.test_session.send_packet() - self.logger.info("BFD: Waiting for event") - e = self.vapi.wait_for_event(1, "bfd_udp_session_details") - self.verify_event(e, expected_state=BFDState.up) - self.logger.info("BFD: Session is Up") - self.test_session.update(state=BFDState.up) - def test_session_up(self): """ bring BFD session up """ self.bfd_session_up() + def test_hold_up(self): + """ hold BFD session up """ + self.bfd_session_up() + for i in range(5): + self.wait_for_bfd_packet() + self.test_session.send_packet() + + +class BFD4TestCase(VppTestCase, BFDCommonCode): + """Bidirectional Forwarding Detection (BFD)""" + + @classmethod + def setUpClass(cls): + super(BFD4TestCase, cls).setUpClass() + try: + cls.create_pg_interfaces([0]) + cls.pg0.config_ip4() + cls.pg0.generate_remote_hosts() + cls.pg0.configure_ipv4_neighbors() + cls.pg0.admin_up() + cls.pg0.resolve_arp() + + except Exception: + super(BFD4TestCase, cls).tearDownClass() + raise + + def setUp(self): + super(BFD4TestCase, self).setUp() + self.vapi.want_bfd_events() + try: + self.vpp_session = VppBFDUDPSession(self, self.pg0, + self.pg0.remote_ip4) + self.vpp_session.add_vpp_config() + self.vpp_session.admin_up() + self.test_session = BFDTestSession(self, self.pg0, AF_INET) + except: + self.vapi.want_bfd_events(enable_disable=0) + raise + + def tearDown(self): + BFDCommonCode.tearDown(self) + super(BFD4TestCase, self).tearDown() + def test_slow_timer(self): """ verify slow periodic control frames while session down """ self.pg_enable_capture([self.pg0]) @@ -261,13 +294,6 @@ class BFDTestCase(VppTestCase): return raise Exception(ppp("Received unexpected BFD packet:", p)) - def test_hold_up(self): - """ hold BFD session up """ - self.bfd_session_up() - for i in range(5): - self.wait_for_bfd_packet() - self.test_session.send_packet() - def test_conn_down(self): """ verify session goes down after inactivity """ self.bfd_session_up() @@ -324,5 +350,42 @@ class BFDTestCase(VppTestCase): 1.10 * interval / us_in_sec, "time between BFD packets") + +class BFD6TestCase(VppTestCase, BFDCommonCode): + """Bidirectional Forwarding Detection (BFD) (IPv6) """ + + @classmethod + def setUpClass(cls): + super(BFD6TestCase, cls).setUpClass() + try: + cls.create_pg_interfaces([0]) + cls.pg0.config_ip6() + cls.pg0.configure_ipv6_neighbors() + cls.pg0.admin_up() + cls.pg0.resolve_ndp() + + except Exception: + super(BFD6TestCase, cls).tearDownClass() + raise + + def setUp(self): + super(BFD6TestCase, self).setUp() + self.vapi.want_bfd_events() + try: + self.vpp_session = VppBFDUDPSession(self, self.pg0, + self.pg0.remote_ip6, + af=AF_INET6) + self.vpp_session.add_vpp_config() + self.vpp_session.admin_up() + self.test_session = BFDTestSession(self, self.pg0, AF_INET6) + self.logger.debug(self.vapi.cli("show adj nbr")) + except: + self.vapi.want_bfd_events(enable_disable=0) + raise + + def tearDown(self): + BFDCommonCode.tearDown(self) + super(BFD6TestCase, self).tearDown() + if __name__ == '__main__': unittest.main(testRunner=VppTestRunner) diff --git a/test/util.py b/test/util.py index 79893602..24e9af44 100644 --- a/test/util.py +++ b/test/util.py @@ -76,19 +76,24 @@ class Host(object): @property def ip4(self): - """ IPv4 address """ + """ IPv4 address - string """ return self._ip4 @property def ip4n(self): - """ IPv4 address """ + """ IPv4 address of remote host - raw, suitable as API parameter.""" return socket.inet_pton(socket.AF_INET, self._ip4) @property def ip6(self): - """ IPv6 address """ + """ IPv6 address - string """ return self._ip6 + @property + def ip6n(self): + """ IPv6 address of remote host - raw, suitable as API parameter.""" + return socket.inet_pton(socket.AF_INET6, self._ip6) + def __init__(self, mac=None, ip4=None, ip6=None): self._mac = mac self._ip4 = ip4 diff --git a/test/vpp_interface.py b/test/vpp_interface.py index e0a29f94..ee4a9ef6 100644 --- a/test/vpp_interface.py +++ b/test/vpp_interface.py @@ -131,7 +131,7 @@ class VppInterface(object): 2, count + 2): # 0: network address, 1: local vpp address mac = "02:%02x:00:00:ff:%02x" % (self.sw_if_index, i) ip4 = "172.16.%u.%u" % (self.sw_if_index, i) - ip6 = "fd01:%04x::%04x" % (self.sw_if_index, i) + ip6 = "fd01:%x::%x" % (self.sw_if_index, i) host = Host(mac, ip4, ip6) self._remote_hosts.append(host) self._hosts_by_mac[mac] = host @@ -155,7 +155,7 @@ class VppInterface(object): self.has_ip4_config = False self.ip4_table_id = 0 - self._local_ip6 = "fd01:%04x::1" % self.sw_if_index + self._local_ip6 = "fd01:%x::1" % self.sw_if_index self._local_ip6n = socket.inet_pton(socket.AF_INET6, self.local_ip6) self.local_ip6_prefix_len = 64 self.has_ip6_config = False @@ -226,6 +226,13 @@ class VppInterface(object): self.has_ip6_config = False self.has_ip6_config = False + def configure_ipv6_neighbors(self): + """For every remote host assign neighbor's MAC to IPv6 address.""" + for host in self._remote_hosts: + macn = host.mac.replace(":", "").decode('hex') + self.test.vapi.ip_neighbor_add_del( + self.sw_if_index, macn, host.ip6n, is_ipv6=1) + def unconfig(self): """Unconfigure IPv6 and IPv4 address on the VPP interface.""" self.unconfig_ip4() -- cgit 1.2.3-korg 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 --- src/scripts/vnet/mcast/ip4 | 22 + src/vat/api_format.c | 173 +++++ src/vnet.am | 35 +- src/vnet/adj/adj.c | 11 +- src/vnet/adj/adj.h | 1 - src/vnet/adj/adj_internal.h | 2 + src/vnet/adj/adj_mcast.c | 346 ++++++++++ src/vnet/adj/adj_mcast.h | 78 +++ src/vnet/adj/adj_nbr.c | 2 +- src/vnet/adj/adj_rewrite.c | 53 -- src/vnet/adj/adj_rewrite.h | 49 -- src/vnet/dhcpv6/proxy_node.c | 46 +- src/vnet/dpo/dpo.c | 2 + src/vnet/dpo/dpo.h | 8 +- src/vnet/dpo/load_balance.c | 13 +- src/vnet/dpo/load_balance.h | 8 + src/vnet/dpo/replicate_dpo.c | 759 ++++++++++++++++++++++ src/vnet/dpo/replicate_dpo.h | 143 +++++ src/vnet/ethernet/arp.c | 84 ++- src/vnet/ethernet/ethernet.h | 2 + src/vnet/ethernet/interface.c | 20 + src/vnet/fib/fib_attached_export.c | 4 +- src/vnet/fib/fib_entry.h | 2 +- src/vnet/fib/fib_entry_delegate.c | 3 + src/vnet/fib/fib_entry_src.c | 4 + src/vnet/fib/fib_node.h | 2 + src/vnet/fib/fib_path.c | 91 ++- src/vnet/fib/fib_path_list.c | 9 +- src/vnet/fib/fib_path_list.h | 6 + src/vnet/fib/fib_table.c | 69 +- src/vnet/fib/fib_table.h | 16 + src/vnet/fib/fib_test.c | 207 +++--- src/vnet/fib/fib_types.c | 4 + src/vnet/fib/fib_types.h | 14 + src/vnet/fib/fib_urpf_list.c | 20 +- src/vnet/fib/ip4_fib.c | 50 +- src/vnet/fib/ip4_fib.h | 9 + src/vnet/fib/ip6_fib.c | 117 ++-- src/vnet/fib/ip6_fib.h | 11 +- src/vnet/fib/mpls_fib.c | 17 +- src/vnet/fib/mpls_fib.h | 9 + src/vnet/ip/ip.api | 53 ++ src/vnet/ip/ip4.h | 24 + src/vnet/ip/ip4_forward.c | 498 +++++---------- src/vnet/ip/ip4_input.c | 4 +- src/vnet/ip/ip6.h | 27 + src/vnet/ip/ip6_forward.c | 83 ++- src/vnet/ip/ip6_input.c | 43 +- src/vnet/ip/ip6_neighbor.c | 134 ++-- src/vnet/ip/ip_api.c | 210 +++++++ src/vnet/ip/lookup.c | 171 +++++ src/vnet/ip/lookup.h | 82 +-- src/vnet/mcast/mcast.c | 565 ----------------- src/vnet/mcast/mcast.h | 50 -- src/vnet/mcast/mcast_test.c | 149 ----- src/vnet/mfib/ip4_mfib.c | 465 ++++++++++++++ src/vnet/mfib/ip4_mfib.h | 95 +++ src/vnet/mfib/ip6_mfib.c | 663 +++++++++++++++++++ src/vnet/mfib/ip6_mfib.h | 109 ++++ src/vnet/mfib/mfib_entry.c | 1096 ++++++++++++++++++++++++++++++++ src/vnet/mfib/mfib_entry.h | 172 +++++ src/vnet/mfib/mfib_forward.c | 512 +++++++++++++++ src/vnet/mfib/mfib_itf.c | 119 ++++ src/vnet/mfib/mfib_itf.h | 63 ++ src/vnet/mfib/mfib_signal.c | 201 ++++++ src/vnet/mfib/mfib_signal.h | 59 ++ src/vnet/mfib/mfib_table.c | 489 ++++++++++++++ src/vnet/mfib/mfib_table.h | 331 ++++++++++ src/vnet/mfib/mfib_test.c | 1225 ++++++++++++++++++++++++++++++++++++ src/vnet/mfib/mfib_types.c | 213 +++++++ src/vnet/mfib/mfib_types.h | 185 ++++++ src/vnet/misc.c | 3 + src/vnet/rewrite.h | 31 + src/vnet/sr/sr.c | 4 +- src/vnet/util/radix.c | 1104 ++++++++++++++++++++++++++++++++ src/vnet/util/radix.h | 147 +++++ src/vnet/vxlan/vxlan.c | 112 +++- src/vpp/api/api.c | 14 +- src/vppinfra.am | 2 +- src/vppinfra/dlist.h | 2 +- src/vppinfra/format.c | 8 +- src/vppinfra/format.h | 4 +- src/vppinfra/unformat.c | 16 +- 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 +- 90 files changed, 11211 insertions(+), 1767 deletions(-) create mode 100644 src/scripts/vnet/mcast/ip4 create mode 100644 src/vnet/adj/adj_mcast.c create mode 100644 src/vnet/adj/adj_mcast.h delete mode 100644 src/vnet/adj/adj_rewrite.c delete mode 100644 src/vnet/adj/adj_rewrite.h create mode 100644 src/vnet/dpo/replicate_dpo.c create mode 100644 src/vnet/dpo/replicate_dpo.h delete mode 100644 src/vnet/mcast/mcast.c delete mode 100644 src/vnet/mcast/mcast.h delete mode 100644 src/vnet/mcast/mcast_test.c create mode 100644 src/vnet/mfib/ip4_mfib.c create mode 100644 src/vnet/mfib/ip4_mfib.h create mode 100644 src/vnet/mfib/ip6_mfib.c create mode 100644 src/vnet/mfib/ip6_mfib.h create mode 100644 src/vnet/mfib/mfib_entry.c create mode 100644 src/vnet/mfib/mfib_entry.h create mode 100644 src/vnet/mfib/mfib_forward.c create mode 100644 src/vnet/mfib/mfib_itf.c create mode 100644 src/vnet/mfib/mfib_itf.h create mode 100644 src/vnet/mfib/mfib_signal.c create mode 100644 src/vnet/mfib/mfib_signal.h create mode 100644 src/vnet/mfib/mfib_table.c create mode 100644 src/vnet/mfib/mfib_table.h create mode 100644 src/vnet/mfib/mfib_test.c create mode 100644 src/vnet/mfib/mfib_types.c create mode 100644 src/vnet/mfib/mfib_types.h create mode 100644 src/vnet/util/radix.c create mode 100644 src/vnet/util/radix.h create mode 100644 test/test_ip_mcast.py create mode 100644 test/test_mfib.py (limited to 'test/vpp_interface.py') diff --git a/src/scripts/vnet/mcast/ip4 b/src/scripts/vnet/mcast/ip4 new file mode 100644 index 00000000..69f1ee00 --- /dev/null +++ b/src/scripts/vnet/mcast/ip4 @@ -0,0 +1,22 @@ +packet-generator new { + name x + limit 1 + node ip4-input + size 64-64 + no-recycle + data { + ICMP: 1.0.0.2 -> 232.1.1.1 + ICMP echo_request + incrementing 100 + } +} + +trace add pg-input 100 +loop create +loop create +set int state loop0 up +set int state loop1 up + +ip mroute add 232.1.1.1 via pg0 Accept +ip mroute add 232.1.1.1 via loop0 Forward +ip mroute add 232.1.1.1 via loop1 Forward diff --git a/src/vat/api_format.c b/src/vat/api_format.c index b83313de..4cfe4a58 100644 --- a/src/vat/api_format.c +++ b/src/vat/api_format.c @@ -48,6 +48,7 @@ #include #include #include +#include #include "vat/json_format.h" @@ -505,6 +506,53 @@ unformat_flow_classify_table_type (unformat_input_t * input, va_list * va) return 1; } +static const char *mfib_flag_names[] = MFIB_ENTRY_NAMES_SHORT; +static const char *mfib_flag_long_names[] = MFIB_ENTRY_NAMES_LONG; +static const char *mfib_itf_flag_long_names[] = MFIB_ITF_NAMES_LONG; +static const char *mfib_itf_flag_names[] = MFIB_ITF_NAMES_SHORT; + +uword +unformat_mfib_itf_flags (unformat_input_t * input, va_list * args) +{ + mfib_itf_flags_t old, *iflags = va_arg (*args, mfib_itf_flags_t *); + mfib_itf_attribute_t attr; + + old = *iflags; + FOR_EACH_MFIB_ITF_ATTRIBUTE (attr) + { + if (unformat (input, mfib_itf_flag_long_names[attr])) + *iflags |= (1 << attr); + } + FOR_EACH_MFIB_ITF_ATTRIBUTE (attr) + { + if (unformat (input, mfib_itf_flag_names[attr])) + *iflags |= (1 << attr); + } + + return (old == *iflags ? 0 : 1); +} + +uword +unformat_mfib_entry_flags (unformat_input_t * input, va_list * args) +{ + mfib_entry_flags_t old, *eflags = va_arg (*args, mfib_entry_flags_t *); + mfib_entry_attribute_t attr; + + old = *eflags; + FOR_EACH_MFIB_ATTRIBUTE (attr) + { + if (unformat (input, mfib_flag_long_names[attr])) + *eflags |= (1 << attr); + } + FOR_EACH_MFIB_ATTRIBUTE (attr) + { + if (unformat (input, mfib_flag_names[attr])) + *eflags |= (1 << attr); + } + + return (old == *eflags ? 0 : 1); +} + #if (VPP_API_TEST_BUILTIN==0) u8 * format_ip4_address (u8 * s, va_list * args) @@ -3592,6 +3640,7 @@ _(bridge_domain_add_del_reply) \ _(sw_interface_set_l2_xconnect_reply) \ _(l2fib_add_del_reply) \ _(ip_add_del_route_reply) \ +_(ip_mroute_add_del_reply) \ _(mpls_route_add_del_reply) \ _(mpls_ip_bind_unbind_reply) \ _(proxy_arp_add_del_reply) \ @@ -3792,6 +3841,7 @@ _(TAP_MODIFY_REPLY, tap_modify_reply) \ _(TAP_DELETE_REPLY, tap_delete_reply) \ _(SW_INTERFACE_TAP_DETAILS, sw_interface_tap_details) \ _(IP_ADD_DEL_ROUTE_REPLY, ip_add_del_route_reply) \ +_(IP_MROUTE_ADD_DEL_REPLY, ip_mroute_add_del_reply) \ _(MPLS_ROUTE_ADD_DEL_REPLY, mpls_route_add_del_reply) \ _(MPLS_IP_BIND_UNBIND_REPLY, mpls_ip_bind_unbind_reply) \ _(PROXY_ARP_ADD_DEL_REPLY, proxy_arp_add_del_reply) \ @@ -6383,6 +6433,126 @@ api_ip_add_del_route (vat_main_t * vam) return (vam->retval); } +static int +api_ip_mroute_add_del (vat_main_t * vam) +{ + unformat_input_t *i = vam->input; + vl_api_ip_mroute_add_del_t *mp; + f64 timeout; + u32 sw_if_index = ~0, vrf_id = 0; + u8 is_ipv6 = 0; + u8 is_local = 0; + u8 create_vrf_if_needed = 0; + u8 is_add = 1; + u8 address_set = 0; + u32 grp_address_length = 0; + ip4_address_t v4_grp_address, v4_src_address; + ip6_address_t v6_grp_address, v6_src_address; + mfib_itf_flags_t iflags = 0; + mfib_entry_flags_t eflags = 0; + + /* Parse args required to build the message */ + while (unformat_check_input (i) != UNFORMAT_END_OF_INPUT) + { + if (unformat (i, "sw_if_index %d", &sw_if_index)) + ; + else if (unformat (i, "%U %U", + unformat_ip4_address, &v4_src_address, + unformat_ip4_address, &v4_grp_address)) + { + grp_address_length = 64; + address_set = 1; + is_ipv6 = 0; + } + else if (unformat (i, "%U %U", + unformat_ip6_address, &v6_src_address, + unformat_ip6_address, &v6_grp_address)) + { + grp_address_length = 256; + address_set = 1; + is_ipv6 = 1; + } + else if (unformat (i, "%U", unformat_ip4_address, &v4_grp_address)) + { + memset (&v4_src_address, 0, sizeof (v4_src_address)); + grp_address_length = 32; + address_set = 1; + is_ipv6 = 0; + } + else if (unformat (i, "%U", unformat_ip6_address, &v6_grp_address)) + { + memset (&v6_src_address, 0, sizeof (v6_src_address)); + grp_address_length = 128; + address_set = 1; + is_ipv6 = 1; + } + else if (unformat (i, "/%d", &grp_address_length)) + ; + else if (unformat (i, "local")) + { + is_local = 1; + } + else if (unformat (i, "del")) + is_add = 0; + else if (unformat (i, "add")) + is_add = 1; + else if (unformat (i, "vrf %d", &vrf_id)) + ; + else if (unformat (i, "create-vrf")) + create_vrf_if_needed = 1; + else if (unformat (i, "%U", unformat_mfib_itf_flags, &iflags)) + ; + else if (unformat (i, "%U", unformat_mfib_entry_flags, &eflags)) + ; + else + { + clib_warning ("parse error '%U'", format_unformat_error, i); + return -99; + } + } + + if (address_set == 0) + { + errmsg ("missing addresses\n"); + return -99; + } + + /* Construct the API message */ + M (IP_MROUTE_ADD_DEL, ip_mroute_add_del); + + mp->next_hop_sw_if_index = ntohl (sw_if_index); + mp->table_id = ntohl (vrf_id); + mp->create_vrf_if_needed = create_vrf_if_needed; + + mp->is_add = is_add; + mp->is_ipv6 = is_ipv6; + mp->is_local = is_local; + mp->itf_flags = ntohl (iflags); + mp->entry_flags = ntohl (eflags); + mp->grp_address_length = grp_address_length; + mp->grp_address_length = ntohs (mp->grp_address_length); + + if (is_ipv6) + { + clib_memcpy (mp->grp_address, &v6_grp_address, sizeof (v6_grp_address)); + clib_memcpy (mp->src_address, &v6_src_address, sizeof (v6_src_address)); + } + else + { + clib_memcpy (mp->grp_address, &v4_grp_address, sizeof (v4_grp_address)); + clib_memcpy (mp->src_address, &v4_src_address, sizeof (v4_src_address)); + + } + + /* send it... */ + S; + /* Wait for a reply... */ + W; + + /* Return the good/bad news */ + return (vam->retval); +} + static int api_mpls_route_add_del (vat_main_t * vam) { @@ -17512,6 +17682,9 @@ _(ip_add_del_route, \ "[ | sw_if_index ] [resolve-attempts ]\n" \ "[weight ] [drop] [local] [classify ] [del]\n" \ "[multipath] [count ]") \ +_(ip_mroute_add_del, \ + " / [table-id ]\n" \ + "[ | sw_if_index ] [local] [del]") \ _(mpls_route_add_del, \ "