X-Git-Url: https://gerrit.fd.io/r/gitweb?a=blobdiff_plain;f=test%2Ftest_ip6.py;h=87509f3f8e1347550a2e93e9b799249bd3b99a0a;hb=853cc9f2ad3ee52cbdd891fb09d51c25678baed0;hp=0a0d56cbb8dfd52dc904ed0521e90fbb9d2d8b77;hpb=4c7c8e55b03e21787cafb11cd49b9488c5ffef70;p=vpp.git diff --git a/test/test_ip6.py b/test/test_ip6.py index 0a0d56cbb8d..87509f3f8e1 100644 --- a/test/test_ip6.py +++ b/test/test_ip6.py @@ -1,35 +1,81 @@ -#!/usr/bin/env python +#!/usr/bin/env python3 +import socket +from socket import inet_pton, inet_ntop import unittest -from socket import AF_INET6 -from framework import VppTestCase, VppTestRunner -from vpp_sub_interface import VppSubInterface, VppDot1QSubint -from vpp_pg_interface import is_ipv6_misc -from vpp_ip_route import VppIpRoute, VppRoutePath, find_route, VppIpMRoute, \ - VppMRoutePath, MRouteItfFlags, MRouteEntryFlags, VppMplsIpBind, \ - VppMplsRoute, DpoProto, VppMplsTable +from parameterized import parameterized +import scapy.layers.inet6 as inet6 +from scapy.layers.inet import UDP, IP +from scapy.contrib.mpls import MPLS +from scapy.layers.inet6 import ( + IPv6, + ICMPv6ND_NS, + ICMPv6ND_RS, + ICMPv6ND_RA, + ICMPv6NDOptMTU, + ICMPv6NDOptSrcLLAddr, + ICMPv6NDOptPrefixInfo, + ICMPv6ND_NA, + ICMPv6NDOptDstLLAddr, + ICMPv6DestUnreach, + icmp6types, + ICMPv6TimeExceeded, + ICMPv6EchoRequest, + ICMPv6EchoReply, + IPv6ExtHdrHopByHop, + ICMPv6MLReport2, +) +from scapy.layers.l2 import Ether, Dot1Q, GRE +from scapy.packet import Raw +from scapy.utils6 import ( + in6_getnsma, + in6_getnsmac, + in6_ptop, + in6_islladdr, +) +from six import moves + +from framework import VppTestCase +from asfframework import VppTestRunner, tag_run_solo +from util import ppp, ip6_normalize, mk_ll_addr +from vpp_papi import VppEnum +from vpp_ip import VppIpPuntPolicer, VppIpPuntRedirect, VppIpPathMtu +from vpp_ip_route import ( + VppIpRoute, + VppRoutePath, + find_route, + VppIpMRoute, + VppMRoutePath, + VppMplsIpBind, + VppMplsRoute, + VppMplsTable, + VppIpTable, + FibPathType, + FibPathProto, + VppIpInterfaceAddress, + find_route_in_dump, + find_mroute_in_dump, + VppIp6LinkLocalAddress, + VppIpRouteV2, +) from vpp_neighbor import find_nbr, VppNeighbor +from vpp_ipip_tun_interface import VppIpIpTunInterface +from vpp_pg_interface import is_ipv6_misc +from vpp_sub_interface import VppSubInterface, VppDot1QSubint +from vpp_policer import VppPolicer, PolicerAction +from ipaddress import IPv6Network, IPv6Address +from vpp_gre_interface import VppGreInterface +from vpp_teib import VppTeib -from scapy.packet import Raw -from scapy.layers.l2 import Ether, Dot1Q -from scapy.layers.inet6 import IPv6, UDP, TCP, ICMPv6ND_NS, ICMPv6ND_RS, \ - ICMPv6ND_RA, ICMPv6NDOptSrcLLAddr, getmacbyip6, ICMPv6MRD_Solicitation, \ - ICMPv6NDOptMTU, ICMPv6NDOptSrcLLAddr, ICMPv6NDOptPrefixInfo, \ - ICMPv6ND_NA, ICMPv6NDOptDstLLAddr, ICMPv6DestUnreach, icmp6types, \ - ICMPv6TimeExceeded - -from util import ppp -from scapy.utils6 import in6_getnsma, in6_getnsmac, in6_ptop, in6_islladdr, \ - in6_mactoifaceid, in6_ismaddr -from scapy.utils import inet_pton, inet_ntop -from scapy.contrib.mpls import MPLS +AF_INET6 = socket.AF_INET6 +try: + text_type = unicode +except NameError: + text_type = str -def mk_ll_addr(mac): - euid = in6_mactoifaceid(mac) - addr = "fe80::" + euid - return addr +NUM_PKTS = 67 class TestIPv6ND(VppTestCase): @@ -45,13 +91,11 @@ class TestIPv6ND(VppTestCase): # 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(dst_ip)) + self.assertEqual(in6_ptop(rx[IPv6].dst), in6_ptop(dst_ip)) # and come from the router's link local self.assertTrue(in6_islladdr(rx[IPv6].src)) - self.assertEqual(in6_ptop(rx[IPv6].src), - in6_ptop(mk_ll_addr(intf.local_mac))) + self.assertEqual(in6_ptop(rx[IPv6].src), in6_ptop(mk_ll_addr(intf.local_mac))) def validate_na(self, intf, rx, dst_ip=None, tgt_ip=None): if not dst_ip: @@ -67,8 +111,7 @@ class TestIPv6ND(VppTestCase): # the rx'd NA should be addressed to the sender's source self.assertTrue(rx.haslayer(ICMPv6ND_NA)) - self.assertEqual(in6_ptop(rx[IPv6].dst), - in6_ptop(dst_ip)) + self.assertEqual(in6_ptop(rx[IPv6].dst), in6_ptop(dst_ip)) # and come from the target address self.assertEqual(in6_ptop(rx[IPv6].src), in6_ptop(tgt_ip)) @@ -82,7 +125,7 @@ class TestIPv6ND(VppTestCase): dst_ip = inet_ntop(AF_INET6, nsma) # NS is broadcast - self.assertEqual(rx[Ether].dst, "ff:ff:ff:ff:ff:ff") + self.assertEqual(rx[Ether].dst, in6_getnsmac(nsma)) # and from the router's MAC self.assertEqual(rx[Ether].src, intf.local_mac) @@ -96,14 +139,15 @@ class TestIPv6ND(VppTestCase): self.assertEqual(in6_ptop(ns.tgt), in6_ptop(tgt_ip)) # packet is from the router's local address - self.assertEqual(in6_ptop(rx[IPv6].src), intf.local_ip6) + self.assertEqual(in6_ptop(rx[IPv6].src), intf.local_ip6_ll) # Src link-layer options should have the router's MAC sll = rx[ICMPv6NDOptSrcLLAddr] self.assertEqual(sll.lladdr, intf.local_mac) - def send_and_expect_ra(self, intf, pkts, remark, dst_ip=None, - filter_out_fn=is_ipv6_misc): + def send_and_expect_ra( + self, intf, pkts, remark, dst_ip=None, filter_out_fn=is_ipv6_misc + ): intf.add_stream(pkts) self.pg_enable_capture(self.pg_interfaces) self.pg_start() @@ -113,9 +157,9 @@ class TestIPv6ND(VppTestCase): rx = rx[0] self.validate_ra(intf, rx, dst_ip) - def send_and_expect_na(self, intf, pkts, remark, dst_ip=None, - tgt_ip=None, - filter_out_fn=is_ipv6_misc): + def send_and_expect_na( + self, intf, pkts, remark, dst_ip=None, tgt_ip=None, filter_out_fn=is_ipv6_misc + ): intf.add_stream(pkts) self.pg_enable_capture(self.pg_interfaces) self.pg_start() @@ -125,8 +169,10 @@ class TestIPv6ND(VppTestCase): rx = rx[0] self.validate_na(intf, rx, dst_ip, tgt_ip) - def send_and_expect_ns(self, tx_intf, rx_intf, pkts, tgt_ip, - filter_out_fn=is_ipv6_misc): + def send_and_expect_ns( + self, tx_intf, rx_intf, pkts, tgt_ip, filter_out_fn=is_ipv6_misc + ): + self.vapi.cli("clear trace") tx_intf.add_stream(pkts) self.pg_enable_capture(self.pg_interfaces) self.pg_start() @@ -136,14 +182,6 @@ class TestIPv6ND(VppTestCase): rx = rx[0] self.validate_ns(rx_intf, rx, tgt_ip) - def send_and_assert_no_replies(self, intf, pkts, remark): - intf.add_stream(pkts) - self.pg_enable_capture(self.pg_interfaces) - self.pg_start() - for i in self.pg_interfaces: - i.get_capture(0) - i.assert_nothing_captured(remark=remark) - def verify_ip(self, rx, smac, dmac, sip, dip): ether = rx[Ether] self.assertEqual(ether.dst, dmac) @@ -153,14 +191,35 @@ class TestIPv6ND(VppTestCase): self.assertEqual(ip.src, sip) self.assertEqual(ip.dst, dip) + def get_ip6_nd_rx_requests(self, itf): + """Get IP6 ND RX request stats for and interface""" + return self.statistics["/net/ip6-nd/rx/requests"][:, itf.sw_if_index].sum() + def get_ip6_nd_tx_requests(self, itf): + """Get IP6 ND TX request stats for and interface""" + return self.statistics["/net/ip6-nd/tx/requests"][:, itf.sw_if_index].sum() + + def get_ip6_nd_rx_replies(self, itf): + """Get IP6 ND RX replies stats for and interface""" + return self.statistics["/net/ip6-nd/rx/replies"][:, itf.sw_if_index].sum() + + def get_ip6_nd_tx_replies(self, itf): + """Get IP6 ND TX replies stats for and interface""" + return self.statistics["/net/ip6-nd/tx/replies"][:, itf.sw_if_index].sum() + + +@tag_run_solo class TestIPv6(TestIPv6ND): - """ IPv6 Test Case """ + """IPv6 Test Case""" @classmethod def setUpClass(cls): super(TestIPv6, cls).setUpClass() + @classmethod + def tearDownClass(cls): + super(TestIPv6, cls).tearDownClass() + def setUp(self): """ Perform test setup before test case. @@ -178,7 +237,6 @@ class TestIPv6(TestIPv6ND): :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 """ @@ -201,8 +259,7 @@ class TestIPv6(TestIPv6ND): self.flows[self.pg2.sub_if] = [self.pg0, self.pg1.sub_if] # packet sizes - self.pg_if_packet_sizes = [64, 512, 1518, 9018] - self.sub_if_packet_sizes = [64, 512, 1518 + 4, 9018 + 4] + self.pg_if_packet_sizes = [64, 1500, 9020] self.interfaces = list(self.pg_interfaces) self.interfaces.extend(self.sub_interfaces) @@ -213,15 +270,12 @@ class TestIPv6(TestIPv6ND): i.config_ip6() i.resolve_ndp() - # config 2M FIB entries - self.config_fib_entries(200) - def tearDown(self): """Run standard test teardown and log ``show ip6 neighbors``.""" - for i in self.sub_interfaces: + for i in reversed(self.interfaces): i.unconfig_ip6() - i.ip6_disable() i.admin_down() + for i in self.sub_interfaces: i.remove_vpp_config() super(TestIPv6, self).tearDown() @@ -229,53 +283,55 @@ class TestIPv6(TestIPv6ND): 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. + def modify_packet(self, src_if, packet_size, pkt): + """Add load, set destination IP and extend packet to required packet + size for defined interface. - - *TODO:* check if the next-hop address shouldn't be remote address - instead of local address. + :param VppInterface src_if: Interface to create packet for. + :param int packet_size: Required packet size. + :param Scapy pkt: Packet to be modified. """ - n_int = len(self.interfaces) - percent = 0 - counter = 0.0 - dest_addr = inet_pton(AF_INET6, "fd02::1") - dest_addr_len = 128 - for i in self.interfaces: - next_hop_address = i.local_ip6n - 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 += 1 - if counter / count * 100 > percent: - self.logger.info("Configure %d FIB entries .. %d%% done" % - (count, percent)) - percent += 1 - - def create_stream(self, src_if, packet_sizes): + dst_if_idx = int(packet_size / 10 % 2) + dst_if = self.flows[src_if][dst_if_idx] + info = self.create_packet_info(src_if, dst_if) + payload = self.info_to_payload(info) + p = pkt / Raw(payload) + p[IPv6].dst = dst_if.remote_ip6 + info.data = p.copy() + if isinstance(src_if, VppSubInterface): + p = src_if.add_dot1_layer(p) + self.extend_packet(p, packet_size) + + return p + + def create_stream(self, src_if): """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] - info = self.create_packet_info(src_if, dst_if) - payload = self.info_to_payload(info) - p = (Ether(dst=src_if.local_mac, src=src_if.remote_mac) / - IPv6(src=src_if.remote_ip6, dst=dst_if.remote_ip6) / - UDP(sport=1234, dport=1234) / - Raw(payload)) - info.data = p.copy() - if isinstance(src_if, VppSubInterface): - p = src_if.add_dot1_layer(p) - size = packet_sizes[(i // 2) % len(packet_sizes)] - self.extend_packet(p, size) - pkts.append(p) + hdr_ext = 4 if isinstance(src_if, VppSubInterface) else 0 + pkt_tmpl = ( + Ether(dst=src_if.local_mac, src=src_if.remote_mac) + / IPv6(src=src_if.remote_ip6) + / inet6.UDP(sport=1234, dport=1234) + ) + + pkts = [ + self.modify_packet(src_if, i, pkt_tmpl) + for i in moves.range( + self.pg_if_packet_sizes[0], self.pg_if_packet_sizes[1], 10 + ) + ] + pkts_b = [ + self.modify_packet(src_if, i, pkt_tmpl) + for i in moves.range( + self.pg_if_packet_sizes[1] + hdr_ext, + self.pg_if_packet_sizes[2] + hdr_ext, + 50, + ) + ] + pkts.extend(pkts_b) + return pkts def verify_capture(self, dst_if, capture): @@ -291,7 +347,7 @@ class TestIPv6(TestIPv6ND): last_info[i.sw_if_index] = None is_sub_if = False dst_sw_if_index = dst_if.sw_if_index - if hasattr(dst_if, 'parent'): + if hasattr(dst_if, "parent"): is_sub_if = True for packet in capture: if is_sub_if: @@ -300,16 +356,17 @@ class TestIPv6(TestIPv6ND): self.assertTrue(Dot1Q not in packet) try: ip = packet[IPv6] - udp = packet[UDP] - payload_info = self.payload_to_info(str(packet[Raw])) + udp = packet[inet6.UDP] + payload_info = self.payload_to_info(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)) + "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]) + payload_info.src, dst_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) @@ -317,20 +374,43 @@ class TestIPv6(TestIPv6ND): # Check standard fields self.assertEqual(ip.src, saved_packet[IPv6].src) self.assertEqual(ip.dst, saved_packet[IPv6].dst) - self.assertEqual(udp.sport, saved_packet[UDP].sport) - self.assertEqual(udp.dport, saved_packet[UDP].dport) + self.assertEqual(udp.sport, saved_packet[inet6.UDP].sport) + self.assertEqual(udp.dport, saved_packet[inet6.UDP].dport) except: 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)) + 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), + ) + + def test_next_header_anomaly(self): + """IPv6 next header anomaly test + + Test scenario: + - ipv6 next header field = Fragment Header (44) + - next header is ICMPv6 Echo Request + - wait for reassembly + """ + pkt = ( + Ether(src=self.pg0.local_mac, dst=self.pg0.remote_mac) + / IPv6(src=self.pg0.remote_ip6, dst=self.pg0.local_ip6, nh=44) + / ICMPv6EchoRequest() + ) + + self.pg0.add_stream(pkt) + self.pg_start() + + # wait for reassembly + self.sleep(10) def test_fib(self): - """ IPv6 FIB test + """IPv6 FIB test Test scenario: - Create IPv6 stream for pg0 interface @@ -338,11 +418,11 @@ class TestIPv6(TestIPv6ND): - Send and verify received packets on each interface. """ - pkts = self.create_stream(self.pg0, self.pg_if_packet_sizes) + pkts = self.create_stream(self.pg0) self.pg0.add_stream(pkts) for i in self.sub_interfaces: - pkts = self.create_stream(i, self.sub_if_packet_sizes) + pkts = self.create_stream(i) i.parent.add_stream(pkts) self.pg_enable_capture(self.pg_interfaces) @@ -356,7 +436,7 @@ class TestIPv6(TestIPv6ND): self.verify_capture(i, pkts) def test_ns(self): - """ IPv6 Neighbour Solicitation Exceptions + """IPv6 Neighbour Solicitation Exceptions Test scenario: - Send an NS Sourced from an address not covered by the link sub-net @@ -364,21 +444,27 @@ class TestIPv6(TestIPv6ND): - Send NS for a target address the router does not onn. """ + n_rx_req_pg0 = self.get_ip6_nd_rx_requests(self.pg0) + n_tx_rep_pg0 = self.get_ip6_nd_tx_replies(self.pg0) + # # An NS from a non link source address # nsma = in6_getnsma(inet_pton(AF_INET6, self.pg0.local_ip6)) d = inet_ntop(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)) + 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") + self.pg0, pkts, "No response to NS source by address not on sub-net" + ) + self.assert_equal(self.get_ip6_nd_rx_requests(self.pg0), n_rx_req_pg0 + 1) # # An NS for sent to a solicited mcast group the router is @@ -388,15 +474,17 @@ class TestIPv6(TestIPv6ND): nsma = in6_getnsma(inet_pton(AF_INET6, "fd::ffff")) d = inet_ntop(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)) + 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") + self.pg0, pkts, "No response to NS sent to unjoined mcast address" + ) # # An NS whose target address is one the router does not own @@ -404,94 +492,142 @@ class TestIPv6(TestIPv6ND): nsma = in6_getnsma(inet_pton(AF_INET6, self.pg0.local_ip6)) d = inet_ntop(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)) + 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") + self.send_and_assert_no_replies( + self.pg0, pkts, "No response to NS for unknown target" + ) + self.assert_equal(self.get_ip6_nd_rx_requests(self.pg0), n_rx_req_pg0 + 2) # # A neighbor entry that has no associated FIB-entry # self.pg0.generate_remote_hosts(4) - nd_entry = VppNeighbor(self, - self.pg0.sw_if_index, - self.pg0.remote_hosts[2].mac, - self.pg0.remote_hosts[2].ip6, - af=AF_INET6, - is_no_fib_entry=1) + nd_entry = VppNeighbor( + self, + self.pg0.sw_if_index, + self.pg0.remote_hosts[2].mac, + self.pg0.remote_hosts[2].ip6, + is_no_fib_entry=1, + ) nd_entry.add_vpp_config() # # check we have the neighbor, but no route # - self.assertTrue(find_nbr(self, - self.pg0.sw_if_index, - self.pg0._remote_hosts[2].ip6, - inet=AF_INET6)) - self.assertFalse(find_route(self, - self.pg0._remote_hosts[2].ip6, - 128, - inet=AF_INET6)) + self.assertTrue( + find_nbr(self, self.pg0.sw_if_index, self.pg0._remote_hosts[2].ip6) + ) + self.assertFalse(find_route(self, self.pg0._remote_hosts[2].ip6, 128)) # # send an NS from a link local address to the interface's global # address # - p = (Ether(dst=in6_getnsmac(nsma), src=self.pg0.remote_mac) / - IPv6(dst=d, src=self.pg0._remote_hosts[2].ip6_ll) / - ICMPv6ND_NS(tgt=self.pg0.local_ip6) / - ICMPv6NDOptSrcLLAddr(lladdr=self.pg0.remote_mac)) + p = ( + Ether(dst=in6_getnsmac(nsma), src=self.pg0.remote_mac) + / IPv6(dst=d, src=self.pg0._remote_hosts[2].ip6_ll) + / ICMPv6ND_NS(tgt=self.pg0.local_ip6) + / ICMPv6NDOptSrcLLAddr(lladdr=self.pg0.remote_mac) + ) - self.send_and_expect_na(self.pg0, p, - "NS from link-local", - dst_ip=self.pg0._remote_hosts[2].ip6_ll, - tgt_ip=self.pg0.local_ip6) + self.send_and_expect_na( + self.pg0, + p, + "NS from link-local", + dst_ip=self.pg0._remote_hosts[2].ip6_ll, + tgt_ip=self.pg0.local_ip6, + ) + self.assert_equal(self.get_ip6_nd_rx_requests(self.pg0), n_rx_req_pg0 + 3) + self.assert_equal(self.get_ip6_nd_tx_replies(self.pg0), n_tx_rep_pg0 + 1) # # we should have learned an ND entry for the peer's link-local # but not inserted a route to it in the FIB # - self.assertTrue(find_nbr(self, - self.pg0.sw_if_index, - self.pg0._remote_hosts[2].ip6_ll, - inet=AF_INET6)) - self.assertFalse(find_route(self, - self.pg0._remote_hosts[2].ip6_ll, - 128, - inet=AF_INET6)) + self.assertTrue( + find_nbr(self, self.pg0.sw_if_index, self.pg0._remote_hosts[2].ip6_ll) + ) + self.assertFalse(find_route(self, self.pg0._remote_hosts[2].ip6_ll, 128)) # # An NS to the router's own Link-local # - p = (Ether(dst=in6_getnsmac(nsma), src=self.pg0.remote_mac) / - IPv6(dst=d, src=self.pg0._remote_hosts[3].ip6_ll) / - ICMPv6ND_NS(tgt=self.pg0.local_ip6_ll) / - ICMPv6NDOptSrcLLAddr(lladdr=self.pg0.remote_mac)) + p = ( + Ether(dst=in6_getnsmac(nsma), src=self.pg0.remote_mac) + / IPv6(dst=d, src=self.pg0._remote_hosts[3].ip6_ll) + / ICMPv6ND_NS(tgt=self.pg0.local_ip6_ll) + / ICMPv6NDOptSrcLLAddr(lladdr=self.pg0.remote_mac) + ) - self.send_and_expect_na(self.pg0, p, - "NS to/from link-local", - dst_ip=self.pg0._remote_hosts[3].ip6_ll, - tgt_ip=self.pg0.local_ip6_ll) + self.send_and_expect_na( + self.pg0, + p, + "NS to/from link-local", + dst_ip=self.pg0._remote_hosts[3].ip6_ll, + tgt_ip=self.pg0.local_ip6_ll, + ) + + # + # do not respond to a NS for the peer's address + # + p = ( + Ether(dst=in6_getnsmac(nsma), src=self.pg0.remote_mac) + / IPv6(dst=d, src=self.pg0._remote_hosts[3].ip6_ll) + / ICMPv6ND_NS(tgt=self.pg0._remote_hosts[3].ip6_ll) + / ICMPv6NDOptSrcLLAddr(lladdr=self.pg0.remote_mac) + ) + + self.send_and_assert_no_replies(self.pg0, p) # # we should have learned an ND entry for the peer's link-local # but not inserted a route to it in the FIB # - self.assertTrue(find_nbr(self, - self.pg0.sw_if_index, - self.pg0._remote_hosts[3].ip6_ll, - inet=AF_INET6)) - self.assertFalse(find_route(self, - self.pg0._remote_hosts[3].ip6_ll, - 128, - inet=AF_INET6)) + self.assertTrue( + find_nbr(self, self.pg0.sw_if_index, self.pg0._remote_hosts[3].ip6_ll) + ) + self.assertFalse(find_route(self, self.pg0._remote_hosts[3].ip6_ll, 128)) + + def test_nd_incomplete(self): + """IP6-ND Incomplete""" + self.pg1.generate_remote_hosts(3) + + p0 = ( + Ether(dst=self.pg0.local_mac, src=self.pg0.remote_mac) + / IPv6(src=self.pg0.remote_ip6, dst=self.pg1.remote_hosts[1].ip6) + / UDP(sport=1234, dport=1234) + / Raw() + ) + + # + # a packet to an unresolved destination generates an ND request + # + n_tx_req_pg1 = self.get_ip6_nd_tx_requests(self.pg1) + self.send_and_expect_ns(self.pg0, self.pg1, p0, self.pg1.remote_hosts[1].ip6) + self.assert_equal(self.get_ip6_nd_tx_requests(self.pg1), n_tx_req_pg1 + 1) + + # + # a reply to the request + # + self.assert_equal(self.get_ip6_nd_rx_replies(self.pg1), 0) + na = ( + Ether(dst=self.pg1.local_mac, src=self.pg1.remote_mac) + / IPv6(dst=self.pg1.local_ip6, src=self.pg1.remote_hosts[1].ip6) + / ICMPv6ND_NA(tgt=self.pg1.remote_hosts[1].ip6) + / ICMPv6NDOptSrcLLAddr(lladdr=self.pg1.remote_hosts[1].mac) + ) + self.send_and_assert_no_replies(self.pg1, [na]) + self.assert_equal(self.get_ip6_nd_rx_replies(self.pg1), 1) def test_ns_duplicates(self): - """ ND Duplicates""" + """ND Duplicates""" # # Generate some hosts on the LAN @@ -501,28 +637,30 @@ class TestIPv6(TestIPv6ND): # # Add host 1 on pg1 and pg2 # - ns_pg1 = VppNeighbor(self, - self.pg1.sw_if_index, - self.pg1.remote_hosts[1].mac, - self.pg1.remote_hosts[1].ip6, - af=AF_INET6) + ns_pg1 = VppNeighbor( + self, + self.pg1.sw_if_index, + self.pg1.remote_hosts[1].mac, + self.pg1.remote_hosts[1].ip6, + ) ns_pg1.add_vpp_config() - ns_pg2 = VppNeighbor(self, - self.pg2.sw_if_index, - self.pg2.remote_mac, - self.pg1.remote_hosts[1].ip6, - af=AF_INET6) + ns_pg2 = VppNeighbor( + self, + self.pg2.sw_if_index, + self.pg2.remote_mac, + self.pg1.remote_hosts[1].ip6, + ) ns_pg2.add_vpp_config() # # IP packet destined for pg1 remote host arrives on pg1 again. # - p = (Ether(dst=self.pg0.local_mac, - src=self.pg0.remote_mac) / - IPv6(src=self.pg0.remote_ip6, - dst=self.pg1.remote_hosts[1].ip6) / - UDP(sport=1234, dport=1234) / - Raw()) + p = ( + Ether(dst=self.pg0.local_mac, src=self.pg0.remote_mac) + / IPv6(src=self.pg0.remote_ip6, dst=self.pg1.remote_hosts[1].ip6) + / inet6.UDP(sport=1234, dport=1234) + / Raw() + ) self.pg0.add_stream(p) self.pg_enable_capture(self.pg_interfaces) @@ -530,20 +668,21 @@ class TestIPv6(TestIPv6ND): rx1 = self.pg1.get_capture(1) - self.verify_ip(rx1[0], - self.pg1.local_mac, - self.pg1.remote_hosts[1].mac, - self.pg0.remote_ip6, - self.pg1.remote_hosts[1].ip6) + self.verify_ip( + rx1[0], + self.pg1.local_mac, + self.pg1.remote_hosts[1].mac, + self.pg0.remote_ip6, + self.pg1.remote_hosts[1].ip6, + ) # # remove the duplicate on pg1 - # packet stream shoud generate NSs out of pg1 + # packet stream should generate NSs out of pg1 # ns_pg1.remove_vpp_config() - self.send_and_expect_ns(self.pg0, self.pg1, - p, self.pg1.remote_hosts[1].ip6) + self.send_and_expect_ns(self.pg0, self.pg1, p, self.pg1.remote_hosts[1].ip6) # # Add it back @@ -556,15 +695,19 @@ class TestIPv6(TestIPv6ND): rx1 = self.pg1.get_capture(1) - self.verify_ip(rx1[0], - self.pg1.local_mac, - self.pg1.remote_hosts[1].mac, - self.pg0.remote_ip6, - self.pg1.remote_hosts[1].ip6) + self.verify_ip( + rx1[0], + self.pg1.local_mac, + self.pg1.remote_hosts[1].mac, + self.pg0.remote_ip6, + self.pg1.remote_hosts[1].ip6, + ) - def validate_ra(self, intf, rx, dst_ip=None, mtu=9000, pi_opt=None): + def validate_ra(self, intf, rx, dst_ip=None, src_ip=None, mtu=9000, pi_opt=None): if not dst_ip: dst_ip = intf.remote_ip6 + if not src_ip: + src_ip = mk_ll_addr(intf.local_mac) # unicasted packets must come to the unicast mac self.assertEqual(rx[Ether].dst, intf.remote_mac) @@ -574,13 +717,11 @@ class TestIPv6(TestIPv6ND): # 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(dst_ip)) + self.assertEqual(in6_ptop(rx[IPv6].dst), in6_ptop(dst_ip)) # and come from the router's link local self.assertTrue(in6_islladdr(rx[IPv6].src)) - self.assertEqual(in6_ptop(rx[IPv6].src), - in6_ptop(mk_ll_addr(intf.local_mac))) + self.assertEqual(in6_ptop(rx[IPv6].src), in6_ptop(src_ip)) # it should contain the links MTU ra = rx[ICMPv6ND_RA] @@ -599,22 +740,34 @@ class TestIPv6(TestIPv6ND): # the options are nested in the scapy packet in way that i cannot # decipher how to decode. this 1st layer of option always returns # nested classes, so a direct obj1=obj2 comparison always fails. - # however, the getlayer(.., 2) does give one instnace. - # so we cheat here and construct a new opt instnace for comparison - rd = ICMPv6NDOptPrefixInfo(prefixlen=raos.prefixlen, - prefix=raos.prefix, - L=raos.L, - A=raos.A) + # however, the getlayer(.., 2) does give one instance. + # so we cheat here and construct a new opt instance for comparison + rd = ICMPv6NDOptPrefixInfo( + prefixlen=raos.prefixlen, prefix=raos.prefix, L=raos.L, A=raos.A + ) if type(pi_opt) is list: for ii in range(len(pi_opt)): self.assertEqual(pi_opt[ii], rd) - rd = rx.getlayer(ICMPv6NDOptPrefixInfo, ii+2) + rd = rx.getlayer(ICMPv6NDOptPrefixInfo, ii + 2) else: - self.assertEqual(pi_opt, raos) - - def send_and_expect_ra(self, intf, pkts, remark, dst_ip=None, - filter_out_fn=is_ipv6_misc, - opt=None): + self.assertEqual( + pi_opt, + raos, + "Expected: %s, received: %s" + % (pi_opt.show(dump=True), raos.show(dump=True)), + ) + + def send_and_expect_ra( + self, + intf, + pkts, + remark, + dst_ip=None, + filter_out_fn=is_ipv6_misc, + opt=None, + src_ip=None, + ): + self.vapi.cli("clear trace") intf.add_stream(pkts) self.pg_enable_capture(self.pg_interfaces) self.pg_start() @@ -622,14 +775,95 @@ class TestIPv6(TestIPv6ND): self.assertEqual(len(rx), 1) rx = rx[0] - self.validate_ra(intf, rx, dst_ip, pi_opt=opt) + self.validate_ra(intf, rx, dst_ip, src_ip=src_ip, pi_opt=opt) + + def test_ip6_ra_dump(self): + """IPv6 RA dump""" + + # Dump IPv6 RA for all interfaces + ip6_ra_dump = self.vapi.sw_interface_ip6nd_ra_dump(sw_if_index=0xFFFFFFFF) + self.assertEqual(len(ip6_ra_dump), len(self.interfaces)) + + for ip6_ra in ip6_ra_dump: + self.assertFalse(ip6_ra.send_radv) + self.assertEqual(ip6_ra.n_prefixes, 0) + self.assertEqual(len(ip6_ra.prefixes), 0) + self.assertEqual(ip6_ra.last_radv_time, 0.0) + self.assertEqual(ip6_ra.last_multicast_time, 0.0) + self.assertEqual(ip6_ra.next_multicast_time, 0.0) + self.assertEqual(ip6_ra.n_advertisements_sent, 0) + self.assertEqual(ip6_ra.n_solicitations_rcvd, 0) + self.assertEqual(ip6_ra.n_solicitations_dropped, 0) + + # Enable sending IPv6 RA for an interface + self.pg0.ip6_ra_config(no=1, suppress=1) + + # Add IPv6 RA prefixes for the interface + pfx0 = IPv6Network( + "%s/%s" % (self.pg0.local_ip6, self.pg0.local_ip6_prefix_len), strict=False + ) + pfx1 = IPv6Network("fafa::/96") + self.pg0.ip6_ra_prefix(pfx0, off_link=0, no_autoconfig=0) + self.pg0.ip6_ra_prefix(pfx1, off_link=1, no_autoconfig=1) + + # Wait for multicast IPv6 RA + self.sleep(1) + + # Dump IPv6 RA for the interface + ip6_ra_dump = self.vapi.sw_interface_ip6nd_ra_dump( + sw_if_index=self.pg0.sw_if_index + ) + self.assertEqual(len(ip6_ra_dump), 1) + ip6_ra = ip6_ra_dump[0] + + self.assertEqual(ip6_ra.sw_if_index, self.pg0.sw_if_index) + self.assertTrue(ip6_ra.send_radv) + self.assertEqual(ip6_ra.n_prefixes, 2) + self.assertEqual(len(ip6_ra.prefixes), 2) + self.assertEqual(ip6_ra.last_radv_time, 0.0) + self.assertGreater(ip6_ra.last_multicast_time, 0.0) + self.assertGreater(ip6_ra.next_multicast_time, 0.0) + self.assertGreater(ip6_ra.n_advertisements_sent, 0) + self.assertEqual(ip6_ra.n_solicitations_rcvd, 0) + self.assertEqual(ip6_ra.n_solicitations_dropped, 0) + + self.assertEqual(ip6_ra.prefixes[0].prefix, pfx0) + self.assertTrue(ip6_ra.prefixes[0].onlink_flag) + self.assertTrue(ip6_ra.prefixes[0].autonomous_flag) + self.assertFalse(ip6_ra.prefixes[0].no_advertise) + + self.assertEqual(ip6_ra.prefixes[1].prefix, pfx1) + self.assertFalse(ip6_ra.prefixes[1].onlink_flag) + self.assertFalse(ip6_ra.prefixes[1].autonomous_flag) + self.assertFalse(ip6_ra.prefixes[1].no_advertise) + + # Reset sending IPv6 RA for the interface + self.pg0.ip6_ra_config(suppress=1) + + # Remove IPv6 RA prefixes for the interface + self.pg0.ip6_ra_prefix(pfx0, is_no=1) + self.pg0.ip6_ra_prefix(pfx1, is_no=1) + + # Dump IPv6 RA for the interface + ip6_ra_dump = self.vapi.sw_interface_ip6nd_ra_dump( + sw_if_index=self.pg0.sw_if_index + ) + self.assertEqual(len(ip6_ra_dump), 1) + ip6_ra = ip6_ra_dump[0] + + self.assertEqual(ip6_ra.sw_if_index, self.pg0.sw_if_index) + self.assertFalse(ip6_ra.send_radv) + self.assertEqual(ip6_ra.n_prefixes, 0) + self.assertEqual(len(ip6_ra.prefixes), 0) def test_rs(self): - """ IPv6 Router Solicitation Exceptions + """IPv6 Router Solicitation Exceptions Test scenario: """ + self.pg0.ip6_ra_config(no=1, suppress=1) + # # Before we begin change the IPv6 RA responses to use the unicast # address - that way we will not confuse them with the periodic @@ -644,9 +878,11 @@ class TestIPv6(TestIPv6ND): # 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()) + 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") @@ -656,7 +892,8 @@ class TestIPv6(TestIPv6ND): 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, + # When we reconfigure 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. # @@ -667,39 +904,55 @@ class TestIPv6(TestIPv6ND): # 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()) + 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") + 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()) + 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", dst_ip=ll) + + # + # Source an RS from a link local address + # Ensure suppress also applies to solicited RS + # + self.pg0.ip6_ra_config(send_unicast=1, suppress=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", - dst_ip=ll) + self.send_and_assert_no_replies(self.pg0, pkts, "Suppressed RS from link-local") # # Send the RS multicast # + self.pg0.ip6_ra_config(no=1, suppress=1) # Reset suppress flag to zero self.pg0.ip6_ra_config(send_unicast=1) dmac = in6_getnsmac(inet_pton(AF_INET6, "ff02::2")) ll = mk_ll_addr(self.pg0.remote_mac) - p = (Ether(dst=dmac, src=self.pg0.remote_mac) / - IPv6(dst="ff02::2", src=ll) / - ICMPv6ND_RS()) + p = ( + Ether(dst=dmac, src=self.pg0.remote_mac) + / IPv6(dst="ff02::2", src=ll) + / ICMPv6ND_RS() + ) pkts = [p] - self.send_and_expect_ra(self.pg0, pkts, - "RS sourced from link-local", - dst_ip=ll) + self.send_and_expect_ra(self.pg0, pkts, "RS sourced from link-local", dst_ip=ll) # # Source from the unspecified address ::. This happens when the RS @@ -709,868 +962,3048 @@ class TestIPv6(TestIPv6ND): # 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, suppress=1) - p = (Ether(dst=dmac, src=self.pg0.remote_mac) / - IPv6(dst="ff02::2", src="::") / - ICMPv6ND_RS()) + self.pg0.ip6_ra_config(send_unicast=1) + p = ( + Ether(dst=dmac, src=self.pg0.remote_mac) + / IPv6(dst="ff02::2", src="::") + / ICMPv6ND_RS() + ) pkts = [p] - self.send_and_expect_ra(self.pg0, pkts, - "RS sourced from unspecified", - dst_ip="ff02::1", - filter_out_fn=None) + self.send_and_expect_ra( + self.pg0, + pkts, + "RS sourced from unspecified", + dst_ip="ff02::1", + filter_out_fn=None, + ) # # Configure The RA to announce the links prefix # - self.pg0.ip6_ra_prefix(self.pg0.local_ip6n, - self.pg0.local_ip6_prefix_len) + self.pg0.ip6_ra_prefix( + "%s/%s" % (self.pg0.local_ip6, self.pg0.local_ip6_prefix_len) + ) # # RAs should now contain the prefix information option # - opt = ICMPv6NDOptPrefixInfo(prefixlen=self.pg0.local_ip6_prefix_len, - prefix=self.pg0.local_ip6, - L=1, - A=1) + opt = ICMPv6NDOptPrefixInfo( + prefixlen=self.pg0.local_ip6_prefix_len, prefix=self.pg0.local_ip6, L=1, A=1 + ) 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()) - self.send_and_expect_ra(self.pg0, p, - "RA with prefix-info", - dst_ip=ll, - opt=opt) + p = ( + Ether(dst=self.pg0.local_mac, src=self.pg0.remote_mac) + / IPv6(dst=self.pg0.local_ip6, src=ll) + / ICMPv6ND_RS() + ) + self.send_and_expect_ra(self.pg0, p, "RA with prefix-info", dst_ip=ll, opt=opt) # # Change the prefix info to not off-link # L-flag is clear # - self.pg0.ip6_ra_prefix(self.pg0.local_ip6n, - self.pg0.local_ip6_prefix_len, - off_link=1) + self.pg0.ip6_ra_prefix( + "%s/%s" % (self.pg0.local_ip6, self.pg0.local_ip6_prefix_len), off_link=1 + ) - opt = ICMPv6NDOptPrefixInfo(prefixlen=self.pg0.local_ip6_prefix_len, - prefix=self.pg0.local_ip6, - L=0, - A=1) + opt = ICMPv6NDOptPrefixInfo( + prefixlen=self.pg0.local_ip6_prefix_len, prefix=self.pg0.local_ip6, L=0, A=1 + ) self.pg0.ip6_ra_config(send_unicast=1) - self.send_and_expect_ra(self.pg0, p, - "RA with Prefix info with L-flag=0", - dst_ip=ll, - opt=opt) + self.send_and_expect_ra( + self.pg0, p, "RA with Prefix info with L-flag=0", dst_ip=ll, opt=opt + ) # # Change the prefix info to not off-link, no-autoconfig # L and A flag are clear in the advert # - self.pg0.ip6_ra_prefix(self.pg0.local_ip6n, - self.pg0.local_ip6_prefix_len, - off_link=1, - no_autoconfig=1) + self.pg0.ip6_ra_prefix( + "%s/%s" % (self.pg0.local_ip6, self.pg0.local_ip6_prefix_len), + off_link=1, + no_autoconfig=1, + ) - opt = ICMPv6NDOptPrefixInfo(prefixlen=self.pg0.local_ip6_prefix_len, - prefix=self.pg0.local_ip6, - L=0, - A=0) + opt = ICMPv6NDOptPrefixInfo( + prefixlen=self.pg0.local_ip6_prefix_len, prefix=self.pg0.local_ip6, L=0, A=0 + ) self.pg0.ip6_ra_config(send_unicast=1) - self.send_and_expect_ra(self.pg0, p, - "RA with Prefix info with A & L-flag=0", - dst_ip=ll, - opt=opt) + self.send_and_expect_ra( + self.pg0, p, "RA with Prefix info with A & L-flag=0", dst_ip=ll, opt=opt + ) # # Change the flag settings back to the defaults # L and A flag are set in the advert # - self.pg0.ip6_ra_prefix(self.pg0.local_ip6n, - self.pg0.local_ip6_prefix_len) + self.pg0.ip6_ra_prefix( + "%s/%s" % (self.pg0.local_ip6, self.pg0.local_ip6_prefix_len) + ) - opt = ICMPv6NDOptPrefixInfo(prefixlen=self.pg0.local_ip6_prefix_len, - prefix=self.pg0.local_ip6, - L=1, - A=1) + opt = ICMPv6NDOptPrefixInfo( + prefixlen=self.pg0.local_ip6_prefix_len, prefix=self.pg0.local_ip6, L=1, A=1 + ) self.pg0.ip6_ra_config(send_unicast=1) - self.send_and_expect_ra(self.pg0, p, - "RA with Prefix info", - dst_ip=ll, - opt=opt) + self.send_and_expect_ra(self.pg0, p, "RA with Prefix info", dst_ip=ll, opt=opt) # # Change the prefix info to not off-link, no-autoconfig # L and A flag are clear in the advert # - self.pg0.ip6_ra_prefix(self.pg0.local_ip6n, - self.pg0.local_ip6_prefix_len, - off_link=1, - no_autoconfig=1) + self.pg0.ip6_ra_prefix( + "%s/%s" % (self.pg0.local_ip6, self.pg0.local_ip6_prefix_len), + off_link=1, + no_autoconfig=1, + ) - opt = ICMPv6NDOptPrefixInfo(prefixlen=self.pg0.local_ip6_prefix_len, - prefix=self.pg0.local_ip6, - L=0, - A=0) + opt = ICMPv6NDOptPrefixInfo( + prefixlen=self.pg0.local_ip6_prefix_len, prefix=self.pg0.local_ip6, L=0, A=0 + ) self.pg0.ip6_ra_config(send_unicast=1) - self.send_and_expect_ra(self.pg0, p, - "RA with Prefix info with A & L-flag=0", - dst_ip=ll, - opt=opt) + self.send_and_expect_ra( + self.pg0, p, "RA with Prefix info with A & L-flag=0", dst_ip=ll, opt=opt + ) # - # Use the reset to defults option to revert to defaults + # Use the reset to defaults option to revert to defaults # L and A flag are clear in the advert # - self.pg0.ip6_ra_prefix(self.pg0.local_ip6n, - self.pg0.local_ip6_prefix_len, - use_default=1) + self.pg0.ip6_ra_prefix( + "%s/%s" % (self.pg0.local_ip6, self.pg0.local_ip6_prefix_len), use_default=1 + ) - opt = ICMPv6NDOptPrefixInfo(prefixlen=self.pg0.local_ip6_prefix_len, - prefix=self.pg0.local_ip6, - L=1, - A=1) + opt = ICMPv6NDOptPrefixInfo( + prefixlen=self.pg0.local_ip6_prefix_len, prefix=self.pg0.local_ip6, L=1, A=1 + ) self.pg0.ip6_ra_config(send_unicast=1) - self.send_and_expect_ra(self.pg0, p, - "RA with Prefix reverted to defaults", - dst_ip=ll, - opt=opt) + self.send_and_expect_ra( + self.pg0, p, "RA with Prefix reverted to defaults", dst_ip=ll, opt=opt + ) # # Advertise Another prefix. With no L-flag/A-flag # - self.pg0.ip6_ra_prefix(self.pg1.local_ip6n, - self.pg1.local_ip6_prefix_len, - off_link=1, - no_autoconfig=1) - - opt = [ICMPv6NDOptPrefixInfo(prefixlen=self.pg0.local_ip6_prefix_len, - prefix=self.pg0.local_ip6, - L=1, - A=1), - ICMPv6NDOptPrefixInfo(prefixlen=self.pg1.local_ip6_prefix_len, - prefix=self.pg1.local_ip6, - L=0, - A=0)] + self.pg0.ip6_ra_prefix( + "%s/%s" % (self.pg1.local_ip6, self.pg1.local_ip6_prefix_len), + off_link=1, + no_autoconfig=1, + ) + + opt = [ + ICMPv6NDOptPrefixInfo( + prefixlen=self.pg0.local_ip6_prefix_len, + prefix=self.pg0.local_ip6, + L=1, + A=1, + ), + ICMPv6NDOptPrefixInfo( + prefixlen=self.pg1.local_ip6_prefix_len, + prefix=self.pg1.local_ip6, + L=0, + A=0, + ), + ] 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()) - self.send_and_expect_ra(self.pg0, p, - "RA with multiple Prefix infos", - dst_ip=ll, - opt=opt) + p = ( + Ether(dst=self.pg0.local_mac, src=self.pg0.remote_mac) + / IPv6(dst=self.pg0.local_ip6, src=ll) + / ICMPv6ND_RS() + ) + self.send_and_expect_ra( + self.pg0, p, "RA with multiple Prefix infos", dst_ip=ll, opt=opt + ) # - # Remove the first refix-info - expect the second is still in the + # Remove the first prefix-info - expect the second is still in the # advert # - self.pg0.ip6_ra_prefix(self.pg0.local_ip6n, - self.pg0.local_ip6_prefix_len, - is_no=1) + self.pg0.ip6_ra_prefix( + "%s/%s" % (self.pg0.local_ip6, self.pg0.local_ip6_prefix_len), is_no=1 + ) - opt = ICMPv6NDOptPrefixInfo(prefixlen=self.pg1.local_ip6_prefix_len, - prefix=self.pg1.local_ip6, - L=0, - A=0) + opt = ICMPv6NDOptPrefixInfo( + prefixlen=self.pg1.local_ip6_prefix_len, prefix=self.pg1.local_ip6, L=0, A=0 + ) self.pg0.ip6_ra_config(send_unicast=1) - self.send_and_expect_ra(self.pg0, p, - "RA with Prefix reverted to defaults", - dst_ip=ll, - opt=opt) + self.send_and_expect_ra( + self.pg0, p, "RA with Prefix reverted to defaults", dst_ip=ll, opt=opt + ) + + # + # Remove the second prefix-info - expect no prefix-info in the adverts + # + self.pg0.ip6_ra_prefix( + "%s/%s" % (self.pg1.local_ip6, self.pg1.local_ip6_prefix_len), is_no=1 + ) # - # Remove the second prefix-info - expect no prefix-info i nthe adverts + # change the link's link local, so we know that works too. # - self.pg0.ip6_ra_prefix(self.pg1.local_ip6n, - self.pg1.local_ip6_prefix_len, - is_no=1) + self.vapi.sw_interface_ip6_set_link_local_address( + sw_if_index=self.pg0.sw_if_index, ip="fe80::88" + ) self.pg0.ip6_ra_config(send_unicast=1) - self.send_and_expect_ra(self.pg0, p, - "RA with Prefix reverted to defaults", - dst_ip=ll) + self.send_and_expect_ra( + self.pg0, + p, + "RA with Prefix reverted to defaults", + dst_ip=ll, + src_ip="fe80::88", + ) # # Reset the periodic advertisements back to default values # - self.pg0.ip6_ra_config(no=1, suppress=1, send_unicast=0) + self.pg0.ip6_ra_config(suppress=1) + self.pg0.ip6_ra_config(no=1, send_unicast=1) + + def test_mld(self): + """MLD Report""" + # + # test one MLD is sent after applying an IPv6 Address on an interface + # + self.pg_enable_capture(self.pg_interfaces) + self.pg_start() + subitf = VppDot1QSubint(self, self.pg1, 99) + self.interfaces.append(subitf) + self.sub_interfaces.append(subitf) -class IPv6NDProxyTest(TestIPv6ND): - """ IPv6 ND ProxyTest Case """ + subitf.admin_up() + subitf.config_ip6() + + rxs = self.pg1._get_capture(timeout=4, filter_out_fn=None) + + # + # hunt for the MLD on vlan 99 + # + for rx in rxs: + # make sure ipv6 packets with hop by hop options have + # correct checksums + self.assert_packet_checksums_valid(rx) + if ( + rx.haslayer(IPv6ExtHdrHopByHop) + and rx.haslayer(Dot1Q) + and rx[Dot1Q].vlan == 99 + ): + mld = rx[ICMPv6MLReport2] + + self.assertEqual(mld.records_number, 4) + + +class TestIPv6RouteLookup(VppTestCase): + """IPv6 Route Lookup Test Case""" + + routes = [] + + def route_lookup(self, prefix, exact): + return self.vapi.api( + self.vapi.papi.ip_route_lookup, + { + "table_id": 0, + "exact": exact, + "prefix": prefix, + }, + ) + + @classmethod + def setUpClass(cls): + super(TestIPv6RouteLookup, cls).setUpClass() + + @classmethod + def tearDownClass(cls): + super(TestIPv6RouteLookup, cls).tearDownClass() def setUp(self): - super(IPv6NDProxyTest, self).setUp() + super(TestIPv6RouteLookup, self).setUp() - # create 3 pg interfaces - self.create_pg_interfaces(range(3)) + drop_nh = VppRoutePath("::1", 0xFFFFFFFF, type=FibPathType.FIB_PATH_TYPE_DROP) - # pg0 is the master interface, with the configured subnet - self.pg0.admin_up() - self.pg0.config_ip6() - self.pg0.resolve_ndp() + # Add 3 routes + r = VppIpRoute(self, "2001:1111::", 32, [drop_nh]) + r.add_vpp_config() + self.routes.append(r) - self.pg1.ip6_enable() - self.pg2.ip6_enable() + r = VppIpRoute(self, "2001:1111:2222::", 48, [drop_nh]) + r.add_vpp_config() + self.routes.append(r) + + r = VppIpRoute(self, "2001:1111:2222::1", 128, [drop_nh]) + r.add_vpp_config() + self.routes.append(r) def tearDown(self): - super(IPv6NDProxyTest, self).tearDown() + # Remove the routes we added + for r in self.routes: + r.remove_vpp_config() - def test_nd_proxy(self): - """ IPv6 Proxy ND """ + super(TestIPv6RouteLookup, self).tearDown() - # - # Generate some hosts in the subnet that we are proxying - # - self.pg0.generate_remote_hosts(8) + def test_exact_match(self): + # Verify we find the host route + prefix = "2001:1111:2222::1/128" + result = self.route_lookup(prefix, True) + assert prefix == str(result.route.prefix) - nsma = in6_getnsma(inet_pton(AF_INET6, self.pg0.local_ip6)) - d = inet_ntop(AF_INET6, nsma) + # Verify we find a middle prefix route + prefix = "2001:1111:2222::/48" + result = self.route_lookup(prefix, True) + assert prefix == str(result.route.prefix) - # - # Send an NS for one of those remote hosts on one of the proxy links - # expect no response since it's from an address that is not - # on the link that has the prefix configured - # - ns_pg1 = (Ether(dst=in6_getnsmac(nsma), src=self.pg1.remote_mac) / - IPv6(dst=d, src=self.pg0._remote_hosts[2].ip6) / - ICMPv6ND_NS(tgt=self.pg0.local_ip6) / - ICMPv6NDOptSrcLLAddr(lladdr=self.pg0._remote_hosts[2].mac)) + # Verify we do not find an available LPM. + with self.vapi.assert_negative_api_retval(): + self.route_lookup("2001::2/128", True) - self.send_and_assert_no_replies(self.pg1, ns_pg1, "Off link NS") + def test_longest_prefix_match(self): + # verify we find lpm + lpm_prefix = "2001:1111:2222::/48" + result = self.route_lookup("2001:1111:2222::2/128", False) + assert lpm_prefix == str(result.route.prefix) - # - # Add proxy support for the host - # - self.vapi.ip6_nd_proxy( - inet_pton(AF_INET6, self.pg0._remote_hosts[2].ip6), - self.pg1.sw_if_index) + # Verify we find the exact when not requested + result = self.route_lookup(lpm_prefix, False) + assert lpm_prefix == str(result.route.prefix) - # - # try that NS again. this time we expect an NA back - # - self.send_and_expect_na(self.pg1, ns_pg1, - "NS to proxy entry", - dst_ip=self.pg0._remote_hosts[2].ip6, - tgt_ip=self.pg0.local_ip6) + # Can't seem to delete the default route so no negative LPM test. - # - # ... and that we have an entry in the ND cache - # - self.assertTrue(find_nbr(self, - self.pg1.sw_if_index, - self.pg0._remote_hosts[2].ip6, - inet=AF_INET6)) - # - # ... and we can route traffic to it - # - t = (Ether(dst=self.pg0.local_mac, src=self.pg0.remote_mac) / - IPv6(dst=self.pg0._remote_hosts[2].ip6, - src=self.pg0.remote_ip6) / - UDP(sport=10000, dport=20000) / - Raw('\xa5' * 100)) +class TestIPv6IfAddrRoute(VppTestCase): + """IPv6 Interface Addr Route Test Case""" - self.pg0.add_stream(t) - self.pg_enable_capture(self.pg_interfaces) - self.pg_start() - rx = self.pg1.get_capture(1) - rx = rx[0] + @classmethod + def setUpClass(cls): + super(TestIPv6IfAddrRoute, cls).setUpClass() - self.assertEqual(rx[Ether].dst, self.pg0._remote_hosts[2].mac) - self.assertEqual(rx[Ether].src, self.pg1.local_mac) + @classmethod + def tearDownClass(cls): + super(TestIPv6IfAddrRoute, cls).tearDownClass() - self.assertEqual(rx[IPv6].src, t[IPv6].src) - self.assertEqual(rx[IPv6].dst, t[IPv6].dst) + def setUp(self): + super(TestIPv6IfAddrRoute, self).setUp() - # - # Test we proxy for the host on the main interface - # - ns_pg0 = (Ether(dst=in6_getnsmac(nsma), src=self.pg0.remote_mac) / - IPv6(dst=d, src=self.pg0.remote_ip6) / - ICMPv6ND_NS(tgt=self.pg0._remote_hosts[2].ip6) / - ICMPv6NDOptSrcLLAddr(lladdr=self.pg0.remote_mac)) + # create 1 pg interface + self.create_pg_interfaces(range(1)) - self.send_and_expect_na(self.pg0, ns_pg0, - "NS to proxy entry on main", - tgt_ip=self.pg0._remote_hosts[2].ip6, - dst_ip=self.pg0.remote_ip6) + for i in self.pg_interfaces: + i.admin_up() + i.config_ip6() + i.resolve_ndp() - # - # Setup and resolve proxy for another host on another interface - # - ns_pg2 = (Ether(dst=in6_getnsmac(nsma), src=self.pg2.remote_mac) / - IPv6(dst=d, src=self.pg0._remote_hosts[3].ip6) / - ICMPv6ND_NS(tgt=self.pg0.local_ip6) / - ICMPv6NDOptSrcLLAddr(lladdr=self.pg0._remote_hosts[2].mac)) + def tearDown(self): + super(TestIPv6IfAddrRoute, self).tearDown() + for i in self.pg_interfaces: + i.unconfig_ip6() + i.admin_down() - self.vapi.ip6_nd_proxy( - inet_pton(AF_INET6, self.pg0._remote_hosts[3].ip6), - self.pg2.sw_if_index) + def test_ipv6_ifaddrs_same_prefix(self): + """IPv6 Interface Addresses Same Prefix test - self.send_and_expect_na(self.pg2, ns_pg2, - "NS to proxy entry other interface", - dst_ip=self.pg0._remote_hosts[3].ip6, - tgt_ip=self.pg0.local_ip6) + Test scenario: - self.assertTrue(find_nbr(self, - self.pg2.sw_if_index, - self.pg0._remote_hosts[3].ip6, - inet=AF_INET6)) + - Verify no route in FIB for prefix 2001:10::/64 + - Configure IPv4 address 2001:10::10/64 on an interface + - Verify route in FIB for prefix 2001:10::/64 + - Configure IPv4 address 2001:10::20/64 on an interface + - Delete 2001:10::10/64 from interface + - Verify route in FIB for prefix 2001:10::/64 + - Delete 2001:10::20/64 from interface + - Verify no route in FIB for prefix 2001:10::/64 + """ - # - # hosts can communicate. pg2->pg1 - # - t2 = (Ether(dst=self.pg2.local_mac, - src=self.pg0.remote_hosts[3].mac) / - IPv6(dst=self.pg0._remote_hosts[2].ip6, - src=self.pg0._remote_hosts[3].ip6) / - UDP(sport=10000, dport=20000) / - Raw('\xa5' * 100)) + addr1 = "2001:10::10" + addr2 = "2001:10::20" - self.pg2.add_stream(t2) - self.pg_enable_capture(self.pg_interfaces) - self.pg_start() - rx = self.pg1.get_capture(1) - rx = rx[0] + if_addr1 = VppIpInterfaceAddress(self, self.pg0, addr1, 64) + if_addr2 = VppIpInterfaceAddress(self, self.pg0, addr2, 64) + self.assertFalse(if_addr1.query_vpp_config()) + self.assertFalse(find_route(self, addr1, 128)) + self.assertFalse(find_route(self, addr2, 128)) - self.assertEqual(rx[Ether].dst, self.pg0._remote_hosts[2].mac) - self.assertEqual(rx[Ether].src, self.pg1.local_mac) + # configure first address, verify route present + if_addr1.add_vpp_config() + self.assertTrue(if_addr1.query_vpp_config()) + self.assertTrue(find_route(self, addr1, 128)) + self.assertFalse(find_route(self, addr2, 128)) - self.assertEqual(rx[IPv6].src, t2[IPv6].src) - self.assertEqual(rx[IPv6].dst, t2[IPv6].dst) + # configure second address, delete first, verify route not removed + if_addr2.add_vpp_config() + if_addr1.remove_vpp_config() + self.assertFalse(if_addr1.query_vpp_config()) + self.assertTrue(if_addr2.query_vpp_config()) + self.assertFalse(find_route(self, addr1, 128)) + self.assertTrue(find_route(self, addr2, 128)) - # - # remove the proxy configs - # - self.vapi.ip6_nd_proxy( - inet_pton(AF_INET6, self.pg0._remote_hosts[2].ip6), - self.pg1.sw_if_index, - is_del=1) - self.vapi.ip6_nd_proxy( - inet_pton(AF_INET6, self.pg0._remote_hosts[3].ip6), - self.pg2.sw_if_index, - is_del=1) + # delete second address, verify route removed + if_addr2.remove_vpp_config() + self.assertFalse(if_addr1.query_vpp_config()) + self.assertFalse(find_route(self, addr1, 128)) + self.assertFalse(find_route(self, addr2, 128)) - self.assertFalse(find_nbr(self, - self.pg2.sw_if_index, - self.pg0._remote_hosts[3].ip6, - inet=AF_INET6)) - self.assertFalse(find_nbr(self, - self.pg1.sw_if_index, - self.pg0._remote_hosts[2].ip6, - inet=AF_INET6)) + def test_ipv6_ifaddr_del(self): + """Delete an interface address that does not exist""" - # - # no longer proxy-ing... - # - self.send_and_assert_no_replies(self.pg0, ns_pg0, "Proxy unconfigured") - self.send_and_assert_no_replies(self.pg1, ns_pg1, "Proxy unconfigured") - self.send_and_assert_no_replies(self.pg2, ns_pg2, "Proxy unconfigured") + loopbacks = self.create_loopback_interfaces(1) + lo = self.lo_interfaces[0] + + lo.config_ip6() + lo.admin_up() # - # no longer forwarding. traffic generates NS out of the glean/main - # interface + # try and remove pg0's subnet from lo # - self.pg2.add_stream(t2) - self.pg_enable_capture(self.pg_interfaces) - self.pg_start() + with self.vapi.assert_negative_api_retval(): + self.vapi.sw_interface_add_del_address( + sw_if_index=lo.sw_if_index, prefix=self.pg0.local_ip6_prefix, is_add=0 + ) - rx = self.pg0.get_capture(1) - self.assertTrue(rx[0].haslayer(ICMPv6ND_NS)) +class TestICMPv6Echo(VppTestCase): + """ICMPv6 Echo Test Case""" + @classmethod + def setUpClass(cls): + super(TestICMPv6Echo, cls).setUpClass() -class TestIPNull(VppTestCase): - """ IPv6 routes via NULL """ + @classmethod + def tearDownClass(cls): + super(TestICMPv6Echo, cls).tearDownClass() def setUp(self): - super(TestIPNull, self).setUp() + super(TestICMPv6Echo, self).setUp() - # create 2 pg interfaces + # create 1 pg interface self.create_pg_interfaces(range(1)) for i in self.pg_interfaces: i.admin_up() i.config_ip6() + i.resolve_ndp(link_layer=True) i.resolve_ndp() def tearDown(self): - super(TestIPNull, self).tearDown() + super(TestICMPv6Echo, self).tearDown() for i in self.pg_interfaces: i.unconfig_ip6() i.admin_down() - def test_ip_null(self): - """ IP NULL route """ - - p = (Ether(src=self.pg0.remote_mac, - dst=self.pg0.local_mac) / - IPv6(src=self.pg0.remote_ip6, dst="2001::1") / - UDP(sport=1234, dport=1234) / - Raw('\xa5' * 100)) - - # - # A route via IP NULL that will reply with ICMP unreachables - # - ip_unreach = VppIpRoute(self, "2001::", 64, [], is_unreach=1, is_ip6=1) - ip_unreach.add_vpp_config() - - self.pg0.add_stream(p) - self.pg_enable_capture(self.pg_interfaces) - self.pg_start() - - rx = self.pg0.get_capture(1) - rx = rx[0] - icmp = rx[ICMPv6DestUnreach] + def test_icmpv6_echo(self): + """VPP replies to ICMPv6 Echo Request - # 0 = "No route to destination" - self.assertEqual(icmp.code, 0) + Test scenario: - # ICMP is rate limited. pause a bit - self.sleep(1) + - Receive ICMPv6 Echo Request message on pg0 interface. + - Check outgoing ICMPv6 Echo Reply message on pg0 interface. + """ - # - # A route via IP NULL that will reply with ICMP prohibited - # - ip_prohibit = VppIpRoute(self, "2001::1", 128, [], - is_prohibit=1, is_ip6=1) - ip_prohibit.add_vpp_config() + # test both with global and local ipv6 addresses + dsts = (self.pg0.local_ip6, self.pg0.local_ip6_ll) + id = 0xB + seq = 5 + data = b"\x0a" * 18 + p = list() + for dst in dsts: + p.append( + ( + Ether(src=self.pg0.remote_mac, dst=self.pg0.local_mac) + / IPv6(src=self.pg0.remote_ip6, dst=dst) + / ICMPv6EchoRequest(id=id, seq=seq, data=data) + ) + ) self.pg0.add_stream(p) self.pg_enable_capture(self.pg_interfaces) self.pg_start() + rxs = self.pg0.get_capture(len(dsts)) - rx = self.pg0.get_capture(1) - rx = rx[0] - icmp = rx[ICMPv6DestUnreach] + for rx, dst in zip(rxs, dsts): + ether = rx[Ether] + ipv6 = rx[IPv6] + icmpv6 = rx[ICMPv6EchoReply] + self.assertEqual(ether.src, self.pg0.local_mac) + self.assertEqual(ether.dst, self.pg0.remote_mac) + self.assertEqual(ipv6.src, dst) + self.assertEqual(ipv6.dst, self.pg0.remote_ip6) + self.assertEqual(icmp6types[icmpv6.type], "Echo Reply") + self.assertEqual(icmpv6.id, id) + self.assertEqual(icmpv6.seq, seq) + self.assertEqual(icmpv6.data, data) - # 1 = "Communication with destination administratively prohibited" - self.assertEqual(icmp.code, 1) +class TestIPv6RD(TestIPv6ND): + """IPv6 Router Discovery Test Case""" -class TestIPDisabled(VppTestCase): - """ IPv6 disabled """ + @classmethod + def setUpClass(cls): + super(TestIPv6RD, cls).setUpClass() + + @classmethod + def tearDownClass(cls): + super(TestIPv6RD, cls).tearDownClass() def setUp(self): - super(TestIPDisabled, self).setUp() + super(TestIPv6RD, self).setUp() # create 2 pg interfaces self.create_pg_interfaces(range(2)) - # PG0 is IP enalbed - self.pg0.admin_up() - self.pg0.config_ip6() - self.pg0.resolve_ndp() + self.interfaces = list(self.pg_interfaces) - # PG 1 is not IP enabled - self.pg1.admin_up() + # setup all interfaces + for i in self.interfaces: + i.admin_up() + i.config_ip6() def tearDown(self): - super(TestIPDisabled, self).tearDown() - for i in self.pg_interfaces: - i.unconfig_ip4() + for i in self.interfaces: + i.unconfig_ip6() i.admin_down() + super(TestIPv6RD, self).tearDown() - def send_and_assert_no_replies(self, intf, pkts, remark): - intf.add_stream(pkts) + def test_rd_send_router_solicitation(self): + """Verify router solicitation packets""" + + count = 2 self.pg_enable_capture(self.pg_interfaces) self.pg_start() - for i in self.pg_interfaces: - i.get_capture(0) - i.assert_nothing_captured(remark=remark) + self.vapi.ip6nd_send_router_solicitation(self.pg1.sw_if_index, mrc=count) + rx_list = self.pg1.get_capture(count, timeout=3) + self.assertEqual(len(rx_list), count) + for packet in rx_list: + self.assertEqual(packet.haslayer(IPv6), 1) + self.assertEqual(packet[IPv6].haslayer(ICMPv6ND_RS), 1) + dst = ip6_normalize(packet[IPv6].dst) + dst2 = ip6_normalize("ff02::2") + self.assert_equal(dst, dst2) + src = ip6_normalize(packet[IPv6].src) + src2 = ip6_normalize(self.pg1.local_ip6_ll) + self.assert_equal(src, src2) + self.assertTrue(bool(packet[ICMPv6ND_RS].haslayer(ICMPv6NDOptSrcLLAddr))) + self.assert_equal(packet[ICMPv6NDOptSrcLLAddr].lladdr, self.pg1.local_mac) + + def verify_prefix_info(self, reported_prefix, prefix_option): + prefix = IPv6Network( + text_type( + prefix_option.getfieldval("prefix") + + "/" + + text_type(prefix_option.getfieldval("prefixlen")) + ), + strict=False, + ) + self.assert_equal( + reported_prefix.prefix.network_address, prefix.network_address + ) + L = prefix_option.getfieldval("L") + A = prefix_option.getfieldval("A") + option_flags = (L << 7) | (A << 6) + self.assert_equal(reported_prefix.flags, option_flags) + self.assert_equal( + reported_prefix.valid_time, prefix_option.getfieldval("validlifetime") + ) + self.assert_equal( + reported_prefix.preferred_time, + prefix_option.getfieldval("preferredlifetime"), + ) + + def test_rd_receive_router_advertisement(self): + """Verify events triggered by received RA packets""" + + self.vapi.want_ip6_ra_events(enable=1) + + prefix_info_1 = ICMPv6NDOptPrefixInfo( + prefix="1::2", + prefixlen=50, + validlifetime=200, + preferredlifetime=500, + L=1, + A=1, + ) + + prefix_info_2 = ICMPv6NDOptPrefixInfo( + prefix="7::4", + prefixlen=20, + validlifetime=70, + preferredlifetime=1000, + L=1, + A=0, + ) + + p = ( + Ether(dst=self.pg1.local_mac, src=self.pg1.remote_mac) + / IPv6(dst=self.pg1.local_ip6_ll, src=mk_ll_addr(self.pg1.remote_mac)) + / ICMPv6ND_RA() + / prefix_info_1 + / prefix_info_2 + ) + self.pg1.add_stream([p]) + self.pg_start() - def test_ip_disabled(self): - """ IP Disabled """ + ev = self.vapi.wait_for_event(10, "ip6_ra_event") - # - # An (S,G). - # one accepting interface, pg0, 2 forwarding interfaces - # - route_ff_01 = VppIpMRoute( + self.assert_equal(ev.current_hop_limit, 0) + self.assert_equal(ev.flags, 8) + self.assert_equal(ev.router_lifetime_in_sec, 1800) + self.assert_equal(ev.neighbor_reachable_time_in_msec, 0) + self.assert_equal( + ev.time_in_msec_between_retransmitted_neighbor_solicitations, 0 + ) + + self.assert_equal(ev.n_prefixes, 2) + + self.verify_prefix_info(ev.prefixes[0], prefix_info_1) + self.verify_prefix_info(ev.prefixes[1], prefix_info_2) + + +class TestIPv6RDControlPlane(TestIPv6ND): + """IPv6 Router Discovery Control Plane Test Case""" + + @classmethod + def setUpClass(cls): + super(TestIPv6RDControlPlane, cls).setUpClass() + + @classmethod + def tearDownClass(cls): + super(TestIPv6RDControlPlane, cls).tearDownClass() + + def setUp(self): + super(TestIPv6RDControlPlane, self).setUp() + + # create 1 pg interface + self.create_pg_interfaces(range(1)) + + self.interfaces = list(self.pg_interfaces) + + # setup all interfaces + for i in self.interfaces: + i.admin_up() + i.config_ip6() + + def tearDown(self): + super(TestIPv6RDControlPlane, self).tearDown() + + @staticmethod + def create_ra_packet(pg, routerlifetime=None): + src_ip = pg.remote_ip6_ll + dst_ip = pg.local_ip6 + if routerlifetime is not None: + ra = ICMPv6ND_RA(routerlifetime=routerlifetime) + else: + ra = ICMPv6ND_RA() + p = ( + Ether(dst=pg.local_mac, src=pg.remote_mac) + / IPv6(dst=dst_ip, src=src_ip) + / ra + ) + return p + + @staticmethod + def get_default_routes(fib): + list = [] + for entry in fib: + if entry.route.prefix.prefixlen == 0: + for path in entry.route.paths: + if path.sw_if_index != 0xFFFFFFFF: + defaut_route = {} + defaut_route["sw_if_index"] = path.sw_if_index + defaut_route["next_hop"] = path.nh.address.ip6 + list.append(defaut_route) + return list + + @staticmethod + def get_interface_addresses(fib, pg): + list = [] + for entry in fib: + if entry.route.prefix.prefixlen == 128: + path = entry.route.paths[0] + if path.sw_if_index == pg.sw_if_index: + list.append(str(entry.route.prefix.network_address)) + return list + + def wait_for_no_default_route(self, n_tries=50, s_time=1): + while n_tries: + fib = self.vapi.ip_route_dump(0, True) + default_routes = self.get_default_routes(fib) + if 0 == len(default_routes): + return True + n_tries = n_tries - 1 + self.sleep(s_time) + + return False + + def test_all(self): + """Test handling of SLAAC addresses and default routes""" + + fib = self.vapi.ip_route_dump(0, True) + default_routes = self.get_default_routes(fib) + initial_addresses = set(self.get_interface_addresses(fib, self.pg0)) + self.assertEqual(default_routes, []) + router_address = IPv6Address(text_type(self.pg0.remote_ip6_ll)) + + self.vapi.ip6_nd_address_autoconfig(self.pg0.sw_if_index, 1, 1) + + self.sleep(0.1) + + # send RA + packet = ( + self.create_ra_packet(self.pg0) + / ICMPv6NDOptPrefixInfo( + prefix="1::", + prefixlen=64, + validlifetime=2, + preferredlifetime=2, + L=1, + A=1, + ) + / ICMPv6NDOptPrefixInfo( + prefix="7::", + prefixlen=20, + validlifetime=1500, + preferredlifetime=1000, + L=1, + A=0, + ) + ) + self.pg0.add_stream([packet]) + self.pg_start() + + self.sleep_on_vpp_time(0.1) + + fib = self.vapi.ip_route_dump(0, True) + + # check FIB for new address + addresses = set(self.get_interface_addresses(fib, self.pg0)) + new_addresses = addresses.difference(initial_addresses) + self.assertEqual(len(new_addresses), 1) + prefix = IPv6Network( + text_type("%s/%d" % (list(new_addresses)[0], 20)), strict=False + ) + self.assertEqual(prefix, IPv6Network(text_type("1::/20"))) + + # check FIB for new default route + default_routes = self.get_default_routes(fib) + self.assertEqual(len(default_routes), 1) + dr = default_routes[0] + self.assertEqual(dr["sw_if_index"], self.pg0.sw_if_index) + self.assertEqual(dr["next_hop"], router_address) + + # send RA to delete default route + packet = self.create_ra_packet(self.pg0, routerlifetime=0) + self.pg0.add_stream([packet]) + self.pg_start() + + self.sleep_on_vpp_time(0.1) + + # check that default route is deleted + fib = self.vapi.ip_route_dump(0, True) + default_routes = self.get_default_routes(fib) + self.assertEqual(len(default_routes), 0) + + self.sleep_on_vpp_time(0.1) + + # send RA + packet = self.create_ra_packet(self.pg0) + self.pg0.add_stream([packet]) + self.pg_start() + + self.sleep_on_vpp_time(0.1) + + # check FIB for new default route + fib = self.vapi.ip_route_dump(0, True) + default_routes = self.get_default_routes(fib) + self.assertEqual(len(default_routes), 1) + dr = default_routes[0] + self.assertEqual(dr["sw_if_index"], self.pg0.sw_if_index) + self.assertEqual(dr["next_hop"], router_address) + + # send RA, updating router lifetime to 1s + packet = self.create_ra_packet(self.pg0, 1) + self.pg0.add_stream([packet]) + self.pg_start() + + self.sleep_on_vpp_time(0.1) + + # check that default route still exists + fib = self.vapi.ip_route_dump(0, True) + default_routes = self.get_default_routes(fib) + self.assertEqual(len(default_routes), 1) + dr = default_routes[0] + self.assertEqual(dr["sw_if_index"], self.pg0.sw_if_index) + self.assertEqual(dr["next_hop"], router_address) + + self.sleep_on_vpp_time(1) + + # check that default route is deleted + self.assertTrue(self.wait_for_no_default_route()) + + # check FIB still contains the SLAAC address + addresses = set(self.get_interface_addresses(fib, self.pg0)) + new_addresses = addresses.difference(initial_addresses) + + self.assertEqual(len(new_addresses), 1) + prefix = IPv6Network( + text_type("%s/%d" % (list(new_addresses)[0], 20)), strict=False + ) + self.assertEqual(prefix, IPv6Network(text_type("1::/20"))) + + self.sleep_on_vpp_time(1) + + # check that SLAAC address is deleted + fib = self.vapi.ip_route_dump(0, True) + addresses = set(self.get_interface_addresses(fib, self.pg0)) + new_addresses = addresses.difference(initial_addresses) + self.assertEqual(len(new_addresses), 0) + + +class IPv6NDProxyTest(TestIPv6ND): + """IPv6 ND ProxyTest Case""" + + @classmethod + def setUpClass(cls): + super(IPv6NDProxyTest, cls).setUpClass() + + @classmethod + def tearDownClass(cls): + super(IPv6NDProxyTest, cls).tearDownClass() + + def setUp(self): + super(IPv6NDProxyTest, self).setUp() + + # create 3 pg interfaces + self.create_pg_interfaces(range(3)) + + # pg0 is the master interface, with the configured subnet + self.pg0.admin_up() + self.pg0.config_ip6() + self.pg0.resolve_ndp() + + self.pg1.ip6_enable() + self.pg2.ip6_enable() + + def tearDown(self): + super(IPv6NDProxyTest, self).tearDown() + + def test_nd_proxy(self): + """IPv6 Proxy ND""" + + # + # Generate some hosts in the subnet that we are proxying + # + self.pg0.generate_remote_hosts(8) + + nsma = in6_getnsma(inet_pton(AF_INET6, self.pg0.local_ip6)) + d = inet_ntop(AF_INET6, nsma) + + # + # Send an NS for one of those remote hosts on one of the proxy links + # expect no response since it's from an address that is not + # on the link that has the prefix configured + # + ns_pg1 = ( + Ether(dst=in6_getnsmac(nsma), src=self.pg1.remote_mac) + / IPv6(dst=d, src=self.pg0._remote_hosts[2].ip6) + / ICMPv6ND_NS(tgt=self.pg0.local_ip6) + / ICMPv6NDOptSrcLLAddr(lladdr=self.pg0._remote_hosts[2].mac) + ) + + self.send_and_assert_no_replies(self.pg1, ns_pg1, "Off link NS") + + # + # Add proxy support for the host + # + self.vapi.ip6nd_proxy_add_del( + is_add=1, + ip=inet_pton(AF_INET6, self.pg0._remote_hosts[2].ip6), + sw_if_index=self.pg1.sw_if_index, + ) + + # + # try that NS again. this time we expect an NA back + # + self.send_and_expect_na( + self.pg1, + ns_pg1, + "NS to proxy entry", + dst_ip=self.pg0._remote_hosts[2].ip6, + tgt_ip=self.pg0.local_ip6, + ) + + # + # ... and that we have an entry in the ND cache + # + self.assertTrue( + find_nbr(self, self.pg1.sw_if_index, self.pg0._remote_hosts[2].ip6) + ) + + # + # ... and we can route traffic to it + # + t = ( + Ether(dst=self.pg0.local_mac, src=self.pg0.remote_mac) + / IPv6(dst=self.pg0._remote_hosts[2].ip6, src=self.pg0.remote_ip6) + / inet6.UDP(sport=10000, dport=20000) + / Raw(b"\xa5" * 100) + ) + + self.pg0.add_stream(t) + self.pg_enable_capture(self.pg_interfaces) + self.pg_start() + rx = self.pg1.get_capture(1) + rx = rx[0] + + self.assertEqual(rx[Ether].dst, self.pg0._remote_hosts[2].mac) + self.assertEqual(rx[Ether].src, self.pg1.local_mac) + + self.assertEqual(rx[IPv6].src, t[IPv6].src) + self.assertEqual(rx[IPv6].dst, t[IPv6].dst) + + # + # Test we proxy for the host on the main interface + # + ns_pg0 = ( + Ether(dst=in6_getnsmac(nsma), src=self.pg0.remote_mac) + / IPv6(dst=d, src=self.pg0.remote_ip6) + / ICMPv6ND_NS(tgt=self.pg0._remote_hosts[2].ip6) + / ICMPv6NDOptSrcLLAddr(lladdr=self.pg0.remote_mac) + ) + + self.send_and_expect_na( + self.pg0, + ns_pg0, + "NS to proxy entry on main", + tgt_ip=self.pg0._remote_hosts[2].ip6, + dst_ip=self.pg0.remote_ip6, + ) + + # + # Setup and resolve proxy for another host on another interface + # + ns_pg2 = ( + Ether(dst=in6_getnsmac(nsma), src=self.pg2.remote_mac) + / IPv6(dst=d, src=self.pg0._remote_hosts[3].ip6) + / ICMPv6ND_NS(tgt=self.pg0.local_ip6) + / ICMPv6NDOptSrcLLAddr(lladdr=self.pg0._remote_hosts[2].mac) + ) + + self.vapi.ip6nd_proxy_add_del( + is_add=1, + ip=inet_pton(AF_INET6, self.pg0._remote_hosts[3].ip6), + sw_if_index=self.pg2.sw_if_index, + ) + + self.send_and_expect_na( + self.pg2, + ns_pg2, + "NS to proxy entry other interface", + dst_ip=self.pg0._remote_hosts[3].ip6, + tgt_ip=self.pg0.local_ip6, + ) + + self.assertTrue( + find_nbr(self, self.pg2.sw_if_index, self.pg0._remote_hosts[3].ip6) + ) + + # + # hosts can communicate. pg2->pg1 + # + t2 = ( + Ether(dst=self.pg2.local_mac, src=self.pg0.remote_hosts[3].mac) + / IPv6(dst=self.pg0._remote_hosts[2].ip6, src=self.pg0._remote_hosts[3].ip6) + / inet6.UDP(sport=10000, dport=20000) + / Raw(b"\xa5" * 100) + ) + + self.pg2.add_stream(t2) + self.pg_enable_capture(self.pg_interfaces) + self.pg_start() + rx = self.pg1.get_capture(1) + rx = rx[0] + + self.assertEqual(rx[Ether].dst, self.pg0._remote_hosts[2].mac) + self.assertEqual(rx[Ether].src, self.pg1.local_mac) + + self.assertEqual(rx[IPv6].src, t2[IPv6].src) + self.assertEqual(rx[IPv6].dst, t2[IPv6].dst) + + # + # remove the proxy configs + # + self.vapi.ip6nd_proxy_add_del( + ip=inet_pton(AF_INET6, self.pg0._remote_hosts[2].ip6), + sw_if_index=self.pg1.sw_if_index, + is_add=0, + ) + self.vapi.ip6nd_proxy_add_del( + ip=inet_pton(AF_INET6, self.pg0._remote_hosts[3].ip6), + sw_if_index=self.pg2.sw_if_index, + is_add=0, + ) + + self.assertFalse( + find_nbr(self, self.pg2.sw_if_index, self.pg0._remote_hosts[3].ip6) + ) + self.assertFalse( + find_nbr(self, self.pg1.sw_if_index, self.pg0._remote_hosts[2].ip6) + ) + + # + # no longer proxy-ing... + # + self.send_and_assert_no_replies(self.pg0, ns_pg0, "Proxy unconfigured") + self.send_and_assert_no_replies(self.pg1, ns_pg1, "Proxy unconfigured") + self.send_and_assert_no_replies(self.pg2, ns_pg2, "Proxy unconfigured") + + # + # no longer forwarding. traffic generates NS out of the glean/main + # interface + # + self.pg2.add_stream(t2) + self.pg_enable_capture(self.pg_interfaces) + self.pg_start() + + rx = self.pg0.get_capture(1) + + self.assertTrue(rx[0].haslayer(ICMPv6ND_NS)) + + +class TestIP6Null(VppTestCase): + """IPv6 routes via NULL""" + + @classmethod + def setUpClass(cls): + super(TestIP6Null, cls).setUpClass() + + @classmethod + def tearDownClass(cls): + super(TestIP6Null, cls).tearDownClass() + + def setUp(self): + super(TestIP6Null, self).setUp() + + # create 2 pg interfaces + self.create_pg_interfaces(range(1)) + + for i in self.pg_interfaces: + i.admin_up() + i.config_ip6() + i.resolve_ndp() + + def tearDown(self): + super(TestIP6Null, self).tearDown() + for i in self.pg_interfaces: + i.unconfig_ip6() + i.admin_down() + + def test_ip_null(self): + """IP NULL route""" + + p = ( + Ether(src=self.pg0.remote_mac, dst=self.pg0.local_mac) + / IPv6(src=self.pg0.remote_ip6, dst="2001::1") + / inet6.UDP(sport=1234, dport=1234) + / Raw(b"\xa5" * 100) + ) + + # + # A route via IP NULL that will reply with ICMP unreachables + # + ip_unreach = VppIpRoute( + self, + "2001::", + 64, + [ + VppRoutePath( + "::", 0xFFFFFFFF, type=FibPathType.FIB_PATH_TYPE_ICMP_UNREACH + ) + ], + ) + ip_unreach.add_vpp_config() + + self.pg0.add_stream(p) + self.pg_enable_capture(self.pg_interfaces) + self.pg_start() + + rx = self.pg0.get_capture(1) + rx = rx[0] + icmp = rx[ICMPv6DestUnreach] + + # 0 = "No route to destination" + self.assertEqual(icmp.code, 0) + + # ICMP is rate limited. pause a bit + self.sleep(1) + + # + # A route via IP NULL that will reply with ICMP prohibited + # + ip_prohibit = VppIpRoute( + self, + "2001::1", + 128, + [ + VppRoutePath( + "::", 0xFFFFFFFF, type=FibPathType.FIB_PATH_TYPE_ICMP_PROHIBIT + ) + ], + ) + ip_prohibit.add_vpp_config() + + self.pg0.add_stream(p) + self.pg_enable_capture(self.pg_interfaces) + self.pg_start() + + rx = self.pg0.get_capture(1) + rx = rx[0] + icmp = rx[ICMPv6DestUnreach] + + # 1 = "Communication with destination administratively prohibited" + self.assertEqual(icmp.code, 1) + + +class TestIP6Disabled(VppTestCase): + """IPv6 disabled""" + + @classmethod + def setUpClass(cls): + super(TestIP6Disabled, cls).setUpClass() + + @classmethod + def tearDownClass(cls): + super(TestIP6Disabled, cls).tearDownClass() + + def setUp(self): + super(TestIP6Disabled, self).setUp() + + # create 2 pg interfaces + self.create_pg_interfaces(range(2)) + + # PG0 is IP enabled + self.pg0.admin_up() + self.pg0.config_ip6() + self.pg0.resolve_ndp() + + # PG 1 is not IP enabled + self.pg1.admin_up() + + def tearDown(self): + super(TestIP6Disabled, self).tearDown() + for i in self.pg_interfaces: + i.unconfig_ip4() + i.admin_down() + + def test_ip_disabled(self): + """IP Disabled""" + + MRouteItfFlags = VppEnum.vl_api_mfib_itf_flags_t + MRouteEntryFlags = VppEnum.vl_api_mfib_entry_flags_t + # + # An (S,G). + # one accepting interface, pg0, 2 forwarding interfaces + # + route_ff_01 = VppIpMRoute( + self, + "::", + "ffef::1", + 128, + MRouteEntryFlags.MFIB_API_ENTRY_FLAG_NONE, + [ + VppMRoutePath( + self.pg1.sw_if_index, MRouteItfFlags.MFIB_API_ITF_FLAG_ACCEPT + ), + VppMRoutePath( + self.pg0.sw_if_index, MRouteItfFlags.MFIB_API_ITF_FLAG_FORWARD + ), + ], + ) + route_ff_01.add_vpp_config() + + pu = ( + Ether(src=self.pg1.remote_mac, dst=self.pg1.local_mac) + / IPv6(src="2001::1", dst=self.pg0.remote_ip6) + / inet6.UDP(sport=1234, dport=1234) + / Raw(b"\xa5" * 100) + ) + pm = ( + Ether(src=self.pg1.remote_mac, dst=self.pg1.local_mac) + / IPv6(src="2001::1", dst="ffef::1") + / inet6.UDP(sport=1234, dport=1234) + / Raw(b"\xa5" * 100) + ) + + # + # PG1 does not forward IP traffic + # + self.send_and_assert_no_replies(self.pg1, pu, "IPv6 disabled") + self.send_and_assert_no_replies(self.pg1, pm, "IPv6 disabled") + + # + # IP enable PG1 + # + self.pg1.config_ip6() + + # + # Now we get packets through + # + self.pg1.add_stream(pu) + self.pg_enable_capture(self.pg_interfaces) + self.pg_start() + rx = self.pg0.get_capture(1) + + self.pg1.add_stream(pm) + self.pg_enable_capture(self.pg_interfaces) + self.pg_start() + rx = self.pg0.get_capture(1) + + # + # Disable PG1 + # + self.pg1.unconfig_ip6() + + # + # PG1 does not forward IP traffic + # + self.send_and_assert_no_replies(self.pg1, pu, "IPv6 disabled") + self.send_and_assert_no_replies(self.pg1, pm, "IPv6 disabled") + + +class TestIP6LoadBalance(VppTestCase): + """IPv6 Load-Balancing""" + + @classmethod + def setUpClass(cls): + super(TestIP6LoadBalance, cls).setUpClass() + + @classmethod + def tearDownClass(cls): + super(TestIP6LoadBalance, cls).tearDownClass() + + def setUp(self): + super(TestIP6LoadBalance, self).setUp() + + self.create_pg_interfaces(range(5)) + + mpls_tbl = VppMplsTable(self, 0) + mpls_tbl.add_vpp_config() + + for i in self.pg_interfaces: + i.admin_up() + i.config_ip6() + i.resolve_ndp() + i.enable_mpls() + + def tearDown(self): + for i in self.pg_interfaces: + i.unconfig_ip6() + i.admin_down() + i.disable_mpls() + super(TestIP6LoadBalance, self).tearDown() + + def test_ip6_load_balance(self): + """IPv6 Load-Balancing""" + + # + # An array of packets that differ only in the destination port + # - IP only + # - MPLS EOS + # - MPLS non-EOS + # - MPLS non-EOS with an entropy label + # + port_ip_pkts = [] + port_mpls_pkts = [] + port_mpls_neos_pkts = [] + port_ent_pkts = [] + + # + # An array of packets that differ only in the source address + # + src_ip_pkts = [] + src_mpls_pkts = [] + + for ii in range(NUM_PKTS): + port_ip_hdr = ( + IPv6(dst="3000::1", src="3000:1::1") + / inet6.UDP(sport=1234, dport=1234 + ii) + / Raw(b"\xa5" * 100) + ) + port_ip_pkts.append( + (Ether(src=self.pg0.remote_mac, dst=self.pg0.local_mac) / port_ip_hdr) + ) + port_mpls_pkts.append( + ( + Ether(src=self.pg0.remote_mac, dst=self.pg0.local_mac) + / MPLS(label=66, ttl=2) + / port_ip_hdr + ) + ) + port_mpls_neos_pkts.append( + ( + Ether(src=self.pg0.remote_mac, dst=self.pg0.local_mac) + / MPLS(label=67, ttl=2) + / MPLS(label=77, ttl=2) + / port_ip_hdr + ) + ) + port_ent_pkts.append( + ( + Ether(src=self.pg0.remote_mac, dst=self.pg0.local_mac) + / MPLS(label=67, ttl=2) + / MPLS(label=14, ttl=2) + / MPLS(label=999, ttl=2) + / port_ip_hdr + ) + ) + src_ip_hdr = ( + IPv6(dst="3000::1", src="3000:1::%d" % ii) + / inet6.UDP(sport=1234, dport=1234) + / Raw(b"\xa5" * 100) + ) + src_ip_pkts.append( + (Ether(src=self.pg0.remote_mac, dst=self.pg0.local_mac) / src_ip_hdr) + ) + src_mpls_pkts.append( + ( + Ether(src=self.pg0.remote_mac, dst=self.pg0.local_mac) + / MPLS(label=66, ttl=2) + / src_ip_hdr + ) + ) + + # + # A route for the IP packets + # + route_3000_1 = VppIpRoute( + self, + "3000::1", + 128, + [ + VppRoutePath(self.pg1.remote_ip6, self.pg1.sw_if_index), + VppRoutePath(self.pg2.remote_ip6, self.pg2.sw_if_index), + ], + ) + route_3000_1.add_vpp_config() + + # + # a local-label for the EOS packets + # + binding = VppMplsIpBind(self, 66, "3000::1", 128, is_ip6=1) + binding.add_vpp_config() + + # + # An MPLS route for the non-EOS packets + # + route_67 = VppMplsRoute( + self, + 67, + 0, + [ + VppRoutePath(self.pg1.remote_ip6, self.pg1.sw_if_index, labels=[67]), + VppRoutePath(self.pg2.remote_ip6, self.pg2.sw_if_index, labels=[67]), + ], + ) + route_67.add_vpp_config() + + # + # inject the packet on pg0 - expect load-balancing across the 2 paths + # - since the default hash config is to use IP src,dst and port + # src,dst + # We are not going to ensure equal amounts of packets across each link, + # since the hash algorithm is statistical and therefore this can never + # be guaranteed. But with 64 different packets we do expect some + # balancing. So instead just ensure there is traffic on each link. + # + rx = self.send_and_expect_load_balancing( + self.pg0, port_ip_pkts, [self.pg1, self.pg2] + ) + n_ip_pg0 = len(rx[0]) + self.send_and_expect_load_balancing(self.pg0, src_ip_pkts, [self.pg1, self.pg2]) + self.send_and_expect_load_balancing( + self.pg0, port_mpls_pkts, [self.pg1, self.pg2] + ) + self.send_and_expect_load_balancing( + self.pg0, src_mpls_pkts, [self.pg1, self.pg2] + ) + rx = self.send_and_expect_load_balancing( + self.pg0, port_mpls_neos_pkts, [self.pg1, self.pg2] + ) + n_mpls_pg0 = len(rx[0]) + + # + # change the router ID and expect the distribution changes + # + self.vapi.set_ip_flow_hash_router_id(router_id=0x11111111) + + rx = self.send_and_expect_load_balancing( + self.pg0, port_ip_pkts, [self.pg1, self.pg2] + ) + self.assertNotEqual(n_ip_pg0, len(rx[0])) + + rx = self.send_and_expect_load_balancing( + self.pg0, src_mpls_pkts, [self.pg1, self.pg2] + ) + self.assertNotEqual(n_mpls_pg0, len(rx[0])) + + # + # The packets with Entropy label in should not load-balance, + # since the Entropy value is fixed. + # + self.send_and_expect_only(self.pg0, port_ent_pkts, self.pg1) + + # + # change the flow hash config so it's only IP src,dst + # - now only the stream with differing source address will + # load-balance + # + self.vapi.set_ip_flow_hash( + vrf_id=0, src=1, dst=1, proto=1, sport=0, dport=0, is_ipv6=1 + ) + + self.send_and_expect_load_balancing(self.pg0, src_ip_pkts, [self.pg1, self.pg2]) + self.send_and_expect_load_balancing( + self.pg0, src_mpls_pkts, [self.pg1, self.pg2] + ) + self.send_and_expect_only(self.pg0, port_ip_pkts, self.pg2) + + # + # change the flow hash config back to defaults + # + self.vapi.set_ip_flow_hash( + vrf_id=0, src=1, dst=1, sport=1, dport=1, proto=1, is_ipv6=1 + ) + + # + # Recursive prefixes + # - testing that 2 stages of load-balancing occurs and there is no + # polarisation (i.e. only 2 of 4 paths are used) + # + port_pkts = [] + src_pkts = [] + + for ii in range(257): + port_pkts.append( + ( + Ether(src=self.pg0.remote_mac, dst=self.pg0.local_mac) + / IPv6(dst="4000::1", src="4000:1::1") + / inet6.UDP(sport=1234, dport=1234 + ii) + / Raw(b"\xa5" * 100) + ) + ) + src_pkts.append( + ( + Ether(src=self.pg0.remote_mac, dst=self.pg0.local_mac) + / IPv6(dst="4000::1", src="4000:1::%d" % ii) + / inet6.UDP(sport=1234, dport=1234) + / Raw(b"\xa5" * 100) + ) + ) + + route_3000_2 = VppIpRoute( + self, + "3000::2", + 128, + [ + VppRoutePath(self.pg3.remote_ip6, self.pg3.sw_if_index), + VppRoutePath(self.pg4.remote_ip6, self.pg4.sw_if_index), + ], + ) + route_3000_2.add_vpp_config() + + route_4000_1 = VppIpRoute( + self, + "4000::1", + 128, + [VppRoutePath("3000::1", 0xFFFFFFFF), VppRoutePath("3000::2", 0xFFFFFFFF)], + ) + route_4000_1.add_vpp_config() + + # + # inject the packet on pg0 - expect load-balancing across all 4 paths + # + self.vapi.cli("clear trace") + self.send_and_expect_load_balancing( + self.pg0, port_pkts, [self.pg1, self.pg2, self.pg3, self.pg4] + ) + self.send_and_expect_load_balancing( + self.pg0, src_pkts, [self.pg1, self.pg2, self.pg3, self.pg4] + ) + + # + # Recursive prefixes + # - testing that 2 stages of load-balancing no choices + # + port_pkts = [] + + for ii in range(257): + port_pkts.append( + ( + Ether(src=self.pg0.remote_mac, dst=self.pg0.local_mac) + / IPv6(dst="6000::1", src="6000:1::1") + / inet6.UDP(sport=1234, dport=1234 + ii) + / Raw(b"\xa5" * 100) + ) + ) + + route_5000_2 = VppIpRoute( + self, + "5000::2", + 128, + [VppRoutePath(self.pg3.remote_ip6, self.pg3.sw_if_index)], + ) + route_5000_2.add_vpp_config() + + route_6000_1 = VppIpRoute( + self, "6000::1", 128, [VppRoutePath("5000::2", 0xFFFFFFFF)] + ) + route_6000_1.add_vpp_config() + + # + # inject the packet on pg0 - expect load-balancing across all 4 paths + # + self.vapi.cli("clear trace") + self.send_and_expect_only(self.pg0, port_pkts, self.pg3) + + +class IP6PuntSetup(object): + """Setup for IPv6 Punt Police/Redirect""" + + def punt_setup(self): + self.create_pg_interfaces(range(4)) + + for i in self.pg_interfaces: + i.admin_up() + i.config_ip6() + i.resolve_ndp() + + self.pkt = ( + Ether(src=self.pg0.remote_mac, dst=self.pg0.local_mac) + / IPv6(src=self.pg0.remote_ip6, dst=self.pg0.local_ip6) + / inet6.TCP(sport=1234, dport=1234) + / Raw(b"\xa5" * 100) + ) + + def punt_teardown(self): + for i in self.pg_interfaces: + i.unconfig_ip6() + i.admin_down() + + +class TestIP6Punt(IP6PuntSetup, VppTestCase): + """IPv6 Punt Police/Redirect""" + + def setUp(self): + super(TestIP6Punt, self).setUp() + super(TestIP6Punt, self).punt_setup() + + def tearDown(self): + super(TestIP6Punt, self).punt_teardown() + super(TestIP6Punt, self).tearDown() + + def test_ip_punt(self): + """IP6 punt police and redirect""" + + pkts = self.pkt * 1025 + + # + # Configure a punt redirect via pg1. + # + nh_addr = self.pg1.remote_ip6 + ip_punt_redirect = VppIpPuntRedirect( + self, self.pg0.sw_if_index, self.pg1.sw_if_index, nh_addr + ) + ip_punt_redirect.add_vpp_config() + + self.send_and_expect(self.pg0, pkts, self.pg1) + + # + # add a policer + # + policer = VppPolicer(self, "ip6-punt", 400, 0, 10, 0, rate_type=1) + policer.add_vpp_config() + ip_punt_policer = VppIpPuntPolicer(self, policer.policer_index, is_ip6=True) + ip_punt_policer.add_vpp_config() + + self.vapi.cli("clear trace") + self.pg0.add_stream(pkts) + self.pg_enable_capture(self.pg_interfaces) + self.pg_start() + + # + # the number of packet received should be greater than 0, + # but not equal to the number sent, since some were policed + # + rx = self.pg1._get_capture(1) + stats = policer.get_stats() + + # Single rate policer - expect conform, violate but no exceed + self.assertGreater(stats["conform_packets"], 0) + self.assertEqual(stats["exceed_packets"], 0) + self.assertGreater(stats["violate_packets"], 0) + + self.assertGreater(len(rx), 0) + self.assertLess(len(rx), len(pkts)) + + # + # remove the policer. back to full rx + # + ip_punt_policer.remove_vpp_config() + policer.remove_vpp_config() + self.send_and_expect(self.pg0, pkts, self.pg1) + + # + # remove the redirect. expect full drop. + # + ip_punt_redirect.remove_vpp_config() + self.send_and_assert_no_replies(self.pg0, pkts, "IP no punt config") + + # + # Add a redirect that is not input port selective + # + ip_punt_redirect = VppIpPuntRedirect( + self, 0xFFFFFFFF, self.pg1.sw_if_index, nh_addr + ) + ip_punt_redirect.add_vpp_config() + self.send_and_expect(self.pg0, pkts, self.pg1) + ip_punt_redirect.remove_vpp_config() + + def test_ip_punt_dump(self): + """IP6 punt redirect dump""" + + # + # Configure a punt redirects + # + nh_address = self.pg3.remote_ip6 + ipr_03 = VppIpPuntRedirect( + self, self.pg0.sw_if_index, self.pg3.sw_if_index, nh_address + ) + ipr_13 = VppIpPuntRedirect( + self, self.pg1.sw_if_index, self.pg3.sw_if_index, nh_address + ) + ipr_23 = VppIpPuntRedirect( + self, self.pg2.sw_if_index, self.pg3.sw_if_index, "0::0" + ) + ipr_03.add_vpp_config() + ipr_13.add_vpp_config() + ipr_23.add_vpp_config() + + # + # Dump pg0 punt redirects + # + self.assertTrue(ipr_03.query_vpp_config()) + self.assertTrue(ipr_13.query_vpp_config()) + self.assertTrue(ipr_23.query_vpp_config()) + + # + # Dump punt redirects for all interfaces + # + punts = self.vapi.ip_punt_redirect_dump(0xFFFFFFFF, is_ipv6=1) + self.assertEqual(len(punts), 3) + for p in punts: + self.assertEqual(p.punt.tx_sw_if_index, self.pg3.sw_if_index) + self.assertNotEqual(punts[1].punt.nh, self.pg3.remote_ip6) + self.assertEqual(str(punts[2].punt.nh), "::") + + +class TestIP6PuntHandoff(IP6PuntSetup, VppTestCase): + """IPv6 Punt Police/Redirect""" + + vpp_worker_count = 2 + + def setUp(self): + super(TestIP6PuntHandoff, self).setUp() + super(TestIP6PuntHandoff, self).punt_setup() + + def tearDown(self): + super(TestIP6PuntHandoff, self).punt_teardown() + super(TestIP6PuntHandoff, self).tearDown() + + def test_ip_punt(self): + """IP6 punt policer thread handoff""" + pkts = self.pkt * NUM_PKTS + + # + # Configure a punt redirect via pg1. + # + nh_addr = self.pg1.remote_ip6 + ip_punt_redirect = VppIpPuntRedirect( + self, self.pg0.sw_if_index, self.pg1.sw_if_index, nh_addr + ) + ip_punt_redirect.add_vpp_config() + + action_tx = PolicerAction( + VppEnum.vl_api_sse2_qos_action_type_t.SSE2_QOS_ACTION_API_TRANSMIT, 0 + ) + # + # This policer drops no packets, we are just + # testing that they get to the right thread. + # + policer = VppPolicer( self, - "::", - "ffef::1", 128, - MRouteEntryFlags.MFIB_ENTRY_FLAG_NONE, - [VppMRoutePath(self.pg1.sw_if_index, - MRouteItfFlags.MFIB_ITF_FLAG_ACCEPT), - VppMRoutePath(self.pg0.sw_if_index, - MRouteItfFlags.MFIB_ITF_FLAG_FORWARD)], - is_ip6=1) - route_ff_01.add_vpp_config() + "ip6-punt", + 400, + 0, + 10, + 0, + 1, + 0, + 0, + False, + action_tx, + action_tx, + action_tx, + ) + policer.add_vpp_config() + ip_punt_policer = VppIpPuntPolicer(self, policer.policer_index, is_ip6=True) + ip_punt_policer.add_vpp_config() + + for worker in [0, 1]: + self.send_and_expect(self.pg0, pkts, self.pg1, worker=worker) + if worker == 0: + self.logger.debug(self.vapi.cli("show trace max 100")) + + # Combined stats, all threads + stats = policer.get_stats() + + # Single rate policer - expect conform, violate but no exceed + self.assertGreater(stats["conform_packets"], 0) + self.assertEqual(stats["exceed_packets"], 0) + self.assertGreater(stats["violate_packets"], 0) + + # Worker 0, should have done all the policing + stats0 = policer.get_stats(worker=0) + self.assertEqual(stats, stats0) + + # Worker 1, should have handed everything off + stats1 = policer.get_stats(worker=1) + self.assertEqual(stats1["conform_packets"], 0) + self.assertEqual(stats1["exceed_packets"], 0) + self.assertEqual(stats1["violate_packets"], 0) + + # Bind the policer to worker 1 and repeat + policer.bind_vpp_config(1, True) + for worker in [0, 1]: + self.send_and_expect(self.pg0, pkts, self.pg1, worker=worker) + self.logger.debug(self.vapi.cli("show trace max 100")) + + # The 2 workers should now have policed the same amount + stats = policer.get_stats() + stats0 = policer.get_stats(worker=0) + stats1 = policer.get_stats(worker=1) + + self.assertGreater(stats0["conform_packets"], 0) + self.assertEqual(stats0["exceed_packets"], 0) + self.assertGreater(stats0["violate_packets"], 0) + + self.assertGreater(stats1["conform_packets"], 0) + self.assertEqual(stats1["exceed_packets"], 0) + self.assertGreater(stats1["violate_packets"], 0) + + self.assertEqual( + stats0["conform_packets"] + stats1["conform_packets"], + stats["conform_packets"], + ) + + self.assertEqual( + stats0["violate_packets"] + stats1["violate_packets"], + stats["violate_packets"], + ) + + # Unbind the policer and repeat + policer.bind_vpp_config(1, False) + for worker in [0, 1]: + self.send_and_expect(self.pg0, pkts, self.pg1, worker=worker) + self.logger.debug(self.vapi.cli("show trace max 100")) + + # The policer should auto-bind to worker 0 when packets arrive + stats = policer.get_stats() + stats0new = policer.get_stats(worker=0) + stats1new = policer.get_stats(worker=1) + + self.assertGreater(stats0new["conform_packets"], stats0["conform_packets"]) + self.assertEqual(stats0new["exceed_packets"], 0) + self.assertGreater(stats0new["violate_packets"], stats0["violate_packets"]) + + self.assertEqual(stats1, stats1new) + + # + # Clean up + # + ip_punt_policer.remove_vpp_config() + policer.remove_vpp_config() + ip_punt_redirect.remove_vpp_config() + + +class TestIP6Deag(VppTestCase): + """IPv6 Deaggregate Routes""" + + @classmethod + def setUpClass(cls): + super(TestIP6Deag, cls).setUpClass() + + @classmethod + def tearDownClass(cls): + super(TestIP6Deag, cls).tearDownClass() + + def setUp(self): + super(TestIP6Deag, self).setUp() + + self.create_pg_interfaces(range(3)) + + for i in self.pg_interfaces: + i.admin_up() + i.config_ip6() + i.resolve_ndp() + + def tearDown(self): + super(TestIP6Deag, self).tearDown() + for i in self.pg_interfaces: + i.unconfig_ip6() + i.admin_down() + + def test_ip_deag(self): + """IP Deag Routes""" + + # + # Create a table to be used for: + # 1 - another destination address lookup + # 2 - a source address lookup + # + table_dst = VppIpTable(self, 1, is_ip6=1) + table_src = VppIpTable(self, 2, is_ip6=1) + table_dst.add_vpp_config() + table_src.add_vpp_config() + + # + # Add a route in the default table to point to a deag/ + # second lookup in each of these tables + # + route_to_dst = VppIpRoute( + self, "1::1", 128, [VppRoutePath("::", 0xFFFFFFFF, nh_table_id=1)] + ) + route_to_src = VppIpRoute( + self, + "1::2", + 128, + [ + VppRoutePath( + "::", + 0xFFFFFFFF, + nh_table_id=2, + type=FibPathType.FIB_PATH_TYPE_SOURCE_LOOKUP, + ) + ], + ) + + route_to_dst.add_vpp_config() + route_to_src.add_vpp_config() + + # + # packets to these destination are dropped, since they'll + # hit the respective default routes in the second table + # + p_dst = ( + Ether(src=self.pg0.remote_mac, dst=self.pg0.local_mac) + / IPv6(src="5::5", dst="1::1") + / inet6.TCP(sport=1234, dport=1234) + / Raw(b"\xa5" * 100) + ) + p_src = ( + Ether(src=self.pg0.remote_mac, dst=self.pg0.local_mac) + / IPv6(src="2::2", dst="1::2") + / inet6.TCP(sport=1234, dport=1234) + / Raw(b"\xa5" * 100) + ) + pkts_dst = p_dst * 257 + pkts_src = p_src * 257 + + self.send_and_assert_no_replies(self.pg0, pkts_dst, "IP in dst table") + self.send_and_assert_no_replies(self.pg0, pkts_src, "IP in src table") + + # + # add a route in the dst table to forward via pg1 + # + route_in_dst = VppIpRoute( + self, + "1::1", + 128, + [VppRoutePath(self.pg1.remote_ip6, self.pg1.sw_if_index)], + table_id=1, + ) + route_in_dst.add_vpp_config() + + self.send_and_expect(self.pg0, pkts_dst, self.pg1) + + # + # add a route in the src table to forward via pg2 + # + route_in_src = VppIpRoute( + self, + "2::2", + 128, + [VppRoutePath(self.pg2.remote_ip6, self.pg2.sw_if_index)], + table_id=2, + ) + route_in_src.add_vpp_config() + self.send_and_expect(self.pg0, pkts_src, self.pg2) + + # + # loop in the lookup DP + # + route_loop = VppIpRoute(self, "3::3", 128, [VppRoutePath("::", 0xFFFFFFFF)]) + route_loop.add_vpp_config() + + p_l = ( + Ether(src=self.pg0.remote_mac, dst=self.pg0.local_mac) + / IPv6(src="3::4", dst="3::3") + / inet6.TCP(sport=1234, dport=1234) + / Raw(b"\xa5" * 100) + ) + + self.send_and_assert_no_replies(self.pg0, p_l * 257, "IP lookup loop") + + +class TestIP6Input(VppTestCase): + """IPv6 Input Exception Test Cases""" + + @classmethod + def setUpClass(cls): + super(TestIP6Input, cls).setUpClass() + + @classmethod + def tearDownClass(cls): + super(TestIP6Input, cls).tearDownClass() + + def setUp(self): + super(TestIP6Input, self).setUp() + + self.create_pg_interfaces(range(2)) + + for i in self.pg_interfaces: + i.admin_up() + i.config_ip6() + i.resolve_ndp() + + def tearDown(self): + super(TestIP6Input, self).tearDown() + for i in self.pg_interfaces: + i.unconfig_ip6() + i.admin_down() + + def test_ip_input_icmp_reply(self): + """IP6 Input Exception - Return ICMP (3,0)""" + # + # hop limit - ICMP replies + # + p_version = ( + Ether(src=self.pg0.remote_mac, dst=self.pg0.local_mac) + / IPv6(src=self.pg0.remote_ip6, dst=self.pg1.remote_ip6, hlim=1) + / inet6.UDP(sport=1234, dport=1234) + / Raw(b"\xa5" * 100) + ) + + rxs = self.send_and_expect_some(self.pg0, p_version * NUM_PKTS, self.pg0) + + for rx in rxs: + icmp = rx[ICMPv6TimeExceeded] + # 0: "hop limit exceeded in transit", + self.assertEqual((icmp.type, icmp.code), (3, 0)) + + icmpv6_data = "\x0a" * 18 + all_0s = "::" + all_1s = "FFFF:FFFF:FFFF:FFFF:FFFF:FFFF:FFFF:FFFF" + + @parameterized.expand( + [ + # Name, src, dst, l4proto, msg, timeout + ( + "src='iface', dst='iface'", + None, + None, + inet6.UDP(sport=1234, dport=1234), + "funky version", + None, + ), + ( + "src='All 0's', dst='iface'", + all_0s, + None, + ICMPv6EchoRequest(id=0xB, seq=5, data=icmpv6_data), + None, + 0.1, + ), + ( + "src='iface', dst='All 0's'", + None, + all_0s, + ICMPv6EchoRequest(id=0xB, seq=5, data=icmpv6_data), + None, + 0.1, + ), + ( + "src='All 1's', dst='iface'", + all_1s, + None, + ICMPv6EchoRequest(id=0xB, seq=5, data=icmpv6_data), + None, + 0.1, + ), + ( + "src='iface', dst='All 1's'", + None, + all_1s, + ICMPv6EchoRequest(id=0xB, seq=5, data=icmpv6_data), + None, + 0.1, + ), + ( + "src='All 1's', dst='All 1's'", + all_1s, + all_1s, + ICMPv6EchoRequest(id=0xB, seq=5, data=icmpv6_data), + None, + 0.1, + ), + ] + ) + def test_ip_input_no_replies(self, name, src, dst, l4, msg, timeout): + self._testMethodDoc = "IPv6 Input Exception - %s" % name + + p_version = ( + Ether(src=self.pg0.remote_mac, dst=self.pg0.local_mac) + / IPv6( + src=src or self.pg0.remote_ip6, + dst=dst or self.pg1.remote_ip6, + version=3, + ) + / l4 + / Raw(b"\xa5" * 100) + ) + + self.send_and_assert_no_replies( + self.pg0, p_version * NUM_PKTS, remark=msg or "", timeout=timeout + ) + + def test_hop_by_hop(self): + """Hop-by-hop header test""" + + p = ( + Ether(src=self.pg0.remote_mac, dst=self.pg0.local_mac) + / IPv6(src=self.pg0.remote_ip6, dst=self.pg0.local_ip6) + / IPv6ExtHdrHopByHop() + / inet6.UDP(sport=1234, dport=1234) + / Raw(b"\xa5" * 100) + ) + + self.pg0.add_stream(p) + self.pg_enable_capture(self.pg_interfaces) + self.pg_start() + + +class TestIP6Replace(VppTestCase): + """IPv6 Table Replace""" + + @classmethod + def setUpClass(cls): + super(TestIP6Replace, cls).setUpClass() + + @classmethod + def tearDownClass(cls): + super(TestIP6Replace, cls).tearDownClass() + + def setUp(self): + super(TestIP6Replace, self).setUp() + + self.create_pg_interfaces(range(4)) + + table_id = 1 + self.tables = [] + + for i in self.pg_interfaces: + i.admin_up() + i.config_ip6() + i.generate_remote_hosts(2) + self.tables.append(VppIpTable(self, table_id, True).add_vpp_config()) + table_id += 1 + + def tearDown(self): + super(TestIP6Replace, self).tearDown() + for i in self.pg_interfaces: + i.admin_down() + i.unconfig_ip6() + + def test_replace(self): + """IP Table Replace""" + + MRouteItfFlags = VppEnum.vl_api_mfib_itf_flags_t + MRouteEntryFlags = VppEnum.vl_api_mfib_entry_flags_t + N_ROUTES = 20 + links = [self.pg0, self.pg1, self.pg2, self.pg3] + routes = [[], [], [], []] + + # the sizes of 'empty' tables + for t in self.tables: + self.assertEqual(len(t.dump()), 2) + self.assertEqual(len(t.mdump()), 5) + + # load up the tables with some routes + for ii, t in enumerate(self.tables): + for jj in range(1, N_ROUTES): + uni = VppIpRoute( + self, + "2001::%d" % jj if jj != 0 else "2001::", + 128, + [ + VppRoutePath( + links[ii].remote_hosts[0].ip6, links[ii].sw_if_index + ), + VppRoutePath( + links[ii].remote_hosts[1].ip6, links[ii].sw_if_index + ), + ], + table_id=t.table_id, + ).add_vpp_config() + multi = VppIpMRoute( + self, + "::", + "ff:2001::%d" % jj, + 128, + MRouteEntryFlags.MFIB_API_ENTRY_FLAG_NONE, + [ + VppMRoutePath( + self.pg0.sw_if_index, + MRouteItfFlags.MFIB_API_ITF_FLAG_ACCEPT, + proto=FibPathProto.FIB_PATH_NH_PROTO_IP6, + ), + VppMRoutePath( + self.pg1.sw_if_index, + MRouteItfFlags.MFIB_API_ITF_FLAG_FORWARD, + proto=FibPathProto.FIB_PATH_NH_PROTO_IP6, + ), + VppMRoutePath( + self.pg2.sw_if_index, + MRouteItfFlags.MFIB_API_ITF_FLAG_FORWARD, + proto=FibPathProto.FIB_PATH_NH_PROTO_IP6, + ), + VppMRoutePath( + self.pg3.sw_if_index, + MRouteItfFlags.MFIB_API_ITF_FLAG_FORWARD, + proto=FibPathProto.FIB_PATH_NH_PROTO_IP6, + ), + ], + table_id=t.table_id, + ).add_vpp_config() + routes[ii].append({"uni": uni, "multi": multi}) + + # + # replace the tables a few times + # + for kk in range(3): + # replace each table + for t in self.tables: + t.replace_begin() + + # all the routes are still there + for ii, t in enumerate(self.tables): + dump = t.dump() + mdump = t.mdump() + for r in routes[ii]: + self.assertTrue(find_route_in_dump(dump, r["uni"], t)) + self.assertTrue(find_mroute_in_dump(mdump, r["multi"], t)) + + # redownload the even numbered routes + for ii, t in enumerate(self.tables): + for jj in range(0, N_ROUTES, 2): + routes[ii][jj]["uni"].add_vpp_config() + routes[ii][jj]["multi"].add_vpp_config() + + # signal each table converged + for t in self.tables: + t.replace_end() + + # we should find the even routes, but not the odd + for ii, t in enumerate(self.tables): + dump = t.dump() + mdump = t.mdump() + for jj in range(0, N_ROUTES, 2): + self.assertTrue(find_route_in_dump(dump, routes[ii][jj]["uni"], t)) + self.assertTrue( + find_mroute_in_dump(mdump, routes[ii][jj]["multi"], t) + ) + for jj in range(1, N_ROUTES - 1, 2): + self.assertFalse(find_route_in_dump(dump, routes[ii][jj]["uni"], t)) + self.assertFalse( + find_mroute_in_dump(mdump, routes[ii][jj]["multi"], t) + ) + + # reload all the routes + for ii, t in enumerate(self.tables): + for r in routes[ii]: + r["uni"].add_vpp_config() + r["multi"].add_vpp_config() + + # all the routes are still there + for ii, t in enumerate(self.tables): + dump = t.dump() + mdump = t.mdump() + for r in routes[ii]: + self.assertTrue(find_route_in_dump(dump, r["uni"], t)) + self.assertTrue(find_mroute_in_dump(mdump, r["multi"], t)) + + # + # finally flush the tables for good measure + # + for t in self.tables: + t.flush() + self.assertEqual(len(t.dump()), 2) + self.assertEqual(len(t.mdump()), 5) + + +class TestIP6AddrReplace(VppTestCase): + """IPv6 Interface Address Replace""" + + @classmethod + def setUpClass(cls): + super(TestIP6AddrReplace, cls).setUpClass() + + @classmethod + def tearDownClass(cls): + super(TestIP6AddrReplace, cls).tearDownClass() + + def setUp(self): + super(TestIP6AddrReplace, self).setUp() + + self.create_pg_interfaces(range(4)) + + for i in self.pg_interfaces: + i.admin_up() + + def tearDown(self): + super(TestIP6AddrReplace, self).tearDown() + for i in self.pg_interfaces: + i.admin_down() + + def get_n_pfxs(self, intf): + return len(self.vapi.ip_address_dump(intf.sw_if_index, True)) + + def test_replace(self): + """IP interface address replace""" + + intf_pfxs = [[], [], [], []] + + # add prefixes to each of the interfaces + for i in range(len(self.pg_interfaces)): + intf = self.pg_interfaces[i] + + # 2001:16:x::1/64 + addr = "2001:16:%d::1" % intf.sw_if_index + a = VppIpInterfaceAddress(self, intf, addr, 64).add_vpp_config() + intf_pfxs[i].append(a) + + # 2001:16:x::2/64 - a different address in the same subnet as above + addr = "2001:16:%d::2" % intf.sw_if_index + a = VppIpInterfaceAddress(self, intf, addr, 64).add_vpp_config() + intf_pfxs[i].append(a) + + # 2001:15:x::2/64 - a different address and subnet + addr = "2001:15:%d::2" % intf.sw_if_index + a = VppIpInterfaceAddress(self, intf, addr, 64).add_vpp_config() + intf_pfxs[i].append(a) + + # a dump should n_address in it + for intf in self.pg_interfaces: + self.assertEqual(self.get_n_pfxs(intf), 3) + + # + # remove all the address thru a replace + # + self.vapi.sw_interface_address_replace_begin() + self.vapi.sw_interface_address_replace_end() + for intf in self.pg_interfaces: + self.assertEqual(self.get_n_pfxs(intf), 0) + + # + # add all the interface addresses back + # + for p in intf_pfxs: + for v in p: + v.add_vpp_config() + for intf in self.pg_interfaces: + self.assertEqual(self.get_n_pfxs(intf), 3) + + # + # replace again, but this time update/re-add the address on the first + # two interfaces + # + self.vapi.sw_interface_address_replace_begin() + + for p in intf_pfxs[:2]: + for v in p: + v.add_vpp_config() + + self.vapi.sw_interface_address_replace_end() + + # on the first two the address still exist, + # on the other two they do not + for intf in self.pg_interfaces[:2]: + self.assertEqual(self.get_n_pfxs(intf), 3) + for p in intf_pfxs[:2]: + for v in p: + self.assertTrue(v.query_vpp_config()) + for intf in self.pg_interfaces[2:]: + self.assertEqual(self.get_n_pfxs(intf), 0) + + # + # add all the interface addresses back on the last two + # + for p in intf_pfxs[2:]: + for v in p: + v.add_vpp_config() + for intf in self.pg_interfaces: + self.assertEqual(self.get_n_pfxs(intf), 3) + + # + # replace again, this time add different prefixes on all the interfaces + # + self.vapi.sw_interface_address_replace_begin() + + pfxs = [] + for intf in self.pg_interfaces: + # 2001:18:x::1/64 + addr = "2001:18:%d::1" % intf.sw_if_index + pfxs.append(VppIpInterfaceAddress(self, intf, addr, 64).add_vpp_config()) + + self.vapi.sw_interface_address_replace_end() + + # only .18 should exist on each interface + for intf in self.pg_interfaces: + self.assertEqual(self.get_n_pfxs(intf), 1) + for pfx in pfxs: + self.assertTrue(pfx.query_vpp_config()) + + # + # remove everything + # + self.vapi.sw_interface_address_replace_begin() + self.vapi.sw_interface_address_replace_end() + for intf in self.pg_interfaces: + self.assertEqual(self.get_n_pfxs(intf), 0) + + # + # add prefixes to each interface. post-begin add the prefix from + # interface X onto interface Y. this would normally be an error + # since it would generate a 'duplicate address' warning. but in + # this case, since what is newly downloaded is sane, it's ok + # + for intf in self.pg_interfaces: + # 2001:18:x::1/64 + addr = "2001:18:%d::1" % intf.sw_if_index + VppIpInterfaceAddress(self, intf, addr, 64).add_vpp_config() + + self.vapi.sw_interface_address_replace_begin() + + pfxs = [] + for intf in self.pg_interfaces: + # 2001:18:x::1/64 + addr = "2001:18:%d::1" % (intf.sw_if_index + 1) + pfxs.append(VppIpInterfaceAddress(self, intf, addr, 64).add_vpp_config()) + + self.vapi.sw_interface_address_replace_end() + + self.logger.info(self.vapi.cli("sh int addr")) + + for intf in self.pg_interfaces: + self.assertEqual(self.get_n_pfxs(intf), 1) + for pfx in pfxs: + self.assertTrue(pfx.query_vpp_config()) + + +class TestIP6LinkLocal(VppTestCase): + """IPv6 Link Local""" + + @classmethod + def setUpClass(cls): + super(TestIP6LinkLocal, cls).setUpClass() + + @classmethod + def tearDownClass(cls): + super(TestIP6LinkLocal, cls).tearDownClass() + + def setUp(self): + super(TestIP6LinkLocal, self).setUp() + + self.create_pg_interfaces(range(2)) + + for i in self.pg_interfaces: + i.admin_up() + + def tearDown(self): + super(TestIP6LinkLocal, self).tearDown() + for i in self.pg_interfaces: + i.admin_down() - pu = (Ether(src=self.pg1.remote_mac, - dst=self.pg1.local_mac) / - IPv6(src="2001::1", dst=self.pg0.remote_ip6) / - UDP(sport=1234, dport=1234) / - Raw('\xa5' * 100)) - pm = (Ether(src=self.pg1.remote_mac, - dst=self.pg1.local_mac) / - IPv6(src="2001::1", dst="ffef::1") / - UDP(sport=1234, dport=1234) / - Raw('\xa5' * 100)) + def test_ip6_ll(self): + """IPv6 Link Local""" # - # PG1 does not forward IP traffic + # two APIs to add a link local address. + # 1 - just like any other prefix + # 2 - with the special set LL API # - self.send_and_assert_no_replies(self.pg1, pu, "IPv6 disabled") - self.send_and_assert_no_replies(self.pg1, pm, "IPv6 disabled") # - # IP enable PG1 + # First with the API to set a 'normal' prefix # - self.pg1.config_ip6() + ll1 = "fe80:1::1" + ll2 = "fe80:2::2" + ll3 = "fe80:3::3" + + VppNeighbor( + self, self.pg0.sw_if_index, self.pg0.remote_mac, ll2 + ).add_vpp_config() + + VppIpInterfaceAddress(self, self.pg0, ll1, 128).add_vpp_config() # - # Now we get packets through + # should be able to ping the ll # - self.pg1.add_stream(pu) - self.pg_enable_capture(self.pg_interfaces) - self.pg_start() - rx = self.pg0.get_capture(1) + p_echo_request_1 = ( + Ether(src=self.pg0.remote_mac, dst=self.pg0.local_mac) + / IPv6(src=ll2, dst=ll1) + / ICMPv6EchoRequest() + ) - self.pg1.add_stream(pm) - self.pg_enable_capture(self.pg_interfaces) - self.pg_start() - rx = self.pg0.get_capture(1) + self.send_and_expect(self.pg0, [p_echo_request_1], self.pg0) # - # Disable PG1 + # change the link-local on pg0 # - self.pg1.unconfig_ip6() + v_ll3 = VppIpInterfaceAddress(self, self.pg0, ll3, 128).add_vpp_config() + + p_echo_request_3 = ( + Ether(src=self.pg0.remote_mac, dst=self.pg0.local_mac) + / IPv6(src=ll2, dst=ll3) + / ICMPv6EchoRequest() + ) + + self.send_and_expect(self.pg0, [p_echo_request_3], self.pg0) # - # PG1 does not forward IP traffic + # set a normal v6 prefix on the link # - self.send_and_assert_no_replies(self.pg1, pu, "IPv6 disabled") - self.send_and_assert_no_replies(self.pg1, pm, "IPv6 disabled") + self.pg0.config_ip6() + self.send_and_expect(self.pg0, [p_echo_request_3], self.pg0) -class TestIP6LoadBalance(VppTestCase): - """ IPv6 Load-Balancing """ + # the link-local cannot be removed + with self.vapi.assert_negative_api_retval(): + v_ll3.remove_vpp_config() - def setUp(self): - super(TestIP6LoadBalance, self).setUp() + # + # Use the specific link-local API on pg1 + # + VppIp6LinkLocalAddress(self, self.pg1, ll1).add_vpp_config() + self.send_and_expect(self.pg1, [p_echo_request_1], self.pg1) - self.create_pg_interfaces(range(5)) + VppIp6LinkLocalAddress(self, self.pg1, ll3).add_vpp_config() + self.send_and_expect(self.pg1, [p_echo_request_3], self.pg1) - mpls_tbl = VppMplsTable(self, 0) - mpls_tbl.add_vpp_config() + def test_ip6_ll_p2p(self): + """IPv6 Link Local P2P (GRE)""" + + self.pg0.config_ip4() + self.pg0.resolve_arp() + gre_if = VppGreInterface( + self, self.pg0.local_ip4, self.pg0.remote_ip4 + ).add_vpp_config() + gre_if.admin_up() + + ll1 = "fe80:1::1" + ll2 = "fe80:2::2" + + VppIpInterfaceAddress(self, gre_if, ll1, 128).add_vpp_config() + + self.logger.info(self.vapi.cli("sh ip6-ll gre0 fe80:2::2")) + + p_echo_request_1 = ( + Ether(src=self.pg0.remote_mac, dst=self.pg0.local_mac) + / IP(src=self.pg0.remote_ip4, dst=self.pg0.local_ip4) + / GRE() + / IPv6(src=ll2, dst=ll1) + / ICMPv6EchoRequest() + ) + self.send_and_expect(self.pg0, [p_echo_request_1], self.pg0) + + self.pg0.unconfig_ip4() + gre_if.remove_vpp_config() + + def test_ip6_ll_p2mp(self): + """IPv6 Link Local P2MP (GRE)""" + + self.pg0.config_ip4() + self.pg0.resolve_arp() + + gre_if = VppGreInterface( + self, + self.pg0.local_ip4, + "0.0.0.0", + mode=(VppEnum.vl_api_tunnel_mode_t.TUNNEL_API_MODE_MP), + ).add_vpp_config() + gre_if.admin_up() + + ll1 = "fe80:1::1" + ll2 = "fe80:2::2" + VppIpInterfaceAddress(self, gre_if, ll1, 128).add_vpp_config() + + p_echo_request_1 = ( + Ether(src=self.pg0.remote_mac, dst=self.pg0.local_mac) + / IP(src=self.pg0.remote_ip4, dst=self.pg0.local_ip4) + / GRE() + / IPv6(src=ll2, dst=ll1) + / ICMPv6EchoRequest() + ) + + # no route back at this point + self.send_and_assert_no_replies(self.pg0, [p_echo_request_1]) + + # add teib entry for the peer + teib = VppTeib(self, gre_if, ll2, self.pg0.remote_ip4) + teib.add_vpp_config() + + self.logger.info(self.vapi.cli("sh ip6-ll gre0 %s" % ll2)) + self.send_and_expect(self.pg0, [p_echo_request_1], self.pg0) + + # teardown + self.pg0.unconfig_ip4() + + +class TestIPv6PathMTU(VppTestCase): + """IPv6 Path MTU""" + + def setUp(self): + super(TestIPv6PathMTU, self).setUp() + + self.create_pg_interfaces(range(2)) + + # setup all interfaces for i in self.pg_interfaces: i.admin_up() i.config_ip6() i.resolve_ndp() - i.enable_mpls() def tearDown(self): + super(TestIPv6PathMTU, self).tearDown() for i in self.pg_interfaces: i.unconfig_ip6() i.admin_down() - i.disable_mpls() - super(TestIP6LoadBalance, self).tearDown() - - def send_and_expect_load_balancing(self, input, pkts, outputs): - input.add_stream(pkts) - self.pg_enable_capture(self.pg_interfaces) - self.pg_start() - for oo in outputs: - rx = oo._get_capture(1) - self.assertNotEqual(0, len(rx)) - - def send_and_expect_one_itf(self, input, pkts, itf): - input.add_stream(pkts) - self.pg_enable_capture(self.pg_interfaces) - self.pg_start() - rx = itf.get_capture(len(pkts)) - - def test_ip6_load_balance(self): - """ IPv6 Load-Balancing """ + def test_path_mtu_local(self): + """Path MTU for attached neighbour""" + + self.vapi.cli("set log class ip level debug") # - # An array of packets that differ only in the destination port - # - IP only - # - MPLS EOS - # - MPLS non-EOS - # - MPLS non-EOS with an entropy label + # The goal here is not test that fragmentation works correctly, + # that's done elsewhere, the intent is to ensure that the Path MTU + # settings are honoured. # - port_ip_pkts = [] - port_mpls_pkts = [] - port_mpls_neos_pkts = [] - port_ent_pkts = [] # - # An array of packets that differ only in the source address + # IPv6 will only frag locally generated packets, so use tunnelled + # packets post encap # - src_ip_pkts = [] - src_mpls_pkts = [] + tun = VppIpIpTunInterface( + self, self.pg1, self.pg1.local_ip6, self.pg1.remote_ip6 + ) + tun.add_vpp_config() + tun.admin_up() + tun.config_ip6() - for ii in range(65): - port_ip_hdr = (IPv6(dst="3000::1", src="3000:1::1") / - UDP(sport=1234, dport=1234 + ii) / - Raw('\xa5' * 100)) - port_ip_pkts.append((Ether(src=self.pg0.remote_mac, - dst=self.pg0.local_mac) / - port_ip_hdr)) - port_mpls_pkts.append((Ether(src=self.pg0.remote_mac, - dst=self.pg0.local_mac) / - MPLS(label=66, ttl=2) / - port_ip_hdr)) - port_mpls_neos_pkts.append((Ether(src=self.pg0.remote_mac, - dst=self.pg0.local_mac) / - MPLS(label=67, ttl=2) / - MPLS(label=77, ttl=2) / - port_ip_hdr)) - port_ent_pkts.append((Ether(src=self.pg0.remote_mac, - dst=self.pg0.local_mac) / - MPLS(label=67, ttl=2) / - MPLS(label=14, ttl=2) / - MPLS(label=999, ttl=2) / - port_ip_hdr)) - src_ip_hdr = (IPv6(dst="3000::1", src="3000:1::%d" % ii) / - UDP(sport=1234, dport=1234) / - Raw('\xa5' * 100)) - src_ip_pkts.append((Ether(src=self.pg0.remote_mac, - dst=self.pg0.local_mac) / - src_ip_hdr)) - src_mpls_pkts.append((Ether(src=self.pg0.remote_mac, - dst=self.pg0.local_mac) / - MPLS(label=66, ttl=2) / - src_ip_hdr)) - - # - # A route for the IP pacekts - # - route_3000_1 = VppIpRoute(self, "3000::1", 128, - [VppRoutePath(self.pg1.remote_ip6, - self.pg1.sw_if_index, - proto=DpoProto.DPO_PROTO_IP6), - VppRoutePath(self.pg2.remote_ip6, - self.pg2.sw_if_index, - proto=DpoProto.DPO_PROTO_IP6)], - is_ip6=1) - route_3000_1.add_vpp_config() + # set the interface MTU to a reasonable value + self.vapi.sw_interface_set_mtu(self.pg1.sw_if_index, [2800, 0, 0, 0]) - # - # a local-label for the EOS packets - # - binding = VppMplsIpBind(self, 66, "3000::1", 128, is_ip6=1) - binding.add_vpp_config() + p_6k = ( + Ether(dst=self.pg0.local_mac, src=self.pg0.remote_mac) + / IPv6(src=self.pg0.remote_ip6, dst=tun.remote_ip6) + / UDP(sport=1234, dport=5678) + / Raw(b"0xa" * 2000) + ) + p_2k = ( + Ether(dst=self.pg0.local_mac, src=self.pg0.remote_mac) + / IPv6(src=self.pg0.remote_ip6, dst=tun.remote_ip6) + / UDP(sport=1234, dport=5678) + / Raw(b"0xa" * 1000) + ) + p_1k = ( + Ether(dst=self.pg0.local_mac, src=self.pg0.remote_mac) + / IPv6(src=self.pg0.remote_ip6, dst=tun.remote_ip6) + / UDP(sport=1234, dport=5678) + / Raw(b"0xa" * 600) + ) + + nbr = VppNeighbor( + self, self.pg1.sw_if_index, self.pg1.remote_mac, self.pg1.remote_ip6 + ).add_vpp_config() + + # this is now the interface MTU frags + self.send_and_expect(self.pg0, [p_6k], self.pg1, n_rx=4) + self.send_and_expect(self.pg0, [p_2k], self.pg1, n_rx=2) + self.send_and_expect(self.pg0, [p_1k], self.pg1) + + # drop the path MTU for this neighbour to below the interface MTU + # expect more frags + pmtu = VppIpPathMtu(self, self.pg1.remote_ip6, 1300).add_vpp_config() + + # print/format the adj delegate and trackers + self.logger.info(self.vapi.cli("sh ip pmtu")) + self.logger.info(self.vapi.cli("sh adj 7")) - # - # An MPLS route for the non-EOS packets - # - route_67 = VppMplsRoute(self, 67, 0, - [VppRoutePath(self.pg1.remote_ip6, - self.pg1.sw_if_index, - labels=[67], - proto=DpoProto.DPO_PROTO_IP6), - VppRoutePath(self.pg2.remote_ip6, - self.pg2.sw_if_index, - labels=[67], - proto=DpoProto.DPO_PROTO_IP6)]) - route_67.add_vpp_config() + self.send_and_expect(self.pg0, [p_2k], self.pg1, n_rx=3) + self.send_and_expect(self.pg0, [p_1k], self.pg1, n_rx=2) - # - # inject the packet on pg0 - expect load-balancing across the 2 paths - # - since the default hash config is to use IP src,dst and port - # src,dst - # We are not going to ensure equal amounts of packets across each link, - # since the hash algorithm is statistical and therefore this can never - # be guaranteed. But wuth 64 different packets we do expect some - # balancing. So instead just ensure there is traffic on each link. - # - self.send_and_expect_load_balancing(self.pg0, port_ip_pkts, - [self.pg1, self.pg2]) - self.send_and_expect_load_balancing(self.pg0, src_ip_pkts, - [self.pg1, self.pg2]) - self.send_and_expect_load_balancing(self.pg0, port_mpls_pkts, - [self.pg1, self.pg2]) - self.send_and_expect_load_balancing(self.pg0, src_mpls_pkts, - [self.pg1, self.pg2]) - self.send_and_expect_load_balancing(self.pg0, port_mpls_neos_pkts, - [self.pg1, self.pg2]) + # increase the path MTU to more than the interface + # expect to use the interface MTU + pmtu.modify(8192) - # - # The packets with Entropy label in should not load-balance, - # since the Entorpy value is fixed. - # - self.send_and_expect_one_itf(self.pg0, port_ent_pkts, self.pg1) + self.send_and_expect(self.pg0, [p_2k], self.pg1, n_rx=2) + self.send_and_expect(self.pg0, [p_1k], self.pg1) - # - # change the flow hash config so it's only IP src,dst - # - now only the stream with differing source address will - # load-balance - # - self.vapi.set_ip_flow_hash(0, is_ip6=1, src=1, dst=1, sport=0, dport=0) + # go back to an MTU from the path + pmtu.modify(1300) - self.send_and_expect_load_balancing(self.pg0, src_ip_pkts, - [self.pg1, self.pg2]) - self.send_and_expect_load_balancing(self.pg0, src_mpls_pkts, - [self.pg1, self.pg2]) - self.send_and_expect_one_itf(self.pg0, port_ip_pkts, self.pg2) + self.send_and_expect(self.pg0, [p_2k], self.pg1, n_rx=3) + self.send_and_expect(self.pg0, [p_1k], self.pg1, n_rx=2) + + # raise the interface's MTU + # should still use that of the path + self.vapi.sw_interface_set_mtu(self.pg1.sw_if_index, [2000, 0, 0, 0]) + self.send_and_expect(self.pg0, [p_2k], self.pg1, n_rx=3) + self.send_and_expect(self.pg0, [p_1k], self.pg1, n_rx=2) - # - # change the flow hash config back to defaults - # - self.vapi.set_ip_flow_hash(0, is_ip6=1, src=1, dst=1, sport=1, dport=1) + # set path high and interface low + pmtu.modify(2000) + self.vapi.sw_interface_set_mtu(self.pg1.sw_if_index, [1300, 0, 0, 0]) + self.send_and_expect(self.pg0, [p_2k], self.pg1, n_rx=3) + self.send_and_expect(self.pg0, [p_1k], self.pg1, n_rx=2) + + # remove the path MTU + self.vapi.sw_interface_set_mtu(self.pg1.sw_if_index, [2800, 0, 0, 0]) + pmtu.modify(0) + + self.send_and_expect(self.pg0, [p_2k], self.pg1, n_rx=2) + self.send_and_expect(self.pg0, [p_1k], self.pg1) + + def test_path_mtu_remote(self): + """Path MTU for remote neighbour""" - # - # Recursive prefixes - # - testing that 2 stages of load-balancing occurs and there is no - # polarisation (i.e. only 2 of 4 paths are used) - # - port_pkts = [] - src_pkts = [] + self.vapi.cli("set log class ip level debug") + # + # The goal here is not test that fragmentation works correctly, + # that's done elsewhere, the intent is to ensure that the Path MTU + # settings are honoured. + # + tun_dst = "2001::1" + + route = VppIpRoute( + self, tun_dst, 64, [VppRoutePath(self.pg1.remote_ip6, self.pg1.sw_if_index)] + ).add_vpp_config() + + # + # IPv6 will only frag locally generated packets, so use tunnelled + # packets post encap + # + tun = VppIpIpTunInterface(self, self.pg1, self.pg1.local_ip6, tun_dst) + tun.add_vpp_config() + tun.admin_up() + tun.config_ip6() + + # set the interface MTU to a reasonable value + self.vapi.sw_interface_set_mtu(self.pg1.sw_if_index, [2800, 0, 0, 0]) + + p_2k = ( + Ether(dst=self.pg0.local_mac, src=self.pg0.remote_mac) + / IPv6(src=self.pg0.remote_ip6, dst=tun.remote_ip6) + / UDP(sport=1234, dport=5678) + / Raw(b"0xa" * 1000) + ) + p_1k = ( + Ether(dst=self.pg0.local_mac, src=self.pg0.remote_mac) + / IPv6(src=self.pg0.remote_ip6, dst=tun.remote_ip6) + / UDP(sport=1234, dport=5678) + / Raw(b"0xa" * 600) + ) - for ii in range(257): - port_pkts.append((Ether(src=self.pg0.remote_mac, - dst=self.pg0.local_mac) / - IPv6(dst="4000::1", src="4000:1::1") / - UDP(sport=1234, dport=1234 + ii) / - Raw('\xa5' * 100))) - src_pkts.append((Ether(src=self.pg0.remote_mac, - dst=self.pg0.local_mac) / - IPv6(dst="4000::1", src="4000:1::%d" % ii) / - UDP(sport=1234, dport=1234) / - Raw('\xa5' * 100))) - - route_3000_2 = VppIpRoute(self, "3000::2", 128, - [VppRoutePath(self.pg3.remote_ip6, - self.pg3.sw_if_index, - proto=DpoProto.DPO_PROTO_IP6), - VppRoutePath(self.pg4.remote_ip6, - self.pg4.sw_if_index, - proto=DpoProto.DPO_PROTO_IP6)], - is_ip6=1) - route_3000_2.add_vpp_config() + nbr = VppNeighbor( + self, self.pg1.sw_if_index, self.pg1.remote_mac, self.pg1.remote_ip6 + ).add_vpp_config() - route_4000_1 = VppIpRoute(self, "4000::1", 128, - [VppRoutePath("3000::1", - 0xffffffff, - proto=DpoProto.DPO_PROTO_IP6), - VppRoutePath("3000::2", - 0xffffffff, - proto=DpoProto.DPO_PROTO_IP6)], - is_ip6=1) - route_4000_1.add_vpp_config() + # this is now the interface MTU frags + self.send_and_expect(self.pg0, [p_2k], self.pg1, n_rx=2) + self.send_and_expect(self.pg0, [p_1k], self.pg1) - # - # inject the packet on pg0 - expect load-balancing across all 4 paths - # - self.vapi.cli("clear trace") - self.send_and_expect_load_balancing(self.pg0, port_pkts, - [self.pg1, self.pg2, - self.pg3, self.pg4]) - self.send_and_expect_load_balancing(self.pg0, src_pkts, - [self.pg1, self.pg2, - self.pg3, self.pg4]) + # drop the path MTU for this neighbour to below the interface MTU + # expect more frags + pmtu = VppIpPathMtu(self, tun_dst, 1300).add_vpp_config() - # - # Recursive prefixes - # - testing that 2 stages of load-balancing no choices - # - port_pkts = [] + # print/format the fib entry/dpo + self.logger.info(self.vapi.cli("sh ip6 fib 2001::1")) - for ii in range(257): - port_pkts.append((Ether(src=self.pg0.remote_mac, - dst=self.pg0.local_mac) / - IPv6(dst="6000::1", src="6000:1::1") / - UDP(sport=1234, dport=1234 + ii) / - Raw('\xa5' * 100))) - - route_5000_2 = VppIpRoute(self, "5000::2", 128, - [VppRoutePath(self.pg3.remote_ip6, - self.pg3.sw_if_index, - proto=DpoProto.DPO_PROTO_IP6)], - is_ip6=1) - route_5000_2.add_vpp_config() + self.send_and_expect(self.pg0, [p_2k], self.pg1, n_rx=3) + self.send_and_expect(self.pg0, [p_1k], self.pg1, n_rx=2) - route_6000_1 = VppIpRoute(self, "6000::1", 128, - [VppRoutePath("5000::2", - 0xffffffff, - proto=DpoProto.DPO_PROTO_IP6)], - is_ip6=1) - route_6000_1.add_vpp_config() + # increase the path MTU to more than the interface + # expect to use the interface MTU + pmtu.modify(8192) - # - # inject the packet on pg0 - expect load-balancing across all 4 paths - # - self.vapi.cli("clear trace") - self.send_and_expect_one_itf(self.pg0, port_pkts, self.pg3) + self.send_and_expect(self.pg0, [p_2k], self.pg1, n_rx=2) + self.send_and_expect(self.pg0, [p_1k], self.pg1) + + # go back to an MTU from the path + pmtu.modify(1300) + + self.send_and_expect(self.pg0, [p_2k], self.pg1, n_rx=3) + self.send_and_expect(self.pg0, [p_1k], self.pg1, n_rx=2) + + # raise the interface's MTU + # should still use that of the path + self.vapi.sw_interface_set_mtu(self.pg1.sw_if_index, [2000, 0, 0, 0]) + self.send_and_expect(self.pg0, [p_2k], self.pg1, n_rx=3) + self.send_and_expect(self.pg0, [p_1k], self.pg1, n_rx=2) + + # turn the tun_dst into an attached neighbour + route.modify([VppRoutePath("::", self.pg1.sw_if_index)]) + nbr2 = VppNeighbor( + self, self.pg1.sw_if_index, self.pg1.remote_mac, tun_dst + ).add_vpp_config() + + self.send_and_expect(self.pg0, [p_2k], self.pg1, n_rx=3) + self.send_and_expect(self.pg0, [p_1k], self.pg1, n_rx=2) + # add back to not attached + nbr2.remove_vpp_config() + route.modify([VppRoutePath(self.pg1.remote_ip6, self.pg1.sw_if_index)]) -class TestIP6Punt(VppTestCase): - """ IPv6 Punt Police/Redirect """ + # set path high and interface low + pmtu.modify(2000) + self.vapi.sw_interface_set_mtu(self.pg1.sw_if_index, [1300, 0, 0, 0]) + self.send_and_expect(self.pg0, [p_2k], self.pg1, n_rx=3) + self.send_and_expect(self.pg0, [p_1k], self.pg1, n_rx=2) + + # remove the path MTU + self.vapi.sw_interface_set_mtu(self.pg1.sw_if_index, [2800, 0, 0, 0]) + pmtu.remove_vpp_config() + self.send_and_expect(self.pg0, [p_2k], self.pg1, n_rx=2) + self.send_and_expect(self.pg0, [p_1k], self.pg1) + + +class TestIPFibSource(VppTestCase): + """IPv6 Table FibSource""" + + @classmethod + def setUpClass(cls): + super(TestIPFibSource, cls).setUpClass() + + @classmethod + def tearDownClass(cls): + super(TestIPFibSource, cls).tearDownClass() def setUp(self): - super(TestIP6Punt, self).setUp() + super(TestIPFibSource, self).setUp() self.create_pg_interfaces(range(2)) for i in self.pg_interfaces: i.admin_up() i.config_ip6() - i.resolve_ndp() + i.resolve_arp() + i.generate_remote_hosts(2) + i.configure_ipv6_neighbors() def tearDown(self): - super(TestIP6Punt, self).tearDown() + super(TestIPFibSource, self).tearDown() for i in self.pg_interfaces: - i.unconfig_ip6() i.admin_down() + i.unconfig_ip4() - def send_and_expect(self, input, pkts, output): - input.add_stream(pkts) - self.pg_enable_capture(self.pg_interfaces) - self.pg_start() - rx = output.get_capture(len(pkts)) - return rx + def test_fib_source(self): + """IP Table FibSource""" + + routes = self.vapi.ip_route_v2_dump(0, True) + + # 2 interfaces (4 routes) + 2 specials + 4 neighbours = 10 routes + self.assertEqual(len(routes), 10) + + # dump all the sources in the FIB + sources = self.vapi.fib_source_dump() + for source in sources: + if source.src.name == "API": + api_source = source.src + if source.src.name == "interface": + intf_source = source.src + if source.src.name == "adjacency": + adj_source = source.src + if source.src.name == "special": + special_source = source.src + if source.src.name == "default-route": + dr_source = source.src + + # dump the individual route types + routes = self.vapi.ip_route_v2_dump(0, True, src=adj_source.id) + self.assertEqual(len(routes), 4) + routes = self.vapi.ip_route_v2_dump(0, True, src=intf_source.id) + self.assertEqual(len(routes), 4) + routes = self.vapi.ip_route_v2_dump(0, True, src=special_source.id) + self.assertEqual(len(routes), 1) + routes = self.vapi.ip_route_v2_dump(0, True, src=dr_source.id) + self.assertEqual(len(routes), 1) + + # add a new soure that'a better than the API + self.vapi.fib_source_add( + src={"name": "bgp", "priority": api_source.priority - 1} + ) + + # dump all the sources to check our new one is there + sources = self.vapi.fib_source_dump() + + for source in sources: + if source.src.name == "bgp": + bgp_source = source.src + + self.assertTrue(bgp_source) + self.assertEqual(bgp_source.priority, api_source.priority - 1) + + # add a route with the default API source + r1 = VppIpRouteV2( + self, + "2001::1", + 128, + [VppRoutePath(self.pg0.remote_ip6, self.pg0.sw_if_index)], + ).add_vpp_config() - def send_and_assert_no_replies(self, intf, pkts, remark): - self.vapi.cli("clear trace") - intf.add_stream(pkts) - self.pg_enable_capture(self.pg_interfaces) - self.pg_start() + r2 = VppIpRouteV2( + self, + "2001::1", + 128, + [VppRoutePath(self.pg1.remote_ip6, self.pg1.sw_if_index)], + src=bgp_source.id, + ).add_vpp_config() + + # ensure the BGP source takes priority + p = ( + Ether(src=self.pg0.remote_mac, dst=self.pg0.local_mac) + / IPv6(src=self.pg0.remote_ip6, dst="2001::1") + / inet6.UDP(sport=1234, dport=1234) + / Raw(b"\xa5" * 100) + ) + + self.send_and_expect(self.pg0, [p], self.pg1) + + r2.remove_vpp_config() + r1.remove_vpp_config() + + self.assertFalse(find_route(self, "2001::1", 128)) + + +class TestIPxAF(VppTestCase): + """IP cross AF""" + + @classmethod + def setUpClass(cls): + super(TestIPxAF, cls).setUpClass() + + @classmethod + def tearDownClass(cls): + super(TestIPxAF, cls).tearDownClass() + + def setUp(self): + super(TestIPxAF, self).setUp() + + self.create_pg_interfaces(range(2)) + + for i in self.pg_interfaces: + i.admin_up() + i.config_ip6() + i.config_ip4() + i.resolve_arp() + i.resolve_ndp() + + def tearDown(self): + super(TestIPxAF, self).tearDown() for i in self.pg_interfaces: - i.get_capture(0) - i.assert_nothing_captured(remark=remark) + i.admin_down() + i.unconfig_ip4() + i.unconfig_ip6() - def test_ip_punt(self): - """ IP6 punt police and redirect """ + def test_x_af(self): + """Cross AF routing""" + + N_PKTS = 63 + # a v4 route via a v6 attached next-hop + VppIpRoute( + self, + "1.1.1.1", + 32, + [VppRoutePath(self.pg1.remote_ip6, self.pg1.sw_if_index)], + ).add_vpp_config() + + p = ( + Ether(src=self.pg0.remote_mac, dst=self.pg0.local_mac) + / IP(src=self.pg0.remote_ip4, dst="1.1.1.1") + / UDP(sport=1234, dport=1234) + / Raw(b"\xa5" * 100) + ) + rxs = self.send_and_expect(self.pg0, p * N_PKTS, self.pg1) + + for rx in rxs: + self.assertEqual(rx[IP].dst, "1.1.1.1") + + # a v6 route via a v4 attached next-hop + VppIpRoute( + self, + "2001::1", + 128, + [VppRoutePath(self.pg1.remote_ip4, self.pg1.sw_if_index)], + ).add_vpp_config() + + p = ( + Ether(src=self.pg0.remote_mac, dst=self.pg0.local_mac) + / IPv6(src=self.pg0.remote_ip6, dst="2001::1") + / UDP(sport=1234, dport=1234) + / Raw(b"\xa5" * 100) + ) + rxs = self.send_and_expect(self.pg0, p * N_PKTS, self.pg1) + + for rx in rxs: + self.assertEqual(rx[IPv6].dst, "2001::1") + + # a recursive v4 route via a v6 next-hop (from above) + VppIpRoute( + self, "2.2.2.2", 32, [VppRoutePath("2001::1", 0xFFFFFFFF)] + ).add_vpp_config() + + p = ( + Ether(src=self.pg0.remote_mac, dst=self.pg0.local_mac) + / IP(src=self.pg0.remote_ip4, dst="2.2.2.2") + / UDP(sport=1234, dport=1234) + / Raw(b"\xa5" * 100) + ) + rxs = self.send_and_expect(self.pg0, p * N_PKTS, self.pg1) + + # a recursive v4 route via a v6 next-hop + VppIpRoute( + self, "2.2.2.3", 32, [VppRoutePath(self.pg1.remote_ip6, 0xFFFFFFFF)] + ).add_vpp_config() + + p = ( + Ether(src=self.pg0.remote_mac, dst=self.pg0.local_mac) + / IP(src=self.pg0.remote_ip4, dst="2.2.2.3") + / UDP(sport=1234, dport=1234) + / Raw(b"\xa5" * 100) + ) + rxs = self.send_and_expect(self.pg0, p * N_PKTS, self.pg1) + + # a recursive v6 route via a v4 next-hop + VppIpRoute( + self, "3001::1", 128, [VppRoutePath(self.pg1.remote_ip4, 0xFFFFFFFF)] + ).add_vpp_config() + + p = ( + Ether(src=self.pg0.remote_mac, dst=self.pg0.local_mac) + / IPv6(src=self.pg0.remote_ip6, dst="3001::1") + / UDP(sport=1234, dport=1234) + / Raw(b"\xa5" * 100) + ) + rxs = self.send_and_expect(self.pg0, p * N_PKTS, self.pg1) + + for rx in rxs: + self.assertEqual(rx[IPv6].dst, "3001::1") + + VppIpRoute( + self, "3001::2", 128, [VppRoutePath("1.1.1.1", 0xFFFFFFFF)] + ).add_vpp_config() + + p = ( + Ether(src=self.pg0.remote_mac, dst=self.pg0.local_mac) + / IPv6(src=self.pg0.remote_ip6, dst="3001::2") + / UDP(sport=1234, dport=1234) + / Raw(b"\xa5" * 100) + ) + rxs = self.send_and_expect(self.pg0, p * N_PKTS, self.pg1) + + for rx in rxs: + self.assertEqual(rx[IPv6].dst, "3001::2") + + +class TestIPv6Punt(VppTestCase): + """IPv6 Punt Police/Redirect""" + + def setUp(self): + super(TestIPv6Punt, self).setUp() + self.create_pg_interfaces(range(4)) + + for i in self.pg_interfaces: + i.admin_up() + i.config_ip6() + i.resolve_ndp() - p = (Ether(src=self.pg0.remote_mac, - dst=self.pg0.local_mac) / - IPv6(src=self.pg0.remote_ip6, dst=self.pg0.local_ip6) / - TCP(sport=1234, dport=1234) / - Raw('\xa5' * 100)) + def tearDown(self): + super(TestIPv6Punt, self).tearDown() + for i in self.pg_interfaces: + i.unconfig_ip6() + i.admin_down() - pkts = p * 1025 + def test_ip6_punt(self): + """IPv6 punt police and redirect""" + + # use UDP packet that have a port we need to explicitly + # register to get punted. + pt_l4 = VppEnum.vl_api_punt_type_t.PUNT_API_TYPE_L4 + af_ip6 = VppEnum.vl_api_address_family_t.ADDRESS_IP6 + udp_proto = VppEnum.vl_api_ip_proto_t.IP_API_PROTO_UDP + punt_udp = { + "type": pt_l4, + "punt": { + "l4": { + "af": af_ip6, + "protocol": udp_proto, + "port": 7654, + } + }, + } + + self.vapi.set_punt(is_add=1, punt=punt_udp) + + pkts = ( + Ether(src=self.pg0.remote_mac, dst=self.pg0.local_mac) + / IPv6(src=self.pg0.remote_ip6, dst=self.pg0.local_ip6) + / UDP(sport=1234, dport=7654) + / Raw(b"\xa5" * 100) + ) * 1025 # # Configure a punt redirect via pg1. # - nh_addr = inet_pton(AF_INET6, - self.pg1.remote_ip6) - self.vapi.ip_punt_redirect(self.pg0.sw_if_index, - self.pg1.sw_if_index, - nh_addr, - is_ip6=1) + nh_addr = self.pg1.remote_ip6 + ip_punt_redirect = VppIpPuntRedirect( + self, self.pg0.sw_if_index, self.pg1.sw_if_index, nh_addr + ) + ip_punt_redirect.add_vpp_config() self.send_and_expect(self.pg0, pkts, self.pg1) # # add a policer # - policer = self.vapi.policer_add_del("ip6-punt", 400, 0, 10, 0, - rate_type=1) - self.vapi.ip_punt_police(policer.policer_index, is_ip6=1) + policer = VppPolicer(self, "ip6-punt", 400, 0, 10, 0, rate_type=1) + policer.add_vpp_config() + ip_punt_policer = VppIpPuntPolicer(self, policer.policer_index, is_ip6=True) + ip_punt_policer.add_vpp_config() self.vapi.cli("clear trace") self.pg0.add_stream(pkts) @@ -1578,121 +4011,214 @@ class TestIP6Punt(VppTestCase): self.pg_start() # - # the number of packet recieved should be greater than 0, + # the number of packet received should be greater than 0, # but not equal to the number sent, since some were policed # rx = self.pg1._get_capture(1) - self.assertTrue(len(rx) > 0) - self.assertTrue(len(rx) < len(pkts)) + + stats = policer.get_stats() + + # Single rate policer - expect conform, violate but no exceed + self.assertGreater(stats["conform_packets"], 0) + self.assertEqual(stats["exceed_packets"], 0) + self.assertGreater(stats["violate_packets"], 0) + + self.assertGreater(len(rx), 0) + self.assertLess(len(rx), len(pkts)) # - # remove the poilcer. back to full rx + # remove the policer. back to full rx # - self.vapi.ip_punt_police(policer.policer_index, is_add=0, is_ip6=1) - self.vapi.policer_add_del("ip6-punt", 400, 0, 10, 0, - rate_type=1, is_add=0) + ip_punt_policer.remove_vpp_config() + policer.remove_vpp_config() self.send_and_expect(self.pg0, pkts, self.pg1) # # remove the redirect. expect full drop. # - self.vapi.ip_punt_redirect(self.pg0.sw_if_index, - self.pg1.sw_if_index, - nh_addr, - is_add=0, - is_ip6=1) - self.send_and_assert_no_replies(self.pg0, pkts, - "IP no punt config") + ip_punt_redirect.remove_vpp_config() + self.send_and_assert_no_replies(self.pg0, pkts, "IP no punt config") # # Add a redirect that is not input port selective # - self.vapi.ip_punt_redirect(0xffffffff, - self.pg1.sw_if_index, - nh_addr, - is_ip6=1) + ip_punt_redirect = VppIpPuntRedirect( + self, 0xFFFFFFFF, self.pg1.sw_if_index, nh_addr + ) + ip_punt_redirect.add_vpp_config() self.send_and_expect(self.pg0, pkts, self.pg1) + ip_punt_redirect.remove_vpp_config() - self.vapi.ip_punt_redirect(0xffffffff, - self.pg1.sw_if_index, - nh_addr, - is_add=0, - is_ip6=1) + def test_ip6_punt_dump(self): + """IPv6 punt redirect dump""" + # + # Configure a punt redirects + # + nh_address = self.pg3.remote_ip6 + ipr_03 = VppIpPuntRedirect( + self, self.pg0.sw_if_index, self.pg3.sw_if_index, nh_address + ) + ipr_13 = VppIpPuntRedirect( + self, self.pg1.sw_if_index, self.pg3.sw_if_index, nh_address + ) + ipr_23 = VppIpPuntRedirect( + self, self.pg2.sw_if_index, self.pg3.sw_if_index, "::" + ) + ipr_03.add_vpp_config() + ipr_13.add_vpp_config() + ipr_23.add_vpp_config() -class TestIP6Input(VppTestCase): - """ IPv6 Input Exceptions """ + # + # Dump pg0 punt redirects + # + self.assertTrue(ipr_03.query_vpp_config()) + self.assertTrue(ipr_13.query_vpp_config()) + self.assertTrue(ipr_23.query_vpp_config()) + + # + # Dump punt redirects for all interfaces + # + punts = self.vapi.ip_punt_redirect_dump(sw_if_index=0xFFFFFFFF, is_ipv6=True) + self.assertEqual(len(punts), 3) + for p in punts: + self.assertEqual(p.punt.tx_sw_if_index, self.pg3.sw_if_index) + self.assertNotEqual(punts[1].punt.nh, self.pg3.remote_ip6) + self.assertEqual(str(punts[2].punt.nh), "::") + + +class TestIP6InterfaceRx(VppTestCase): + """IPv6 Interface Receive""" + + @classmethod + def setUpClass(cls): + super(TestIP6InterfaceRx, cls).setUpClass() + + @classmethod + def tearDownClass(cls): + super(TestIP6InterfaceRx, cls).tearDownClass() def setUp(self): - super(TestIP6Input, self).setUp() + super(TestIP6InterfaceRx, self).setUp() - self.create_pg_interfaces(range(2)) + self.create_pg_interfaces(range(3)) + + table_id = 0 for i in self.pg_interfaces: i.admin_up() + + if table_id != 0: + table = VppIpTable(self, table_id, is_ip6=1) + table.add_vpp_config() + + i.set_table_ip6(table_id) i.config_ip6() i.resolve_ndp() + table_id += 1 def tearDown(self): - super(TestIP6Input, self).tearDown() for i in self.pg_interfaces: i.unconfig_ip6() i.admin_down() + i.set_table_ip6(0) - def send_and_expect(self, input, pkts, output): - self.vapi.cli("clear trace") - input.add_stream(pkts) - self.pg_enable_capture(self.pg_interfaces) - self.pg_start() - rx = output.get_capture(len(pkts)) - return rx + super(TestIP6InterfaceRx, self).tearDown() - def send_and_assert_no_replies(self, intf, pkts, remark): - self.vapi.cli("clear trace") - intf.add_stream(pkts) - self.pg_enable_capture(self.pg_interfaces) - self.pg_start() - for i in self.pg_interfaces: - i.get_capture(0) - i.assert_nothing_captured(remark=remark) + def test_interface_rx(self): + """IPv6 Interface Receive""" + + # + # add a route in the default table to receive ... + # + route_to_dst = VppIpRoute( + self, + "1::", + 122, + [ + VppRoutePath( + "::", + self.pg1.sw_if_index, + type=FibPathType.FIB_PATH_TYPE_INTERFACE_RX, + ) + ], + ) + route_to_dst.add_vpp_config() + + # + # packets to these destination are dropped, since they'll + # hit the respective default routes in table 1 + # + p_dst = ( + Ether(src=self.pg0.remote_mac, dst=self.pg0.local_mac) + / IPv6(src="5::5", dst="1::1") + / inet6.TCP(sport=1234, dport=1234) + / Raw(b"\xa5" * 100) + ) + pkts_dst = p_dst * 10 - def test_ip_input(self): - """ IP6 Input Exceptions """ + self.send_and_assert_no_replies(self.pg0, pkts_dst, "IP in table 1") # - # bad version - this is dropped + # add a route in the dst table to forward via pg1 # - p_version = (Ether(src=self.pg0.remote_mac, - dst=self.pg0.local_mac) / - IPv6(src=self.pg0.remote_ip6, - dst=self.pg1.remote_ip6, - version=3) / - UDP(sport=1234, dport=1234) / - Raw('\xa5' * 100)) + route_in_dst = VppIpRoute( + self, + "1::1", + 128, + [VppRoutePath(self.pg1.remote_ip6, self.pg1.sw_if_index)], + table_id=1, + ) + route_in_dst.add_vpp_config() - self.send_and_assert_no_replies(self.pg0, p_version * 65, - "funky version") + self.send_and_expect(self.pg0, pkts_dst, self.pg1) # - # hop limit - IMCP replies + # add a route in the default table to receive ... # - p_version = (Ether(src=self.pg0.remote_mac, - dst=self.pg0.local_mac) / - IPv6(src=self.pg0.remote_ip6, - dst=self.pg1.remote_ip6, - hlim=1) / - UDP(sport=1234, dport=1234) / - Raw('\xa5' * 100)) + route_to_dst = VppIpRoute( + self, + "1::", + 122, + [ + VppRoutePath( + "::", + self.pg2.sw_if_index, + type=FibPathType.FIB_PATH_TYPE_INTERFACE_RX, + ) + ], + table_id=1, + ) + route_to_dst.add_vpp_config() - rx = self.send_and_expect(self.pg0, p_version * 65, self.pg0) - rx = rx[0] - icmp = rx[ICMPv6TimeExceeded] - self.assertEqual(icmp.type, 3) - # 0: "hop limit exceeded in transit", - self.assertEqual(icmp.code, 0) + # + # packets to these destination are dropped, since they'll + # hit the respective default routes in table 2 + # + p_dst = ( + Ether(src=self.pg0.remote_mac, dst=self.pg0.local_mac) + / IPv6(src="6::6", dst="1::2") + / inet6.TCP(sport=1234, dport=1234) + / Raw(b"\xa5" * 100) + ) + pkts_dst = p_dst * 10 + + self.send_and_assert_no_replies(self.pg0, pkts_dst, "IP in table 2") + + # + # add a route in the table 2 to forward via pg2 + # + route_in_dst = VppIpRoute( + self, + "1::2", + 128, + [VppRoutePath(self.pg2.remote_ip6, self.pg2.sw_if_index)], + table_id=2, + ) + route_in_dst.add_vpp_config() - self.logger.error(self.vapi.cli("sh error")) + self.send_and_expect(self.pg0, pkts_dst, self.pg2) -if __name__ == '__main__': +if __name__ == "__main__": unittest.main(testRunner=VppTestRunner)