summaryrefslogtreecommitdiffstats
path: root/docs/gettingstarted/installing/index.rst
AgeCommit message (Expand)AuthorFilesLines
2018-08-17docs: Moved installing up a level, removed guides.John DeNisco1-0/+50
7' href='#n67'>67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195
                                 Apache License
                           Version 2.0, January 2004
                        http://www.apache.org/licenses/

   TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION

   1. Definitions.

      "License" shall mean the terms and conditions for use, reproduction,
      and distribution as defined by Sections 1 through 9 of this document.

      "Licensor" shall mean the copyright owner or entity authorized by
      the copyright owner that is granting the License.

      "Legal Entity" shall mean the union of the acting entity and all
      other entities that control, are controlled by, or are under common
      control with that entity. For the purposes of this definition,
      "control" means (i) the power, direct or indirect, to cause the
      direction or management of such entity, whether by contract or
      otherwise, or (ii) ownership of fifty percent (50%) or more of the
      outstanding shares, or (iii) beneficial ownership of such entity.

      "You" (or "Your") shall mean an individual or Legal Entity
      exercising permissions granted by this License.

      "Source" form shall mean the preferred form for making modifications,
      including but not limited to software source code, documentation
      source, and configuration files.

      "Object" form shall mean any form resulting from mechanical
      transformation or translation of a Source form, including but
      not limited to compiled object code, generated documentation,
      and conversions to other media types.

      "Work" shall mean the work of authorship, whether in Source or
      Object form, made available under the License, as indicated by a
      copyright notice that is included in or attached to the work
      (an example is provided in the Appendix below).

      "Derivative Works" shall mean any work, whether in Source or Object
      form, that is based on (or derived from) the Work and for which the
      editorial revisions, annotations, elaborations, or other modifications
      represent, as a whole, an original work of authorship. For the purposes
      of this License, Derivative Works shall not include works that remain
      separable from, or merely link (or bind by name) to the interfaces of,
      the Work and Derivative Works thereof.

      "Contribution" shall mean any work of authorship, including
      the original version of the Work and any modifications or additions
      to that Work or Derivative Works thereof, that is intentionally
      submitted to Licensor for inclusion in the Work by the copyright owner
      or by an individual or Legal Entity authorized to submit on behalf of
      the copyright owner. For the purposes of this definition, "submitted"
      means any form of electronic, verbal, or written communication sent
      to the Licensor or its representatives, including but not limited to
      communication on electronic mailing lists, source code control systems,
      and issue tracking systems that are managed by, or on behalf of, the
      Licensor for the purpose of discussing and improving the Work, but
      excluding communication that is conspicuously marked or otherwise
      designated in writing by the copyright owner as "Not a Contribution."

      "Contributor" shall mean Licensor and any individual or Legal Entity
      on behalf of whom a Contribution has been received by Licensor and
      subsequently incorporated within the Work.

   2. Grant of Copyright License. Subject to the terms and conditions of
      this License, each Contributor hereby grants to You a perpetual,
      worldwide, non-exclusive, no-charge, royalty-free, irrevocable
      copyright license to reproduce, prepare Derivative Works of,
      publicly display, publicly perform, sublicense, and distribute the
      Work and such Derivative Works in Source or Object form.

   3. Grant of Patent License. Subject to the terms and conditions of
      this License, each Contributor hereby grants to You a perpetual,
      worldwide, non-exclusive, no-charge, royalty-free, irrevocable
      (except as stated in this section) patent license to make, have made,
      use, offer to sell, sell, import, and otherwise transfer the Work,
      where such license applies only to those patent claims licensable
      by such Contributor that are necessarily infringed by their
      Contribution(s) alone or by combination of their Contribution(s)
      with the Work to which such Contribution(s) was submitted. If You
      institute patent litigation against any entity (including a
      cross-claim or counterclaim in a lawsuit) alleging that the Work
      or a Contribution incorporated within the Work constitutes direct
      or contributory patent infringement, then any patent licenses
      granted to You under this License for that Work shall terminate
      as of the date such litigation is filed.

   4. Redistribution. You may reproduce and distribute copies of the
      Work or Derivative Works thereof in any medium, with or without
      modifications, and in Source or Object form, provided that You
      meet the following conditions:

      (a) You must give any other recipients of the Work or
          Derivative Works a copy of this License; and

      (b) You must cause any modified files to carry prominent notices
          stating that You changed the files; and

      (c) You must retain, in the Source form of any Derivative Works
          that You distribute, all copyright, patent, trademark, and
          attribution notices from the Source form of the Work,
          excluding those notices that do not pertain to any part of
          the Derivative Works; and

      (d) If the Work includes a "NOTICE" text file as part of its
          distribution, then any Derivative Works that You distribute must
          include a readable copy of the attribution notices contained
          within such NOTICE file, excluding those notices that do not
          p
#!/usr/bin/env python3

import unittest
import binascii
from socket import AF_INET6

from framework import VppTestCase, VppTestRunner
from vpp_ip import DpoProto
from vpp_ip_route import VppIpRoute, VppRoutePath, VppIpTable

import scapy.compat
from scapy.packet import Raw
from scapy.layers.l2 import Ether, Dot1Q
from scapy.layers.inet6 import IPv6, UDP, IPv6ExtHdrSegmentRouting
from scapy.layers.inet import IP, UDP

from util import ppp


class TestSRv6(VppTestCase):
    """ SRv6 Flow-based Dynamic Proxy plugin Test Case """

    @classmethod
    def setUpClass(self):
        super(TestSRv6, self).setUpClass()

    @classmethod
    def tearDownClass(cls):
        super(TestSRv6, cls).tearDownClass()

    def setUp(self):
        """ Perform test setup before each test case.
        """
        super(TestSRv6, self).setUp()

        # packet sizes, inclusive L2 overhead
        self.pg_packet_sizes = [64, 512, 1518, 9018]

        # reset packet_infos
        self.reset_packet_infos()

    def tearDown(self):
        """ Clean up test setup after each test case.
        """
        self.teardown_interfaces()

        super(TestSRv6, self).tearDown()

    def configure_interface(self,
                            interface,
                            ipv6=False, ipv4=False,
                            ipv6_table_id=0, ipv4_table_id=0):
        """ Configure interface.
        :param ipv6: configure IPv6 on interface
        :param ipv4: configure IPv4 on interface
        :param ipv6_table_id: FIB table_id for IPv6
        :param ipv4_table_id: FIB table_id for IPv4
        """
        self.logger.debug("Configuring interface %s" % (interface.name))
        if ipv6:
            self.logger.debug("Configuring IPv6")
            interface.set_table_ip6(ipv6_table_id)
            interface.config_ip6()
            interface.resolve_ndp(timeout=5)
        if ipv4:
            self.logger.debug("Configuring IPv4")
            interface.set_table_ip4(ipv4_table_id)
            interface.config_ip4()
            interface.resolve_arp()
        interface.admin_up()

    def setup_interfaces(self, ipv6=[], ipv4=[],
                         ipv6_table_id=[], ipv4_table_id=[]):
        """ Create and configure interfaces.

        :param ipv6: list of interface IPv6 capabilities
        :param ipv4: list of interface IPv4 capabilities
        :param ipv6_table_id: list of intf IPv6 FIB table_ids
        :param ipv4_table_id: list of intf IPv4 FIB table_ids
        :returns: List of created interfaces.
        """
        # how many interfaces?
        if len(ipv6):
            count = len(ipv6)
        else:
            count = len(ipv4)
        self.logger.debug("Creating and configuring %d interfaces" % (count))

        # fill up ipv6 and ipv4 lists if needed
        # not enabled (False) is the default
        if len(ipv6) < count:
            ipv6 += (count - len(ipv6)) * [False]
        if len(ipv4) < count:
            ipv4 += (count - len(ipv4)) * [False]

        # fill up table_id lists if needed
        # table_id 0 (global) is the default
        if len(ipv6_table_id) < count:
            ipv6_table_id += (count - len(ipv6_table_id)) * [0]
        if len(ipv4_table_id) < count:
            ipv4_table_id += (count - len(ipv4_table_id)) * [0]

        # create 'count' pg interfaces
        self.create_pg_interfaces(range(count))

        # setup all interfaces
        for i in range(count):
            intf = self.pg_interfaces[i]
            self.configure_interface(intf,
                                     ipv6[i], ipv4[i],
                                     ipv6_table_id[i], ipv4_table_id[i])

        if any(ipv6):
            self.logger.debug(self.vapi.cli("show ip6 neighbors"))
        if any(ipv4):
            self.logger.debug(self.vapi.cli("show ip4 neighbors"))
        self.logger.debug(self.vapi.cli("show interface"))
        self.logger.debug(self.vapi.cli("show hardware"))

        return self.pg_interfaces

    def teardown_interfaces(self):
        """ Unconfigure and bring down interface.
        """
        self.logger.debug("Tearing down interfaces")
        # tear down all interfaces
        # AFAIK they cannot be deleted
        for i in self.pg_interfaces:
            self.logger.debug("Tear down interface %s" % (i.name))
            i.admin_down()
            i.unconfig()
            i.set_table_ip4(0)
            i.set_table_ip6(0)

    def test_SRv6_End_AD_IPv6(self):
        """ Test SRv6 End.AD behavior with IPv6 traffic.
        """
        self.src_addr = 'a0::'
        self.sid_list = ['a1::', 'a2::a6', 'a3::']
        self.test_sid_index = 1

        # send traffic to one destination interface
        # source and destination interfaces are IPv6 only
        self.setup_interfaces(ipv6=[True, True])

        # configure route to next segment
        route = VppIpRoute(self, self.sid_list[self.test_sid_index + 1], 128,
                           [VppRoutePath(self.pg0.remote_ip6,
                                         self.pg0.sw_if_index,
                                         proto=DpoProto.DPO_PROTO_IP6)])
        route.add_vpp_config()

        # configure SRv6 localSID behavior
        cli_str = "sr localsid address " + \
                  self.sid_list[self.test_sid_index] + \
                  " behavior end.ad.flow" + \
                  " nh " + self.pg1.remote_ip6 + \
                  " oif " + self.pg1.name + \
                  " iif " + self.pg1.name
        self.vapi.cli(cli_str)

        # log the localsids
        self.logger.debug(self.vapi.cli("show sr localsid"))

        # send one packet per packet size
        count = len(self.pg_packet_sizes)

        # prepare IPv6 in SRv6 headers
        packet_header1 = self.create_packet_header_IPv6_SRH_IPv6(
            srcaddr=self.src_addr,
            sidlist=self.sid_list[::-1],
            segleft=len(self.sid_list) - self.test_sid_index - 1)

        # generate packets (pg0->pg1)
        pkts1 = self.create_stream(self.pg0, self.pg1, packet_header1,
                                   self.pg_packet_sizes, count)

        # send packets and verify received packets
        self.send_and_verify_pkts(self.pg0, pkts1, self.pg1,
                                  self.compare_rx_tx_packet_End_AD_IPv6_out)

        # log the localsid counters
        self.logger.info(self.vapi.cli("show sr localsid"))

        # prepare IPv6 header for returning packets
        packet_header2 = self.create_packet_header_IPv6()

        # generate returning packets (pg1->pg0)
        pkts2 = self.create_stream(self.pg1, self.pg0, packet_header2,
                                   self.pg_packet_sizes, count)

        # send packets and verify received packets
        self.send_and_verify_pkts(self.pg1, pkts2, self.pg0,
                                  self.compare_rx_tx_packet_End_AD_IPv6_in)

        # log the localsid counters
        self.logger.info(self.vapi.cli("show sr localsid"))

        # remove SRv6 localSIDs
        cli_str = "sr localsid del address " + \
                  self.sid_list[self.test_sid_index]
        self.vapi.cli(cli_str)

        # cleanup interfaces
        self.teardown_interfaces()

    def compare_rx_tx_packet_End_AD_IPv6_out(self, tx_pkt, rx_pkt):
        """ Compare input and output packet after passing End.AD with IPv6

        :param tx_pkt: transmitted packet
        :param rx_pkt: received packet
        """

        # get first (outer) IPv6 header of rx'ed packet
        rx_ip = rx_pkt.getlayer(IPv6)

        tx_ip = tx_pkt.getlayer(IPv6)
        tx_ip2 = tx_pkt.getlayer(IPv6, 2)

        # verify if rx'ed packet has no SRH
        self.assertFalse(rx_pkt.haslayer(IPv6ExtHdrSegmentRouting))

        # the whole rx_ip pkt should be equal to tx_ip2
        # except for the hlim field
        #   -> adjust tx'ed hlim to expected hlim
        tx_ip2.hlim = tx_ip2.hlim - 1

        self.assertEqual(rx_ip, tx_ip2)

        self.logger.debug("packet verification: SUCCESS")

    def compare_rx_tx_packet_End_AD_IPv6_in(self, tx_pkt, rx_pkt):
        """ Compare input and output packet after passing End.AD

        :param tx_pkt: transmitted packet
        :param rx_pkt: received packet
        """

        # get first (outer) IPv6 header of rx'ed packet
        rx_ip = rx_pkt.getlayer(IPv6)
        # received ip.src should be equal to SR Policy source
        self.assertEqual(rx_ip.src, self.src_addr)
        # received ip.dst should be equal to expected sidlist next segment
        self.assertEqual(rx_ip.dst, self.sid_list[self.test_sid_index + 1])

        # rx'ed packet should have SRH
        self.assertTrue(rx_pkt.haslayer(IPv6ExtHdrSegmentRouting))

        # get SRH
        rx_srh = rx_pkt.getlayer(IPv6ExtHdrSegmentRouting)
        # rx'ed seglist should be equal to SID-list in reversed order
        self.assertEqual(rx_srh.addresses, self.sid_list[::-1])
        # segleft should be equal to previous segleft value minus 1
        self.assertEqual(rx_srh.segleft,
                         len(self.sid_list) - self.test_sid_index - 2)
        # lastentry should be equal to the SID-list length minus 1
        self.assertEqual(rx_srh.lastentry, len(self.sid_list) - 1)

        # the whole rx'ed pkt beyond SRH should be equal to tx'ed pkt
        # except for the hop-limit field
        tx_ip = tx_pkt.getlayer(IPv6)
        #   -> update tx'ed hlim to the expected hlim
        tx_ip.hlim -= 1
        #   -> check payload
        self.assertEqual(rx_srh.payload, tx_ip)

        self.logger.debug("packet verification: SUCCESS")

    def test_SRv6_End_AD_IPv4(self):
        """ Test SRv6 End.AD behavior with IPv4 traffic.
        """
        self.src_addr = 'a0::'
        self.sid_list = ['a1::', 'a2::a4', 'a3::']
        self.test_sid_index = 1

        # send traffic to one destination interface
        # source and destination interfaces are IPv6 only
        self.setup_interfaces(ipv6=[True, False], ipv4=[False, True])

        # configure route to next segment
        route = VppIpRoute(self, self.sid_list[self.test_sid_index + 1], 128,
                           [VppRoutePath(self.pg0.remote_ip6,
                                         self.pg0.sw_if_index,
                                         proto=DpoProto.DPO_PROTO_IP6)])
        route.add_vpp_config()

        # configure SRv6 localSID behavior
        cli_str = "sr localsid address " + \
                  self.sid_list[self.test_sid_index] + \
                  " behavior end.ad.flow" + \
                  " nh " + self.pg1.remote_ip4 + \
                  " oif " + self.pg1.name + \
                  " iif " + self.pg1.name
        self.vapi.cli(cli_str)

        # log the localsids
        self.logger.debug(self.vapi.cli("show sr localsid"))

        # send one packet per packet size
        count = len(self.pg_packet_sizes)

        # prepare IPv4 in SRv6 headers
        packet_header1 = self.create_packet_header_IPv6_SRH_IPv4(
            srcaddr=self.src_addr,
            sidlist=self.sid_list[::-1],
            segleft=len(self.sid_list) - self.test_sid_index - 1)

        # generate packets (pg0->pg1)
        pkts1 = self.create_stream(self.pg0, self.pg1, packet_header1,
                                   self.pg_packet_sizes, count)

        # send packets and verify received packets
        self.send_and_verify_pkts(self.pg0, pkts1, self.pg1,
                                  self.compare_rx_tx_packet_End_AD_IPv4_out)

        # log the localsid counters
        self.logger.info(self.vapi.cli("show sr localsid"))

        # prepare IPv6 header for returning packets
        packet_header2 = self.create_packet_header_IPv4()

        # generate returning packets (pg1->pg0)
        pkts2 = self.create_stream(self.pg1, self.pg0, packet_header2,
                                   self.pg_packet_sizes, count)

        # send packets and verify received packets
        self.send_and_verify_pkts(self.pg1, pkts2, self.pg0,
                                  self.compare_rx_tx_packet_End_AD_IPv4_in)

        # log the localsid counters
        self.logger.info(self.vapi.cli("show sr localsid"))

        # remove SRv6 localSIDs
        cli_str = "sr localsid del address " + \
                  self.sid_list[self.test_sid_index]
        self.vapi.cli(cli_str)

        # cleanup interfaces
        self.teardown_interfaces()

    def compare_rx_tx_packet_End_AD_IPv4_out(self, tx_pkt, rx_pkt):
        """ Compare input and output packet after passing End.AD with IPv4

        :param tx_pkt: transmitted packet
        :param rx_pkt: received packet
        """

        # get IPv4 header of rx'ed packet
        rx_ip = rx_pkt.getlayer(IP)

        tx_ip = tx_pkt.getlayer(IPv6)
        tx_ip2 = tx_pkt.getlayer(IP)

        # verify if rx'ed packet has no SRH
        self.assertFalse(rx_pkt.haslayer(IPv6ExtHdrSegmentRouting))

        # the whole rx_ip pkt should be equal to tx_ip2
        # except for the ttl field and ip checksum
        #   -> adjust tx'ed ttl to expected ttl
        tx_ip2.ttl = tx_ip2.ttl - 1
        #   -> set tx'ed ip checksum to None and let scapy recompute
        tx_ip2.chksum = None
        # read back the pkt (with str()) to force computing these fields
        # probably other ways to accomplish this are possible
        tx_ip2 = IP(scapy.compat.raw(tx_ip2))

        self.assertEqual(rx_ip, tx_ip2)

        self.logger.debug("packet verification: SUCCESS")

    def compare_rx_tx_packet_End_AD_IPv4_in(self, tx_pkt, rx_pkt):
        """ Compare input and output packet after passing End.AD

        :param tx_pkt: transmitted packet
        :param rx_pkt: received packet
        """

        # get first (outer) IPv6 header of rx'ed packet
        rx_ip = rx_pkt.getlayer(IPv6)
        # received ip.src should be equal to SR Policy source
        self.assertEqual(rx_ip.src, self.src_addr)
        # received ip.dst should be equal to expected sidlist next segment
        self.assertEqual(rx_ip.dst, self.sid_list[self.test_sid_index + 1])

        # rx'ed packet should have SRH
        self.assertTrue(rx_pkt.haslayer(IPv6ExtHdrSegmentRouting))

        # get SRH
        rx_srh = rx_pkt.getlayer(IPv6ExtHdrSegmentRouting)
        # rx'ed seglist should be equal to SID-list in reversed order
        self.assertEqual(rx_srh.addresses, self.sid_list[::-1])
        # segleft should be equal to previous segleft value minus 1
        self.assertEqual(rx_srh.segleft,
                         len(self.sid_list) - self.test_sid_index - 2)
        # lastentry should be equal to the SID-list length minus 1
        self.assertEqual(rx_srh.lastentry, len(self.sid_list) - 1)

        # the whole rx'ed pkt beyond SRH should be equal to tx'ed pkt
        # except for the ttl field and ip checksum
        tx_ip = tx_pkt.getlayer(IP)
        #   -> adjust tx'ed ttl to expected ttl
        tx_ip.ttl = tx_ip.ttl - 1
        #   -> set tx'ed ip checksum to None and let scapy recompute
        tx_ip.chksum = None
        #   -> read back the pkt (with str()) to force computing these fields
        # probably other ways to accomplish this are possible
        self.assertEqual(rx_srh.payload, IP(scapy.compat.raw(tx_ip)))

        self.logger.debug("packet verification: SUCCESS")

    def create_stream(self, src_if, dst_if, packet_header, packet_sizes,
                      count):
        """Create SRv6 input packet stream for defined interface.

        :param VppInterface src_if: Interface to create packet stream for
        :param VppInterface dst_if: destination interface of packet stream
        :param packet_header: Layer3 scapy packet headers,
                L2 is added when not provided,
                Raw(payload) with packet_info is added
        :param list packet_sizes: packet stream pckt sizes,sequentially applied
               to packets in stream have
        :param int count: number of packets in packet stream
        :return: list of packets
        """
        self.logger.info("Creating packets")
        pkts = []
        for i in range(0, count - 1):
            payload_info = self.create_packet_info(src_if, dst_if)
            self.logger.debug(
                "Creating packet with index %d" % (payload_info.index))
            payload = self.info_to_payload(payload_info)
            # add L2 header if not yet provided in packet_header
            if packet_header.getlayer(0).name == 'Ethernet':
                p = packet_header / Raw(payload)
            else:
                p = Ether(dst=src_if.local_mac, src=src_if.remote_mac) / \
                    packet_header / Raw(payload)
            size = packet_sizes[i % len(packet_sizes)]
            self.logger.debug("Packet size %d" % (size))
            self.extend_packet(p, size)
            # we need to store the packet with the automatic fields computed
            # read back the dumped packet (with str())
            # to force computing these fields
            # probably other ways are possible
            p = Ether(scapy.compat.raw(p))
            payload_info.data = p.copy()
            self.logger.debug(ppp("Created packet:", p))
            pkts.append(p)
        self.logger.info("Done creating packets")
        return pkts

    def send_and_verify_pkts(self, input, pkts, output, compare_func):
        """Send packets and verify received packets using compare_func

        :param input: ingress interface of DUT
        :param pkts: list of packets to transmit
        :param output: egress interface of DUT
        :param compare_func: function to compare in and out packets
        """
        # add traffic stream to input interface
        input.add_stream(pkts)

        # enable capture on all interfaces
        self.pg_enable_capture(self.pg_interfaces)

        # start traffic
        self.logger.info("Starting traffic")
        self.pg_start()

        # get output capture
        self.logger.info("Getting packet capture")
        capture = output.get_capture()

        # assert nothing was captured on input interface
        # input.assert_nothing_captured()

        # verify captured packets
        self.verify_captured_pkts(output, capture, compare_func)

    def create_packet_header_IPv6(self, saddr='1234::1', daddr='4321::1',
                                  sport=1234, dport=1234):
        """Create packet header: IPv6 header, UDP header

        :param dst: IPv6 destination address

        IPv6 source address is 1234::1
        IPv6 destination address is 4321::1
        UDP source port and destination port are 1234
        """

        p = IPv6(src=saddr, dst=daddr) / UDP(sport=sport, dport=dport)
        return p

    def create_packet_header_IPv6_SRH_IPv6(self, srcaddr, sidlist, segleft,
                                           insrc='1234::1', indst='4321::1',
                                           sport=1234, dport=1234):
        """Create packet header: IPv6 encapsulated in SRv6:
        IPv6 header with SRH, IPv6 header, UDP header

        :param int srcaddr: outer source address
        :param list sidlist: segment list of outer IPv6 SRH
        :param int segleft: segments-left field of outer IPv6 SRH

        Outer IPv6 source address is set to srcaddr
        Outer IPv6 destination address is set to sidlist[segleft]
        Inner IPv6 source addresses is 1234::1
        Inner IPv6 destination address is 4321::1
        UDP source port and destination port are 1234
        """

        p = IPv6(src=srcaddr, dst=sidlist[segleft]) / \
            IPv6ExtHdrSegmentRouting(addresses=sidlist,
                                     segleft=segleft, nh=41) / \
            IPv6(src=insrc, dst=indst) / \
            UDP(sport=sport, dport=dport)
        return p

    def create_packet_header_IPv4(self):
        """Create packet header: IPv4 header, UDP header

        :param dst: IPv4 destination address

        IPv4 source address is 123.1.1.1
        IPv4 destination address is 124.1.1.1
        UDP source port and destination port are 1234
        """

        p = IP(src='123.1.1.1', dst='124.1.1.1') / UDP(sport=1234, dport=1234)
        return p

    def create_packet_header_IPv6_SRH_IPv4(self, srcaddr, sidlist, segleft):
        """Create packet header: IPv4 encapsulated in SRv6:
        IPv6 header with SRH, IPv4 header, UDP header

        :param int srcaddr: outer source address
        :param list sidlist: segment list of outer IPv6 SRH
        :param int segleft: segments-left field of outer IPv6 SRH

        Outer IPv6 source address is set to srcaddr
        Outer IPv6 destination address is set to sidlist[segleft]
        Inner IPv4 source address is 123.1.1.1
        Inner IPv4 destination address is 124.1.1.1
        UDP source port and destination port are 1234
        """

        p = IPv6(src=srcaddr, dst=sidlist[segleft]) / \
            IPv6ExtHdrSegmentRouting(addresses=sidlist,
                                     segleft=segleft, nh=4) / \
            IP(src='123.1.1.1', dst='124.1.1.1') / \
            UDP(sport=1234, dport=1234)
        return p

    def get_payload_info(self, packet):
        """ Extract the payload_info from the packet
        """
        # in most cases, payload_info is in packet[Raw]
        # but packet[Raw] gives the complete payload
        # (incl L2 header) for the T.Encaps L2 case
        try:
            payload_info = self.payload_to_info(packet[Raw])

        except:
            # remote L2 header from packet[Raw]:
            # take packet[Raw], convert it to an Ether layer
            # and then extract Raw from it
            payload_info = self.payload_to_info(
                Ether(scapy.compat.raw(packet[Raw]))[Raw])

        return payload_info

    def verify_captured_pkts(self, dst_if, capture, compare_func):
        """
        Verify captured packet stream for specified interface.
        Compare ingress with egress packets using the specified compare fn

        :param dst_if: egress interface of DUT
        :param capture: captured packets
        :param compare_func: function to compare in and out packet
        """
        self.logger.info("Verifying capture on interface %s using function %s"
                         % (dst_if.name, compare_func.__name__))

        last_info = dict()
        for i in self.pg_interfaces:
            last_info[i.sw_if_index] = None
        dst_sw_if_index = dst_if.sw_if_index

        for packet in capture:
            try:
                # extract payload_info from packet's payload
                payload_info = self.get_payload_info(packet)
                packet_index = payload_info.index

                self.logger.debug("Verifying packet with index %d"
                                  % (packet_index))
                # packet should have arrived on the expected interface
                self.assertEqual(payload_info.dst, dst_sw_if_index)
                self.logger.debug(
                    "Got packet on interface %s: src=%u (idx=%u)" %
                    (dst_if.name, payload_info.src, packet_index))

                # search for payload_info with same src and dst if_index
                # this will give us the transmitted packet
                next_info = self.get_next_packet_info_for_interface2(
                    payload_info.src, dst_sw_if_index,
                    last_info[payload_info.src])
                last_info[payload_info.src] = next_info
                # next_info should not be None
                self.assertTrue(next_info is not None)
                # index of tx and rx packets should be equal
                self.assertEqual(packet_index, next_info.index)
                # data field of next_info contains the tx packet
                txed_packet = next_info.data

                self.logger.debug(ppp("Transmitted packet:",
                                      txed_packet))  # ppp=Pretty Print Packet

                self.logger.debug(ppp("Received packet:", packet))

                # compare rcvd packet with expected packet using compare_func
                compare_func(txed_packet, packet)

            except:
                self.logger.error(ppp("Unexpected or invalid packet:", packet))
                raise

        # have all expected packets arrived?
        for i in self.pg_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))


if __name__ == '__main__':
    unittest.main(testRunner=VppTestRunner)