summaryrefslogtreecommitdiffstats
path: root/src/vnet/ip/icmp4.h
blob: ae805148c897bf8d28ee1fd0728fd7d7fc45b628 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
/*
 * Copyright (c) 2015 Cisco and/or its affiliates.
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at:
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
#ifndef included_vnet_icmp4_h
#define included_vnet_icmp4_h

#define foreach_icmp4_error                                             \
  _ (NONE, "valid packets")                                             \
  _ (UNKNOWN_TYPE, "unknown type")                                      \
  _ (INVALID_CODE_FOR_TYPE, "invalid code for type")                    \
  _ (INVALID_HOP_LIMIT_FOR_TYPE, "hop_limit != 255")                    \
  _ (LENGTH_TOO_SMALL_FOR_TYPE, "payload length too small for type")    \
  _ (OPTIONS_WITH_ODD_LENGTH,                                           \
     "total option length not multiple of 8 bytes")                     \
  _ (OPTION_WITH_ZERO_LENGTH, "option has zero length")                 \
  _ (ECHO_REPLIES_SENT, "echo replies sent")                            \
  _ (DST_LOOKUP_MISS, "icmp6 dst address lookup misses")                \
  _ (DEST_UNREACH_SENT, "destination unreachable response sent")	\
  _ (TTL_EXPIRE_SENT, "hop limit exceeded response sent")		\
  _ (PARAM_PROBLEM_SENT, "parameter problem response sent")		\
  _ (DROP, "error message dropped")

typedef enum
{
#define _(f,s) ICMP4_ERROR_##f,
  foreach_icmp4_error
#undef _
} icmp4_error_t;

typedef struct
{
  u8 packet_data[64];
} icmp_input_trace_t;

format_function_t format_icmp4_input_trace;
void ip4_icmp_register_type (vlib_main_t * vm, icmp4_type_t type,
			     u32 node_index);
void icmp4_error_set_vnet_buffer (vlib_buffer_t * b, u8 type, u8 code,
				  u32 data);

#endif /* included_vnet_icmp4_h */

/*
 * fd.io coding-style-patch-verification: ON
 *
 * Local Variables:
 * eval: (c-set-style "gnu")
 * End:
 */
eral */ .highlight .n { color: #f8f8f2 } /* Name */ .highlight .o { color: #f92672 } /* Operator */ .highlight .p { color: #f8f8f2 } /* Punctuation */ .highlight .ch { color: #75715e } /* Comment.Hashbang */ .highlight .cm { color: #75715e } /* Comment.Multiline */ .highlight .cp { color: #75715e } /* Comment.Preproc */ .highlight .cpf { color: #75715e } /* Comment.PreprocFile */ .highlight .c1 { color: #75715e } /* Comment.Single */ .highlight .cs { color: #75715e } /* Comment.Special */ .highlight .gd { color: #f92672 } /* Generic.Deleted */ .highlight .ge { font-style: italic } /* Generic.Emph */ .highlight .gi { color: #a6e22e } /* Generic.Inserted */ .highlight .gs { font-weight: bold } /* Generic.Strong */ .highlight .gu { color: #75715e } /* Generic.Subheading */ .highlight .kc { color: #66d9ef } /* Keyword.Constant */ .highlight .kd { color: #66d9ef } /* Keyword.Declaration */ .highlight .kn { color: #f92672 } /* Keyword.Namespace */ .highlight .kp { color: #66d9ef } /* Keyword.Pseudo */ .highlight .kr { color: #66d9ef } /* Keyword.Reserved */ .highlight .kt { color: #66d9ef } /* Keyword.Type */ .highlight .ld { color: #e6db74 } /* Literal.Date */ .highlight .m { color: #ae81ff } /* Literal.Number */ .highlight .s { color: #e6db74 } /* Literal.String */ .highlight .na { color: #a6e22e } /* Name.Attribute */ .highlight .nb { color: #f8f8f2 } /* Name.Builtin */ .highlight .nc { color: #a6e22e } /* Name.Class */ .highlight .no { color: #66d9ef } /* Name.Constant */ .highlight .nd { color: #a6e22e } /* Name.Decorator */ .highlight .ni { color: #f8f8f2 } /* Name.Entity */ .highlight .ne { color: #a6e22e } /* Name.Exception */ .highlight .nf { color: #a6e22e } /* Name.Function */ .highlight .nl { color: #f8f8f2 } /* Name.Label */ .highlight .nn { color: #f8f8f2 } /* Name.Namespace */ .highlight .nx { color: #a6e22e } /* Name.Other */ .highlight .py { color: #f8f8f2 } /* Name.Property */ .highlight .nt { color: #f92672 } /* Name.Tag */ .highlight .nv { color: #f8f8f2 } /* Name.Variable */ .highlight .ow { color: #f92672 } /* Operator.Word */ .highlight .w { color: #f8f8f2 } /* Text.Whitespace */ .highlight .mb { color: #ae81ff } /* Literal.Number.Bin */ .highlight .mf { color: #ae81ff } /* Literal.Number.Float */ .highlight .mh { color: #ae81ff } /* Literal.Number.Hex */ .highlight .mi { color: #ae81ff } /* Literal.Number.Integer */ .highlight .mo { color: #ae81ff } /* Literal.Number.Oct */ .highlight .sa { color: #e6db74 } /* Literal.String.Affix */ .highlight .sb { color: #e6db74 } /* Literal.String.Backtick */ .highlight .sc { color: #e6db74 } /* Literal.String.Char */ .highlight .dl { color: #e6db74 } /* Literal.String.Delimiter */ .highlight .sd { color: #e6db74 } /* Literal.String.Doc */ .highlight .s2 { color: #e6db74 } /* Literal.String.Double */ .highlight .se { color: #ae81ff } /* Literal.String.Escape */ .highlight .sh { color: #e6db74 } /* Literal.String.Heredoc */ .highlight .si { color: #e6db74 } /* Literal.String.Interpol */ .highlight .sx { color: #e6db74 } /* Literal.String.Other */ .highlight .sr { color: #e6db74 } /* Literal.String.Regex */ .highlight .s1 { color: #e6db74 } /* Literal.String.Single */ .highlight .ss { color: #e6db74 } /* Literal.String.Symbol */ .highlight .bp { color: #f8f8f2 } /* Name.Builtin.Pseudo */ .highlight .fm { color: #a6e22e } /* Name.Function.Magic */ .highlight .vc { color: #f8f8f2 } /* Name.Variable.Class */ .highlight .vg { color: #f8f8f2 } /* Name.Variable.Global */ .highlight .vi { color: #f8f8f2 } /* Name.Variable.Instance */ .highlight .vm { color: #f8f8f2 } /* Name.Variable.Magic */ .highlight .il { color: #ae81ff } /* Literal.Number.Integer.Long */ } @media (prefers-color-scheme: light) { .highlight .hll { background-color: #ffffcc } .highlight .c { color: #888888 } /* Comment */ .highlight .err { color: #a61717; background-color: #e3d2d2 } /* Error */ .highlight .k { color: #008800; font-weight: bold } /* Keyword */ .highlight .ch { color: #888888 } /* Comment.Hashbang */ .highlight .cm { color: #888888 } /* Comment.Multiline */ .highlight .cp { color: #cc0000; font-weight: bold } /* Comment.Preproc */ .highlight .cpf { color: #888888 } /* Comment.PreprocFile */ .highlight .c1 { color: #888888 } /* Comment.Single */ .highlight .cs { color: #cc0000; font-weight: bold; background-color: #fff0f0 } /* Comment.Special */ .highlight .gd { color: #000000; background-color: #ffdddd } /* Generic.Deleted */ .highlight .ge { font-style: italic } /* Generic.Emph */ .highlight .gr { color: #aa0000 } /* Generic.Error */ .highlight .gh { color: #333333 } /* Generic.Heading */ .highlight .gi { color: #000000; background-color: #ddffdd } /* Generic.Inserted */ .highlight .go { color: #888888 } /* Generic.Output */ .highlight .gp { color: #555555 } /* Generic.Prompt */ .highlight .gs { font-weight: bold } /* Generic.Strong */ .highlight .gu { color: #666666 } /* Generic.Subheading */ .highlight .gt { color: #aa0000 } /* Generic.Traceback */ .highlight .kc { color: #008800; font-weight: bold } /* Keyword.Constant */ .highlight .kd { color: #008800; font-weight: bold } /* Keyword.Declaration */ .highlight .kn { color: #008800; font-weight: bold } /* Keyword.Namespace */ .highlight .kp { color: #008800 } /* Keyword.Pseudo */ .highlight .kr { color: #008800; font-weight: bold } /* Keyword.Reserved */ .highlight .kt { color: #888888; font-weight: bold } /* Keyword.Type */ .highlight .m { color: #0000DD; font-weight: bold } /* Literal.Number */ .highlight .s { color: #dd2200; background-color: #fff0f0 } /* Literal.String */ .highlight .na { color: #336699 } /* Name.Attribute */ .highlight .nb { color: #003388 } /* Name.Builtin */ .highlight .nc { color: #bb0066; font-weight: bold } /* Name.Class */ .highlight .no { color: #003366; font-weight: bold } /* Name.Constant */ .highlight .nd { color: #555555 } /* Name.Decorator */ .highlight .ne { color: #bb0066; font-weight: bold } /* Name.Exception */ .highlight .nf { color: #0066bb; font-weight: bold } /* Name.Function */ .highlight .nl { color: #336699; font-style: italic } /* Name.Label */ .highlight .nn { color: #bb0066; font-weight: bold } /* Name.Namespace */ .highlight .py { color: #336699; font-weight: bold } /* Name.Property */ .highlight .nt { color: #bb0066; font-weight: bold } /* Name.Tag */ .highlight .nv { color: #336699 } /* Name.Variable */ .highlight .ow { color: #008800 } /* Operator.Word */ .highlight .w { color: #bbbbbb } /* Text.Whitespace */ .highlight .mb { color: #0000DD; font-weight: bold } /* Literal.Number.Bin */ .highlight .mf { color: #0000DD; font-weight: bold } /* Literal.Number.Float */ .highlight .mh { color: #0000DD; font-weight: bold } /* Literal.Number.Hex */ .highlight .mi { color: #0000DD; font-weight: bold } /* Literal.Number.Integer */ .highlight .mo { color: #0000DD; font-weight: bold } /* Literal.Number.Oct */ .highlight .sa { color: #dd2200; background-color: #fff0f0 } /* Literal.String.Affix */ .highlight .sb { color: #dd2200; background-color: #fff0f0 } /* Literal.String.Backtick */ .highlight .sc { color: #dd2200; background-color: #fff0f0 } /* Literal.String.Char */ .highlight .dl { color: #dd2200; background-color: #fff0f0 } /* Literal.String.Delimiter */ .highlight .sd { color: #dd2200; background-color: #fff0f0 } /* Literal.String.Doc */ .highlight .s2 { color: #dd2200; background-color: #fff0f0 } /* Literal.String.Double */ .highlight .se { color: #0044dd; background-color: #fff0f0 } /* Literal.String.Escape */ .highlight .sh { color: #dd2200; background-color: #fff0f0 } /* Literal.String.Heredoc */ .highlight .si { color: #3333bb; background-color: #fff0f0 } /* Literal.String.Interpol */ .highlight .sx { color: #22bb22; background-color: #f0fff0 } /* Literal.String.Other */ .highlight .sr { color: #008800; background-color: #fff0ff } /* Literal.String.Regex */ .highlight .s1 { color: #dd2200; background-color: #fff0f0 } /* Literal.String.Single */ .highlight .ss { color: #aa6600; background-color: #fff0f0 } /* Literal.String.Symbol */ .highlight .bp { color: #003388 } /* Name.Builtin.Pseudo */ .highlight .fm { color: #0066bb; font-weight: bold } /* Name.Function.Magic */ .highlight .vc { color: #336699 } /* Name.Variable.Class */ .highlight .vg { color: #dd7700 } /* Name.Variable.Global */ .highlight .vi { color: #3333bb } /* Name.Variable.Instance */ .highlight .vm { color: #336699 } /* Name.Variable.Magic */ .highlight .il { color: #0000DD; font-weight: bold } /* Literal.Number.Integer.Long */ }
import os
import time
import socket
import struct
from traceback import format_exc, format_stack
from scapy.utils import wrpcap, rdpcap, PcapReader
from scapy.plist import PacketList
from vpp_interface import VppInterface

from scapy.layers.l2 import Ether, ARP
from scapy.layers.inet6 import IPv6, ICMPv6ND_NS, ICMPv6ND_NA,\
    ICMPv6NDOptSrcLLAddr, ICMPv6NDOptDstLLAddr, ICMPv6ND_RA, RouterAlert, \
    IPv6ExtHdrHopByHop
from util import ppp, ppc
from scapy.utils6 import in6_getnsma, in6_getnsmac, in6_ismaddr
from scapy.utils import inet_pton, inet_ntop


class CaptureTimeoutError(Exception):
    """ Exception raised if capture or packet doesn't appear within timeout """
    pass


def is_ipv6_misc(p):
    """ Is packet one of uninteresting IPv6 broadcasts? """
    if p.haslayer(ICMPv6ND_RA):
        if in6_ismaddr(p[IPv6].dst):
            return True
    if p.haslayer(IPv6ExtHdrHopByHop):
        for o in p[IPv6ExtHdrHopByHop].options:
            if isinstance(o, RouterAlert):
                return True
    return False


class VppPGInterface(VppInterface):
    """
    VPP packet-generator interface
    """

    @property
    def pg_index(self):
        """packet-generator interface index assigned by VPP"""
        return self._pg_index

    @property
    def out_path(self):
        """pcap file path - captured packets"""
        return self._out_path

    @property
    def in_path(self):
        """ pcap file path - injected packets"""
        return self._in_path

    @property
    def capture_cli(self):
        """CLI string to start capture on this interface"""
        return self._capture_cli

    @property
    def cap_name(self):
        """capture name for this interface"""
        return self._cap_name

    @property
    def input_cli(self):
        """CLI string to load the injected packets"""
        return self._input_cli

    @property
    def in_history_counter(self):
        """Self-incrementing counter used when renaming old pcap files"""
        v = self._in_history_counter
        self._in_history_counter += 1
        return v

    @property
    def out_history_counter(self):
        """Self-incrementing counter used when renaming old pcap files"""
        v = self._out_history_counter
        self._out_history_counter += 1
        return v

    def __init__(self, test, pg_index):
        """ Create VPP packet-generator interface """
        super(VppPGInterface, self).__init__(test)

        r = test.vapi.pg_create_interface(pg_index)
        self.set_sw_if_index(r.sw_if_index)

        self._in_history_counter = 0
        self._out_history_counter = 0
        self._out_assert_counter = 0
        self._pg_index = pg_index
        self._out_file = "pg%u_out.pcap" % self.pg_index
        self._out_path = self.test.tempdir + "/" + self._out_file
        self._in_file = "pg%u_in.pcap" % self.pg_index
        self._in_path = self.test.tempdir + "/" + self._in_file
        self._capture_cli = "packet-generator capture pg%u pcap %s" % (
            self.pg_index, self.out_path)
        self._cap_name = "pcap%u" % self.sw_if_index
        self._input_cli = \
            "packet-generator new pcap %s source pg%u name %s" % (
                self.in_path, self.pg_index, self.cap_name)

    def enable_capture(self):
        """ Enable capture on this packet-generator interface"""
        try:
            if os.path.isfile(self.out_path):
                name = "%s/history.[timestamp:%f].[%s-counter:%04d].%s" % \
                    (self.test.tempdir,
                     time.time(),
                     self.name,
                     self.out_history_counter,
                     self._out_file)
                self.test.logger.debug("Renaming %s->%s" %
                                       (self.out_path, name))
                os.rename(self.out_path, name)
        except:
            pass
        # FIXME this should be an API, but no such exists atm
        self.test.vapi.cli(self.capture_cli)
        self._pcap_reader = None

    def add_stream(self, pkts):
        """
        Add a stream of packets to this packet-generator

        :param pkts: iterable packets

        """
        try:
            if os.path.isfile(self.in_path):
                name = "%s/history.[timestamp:%f].[%s-counter:%04d].%s" %\
                    (self.test.tempdir,
                     time.time(),
                     self.name,
                     self.in_history_counter,
                     self._in_file)
                self.test.logger.debug("Renaming %s->%s" %
                                       (self.in_path, name))
                os.rename(self.in_path, name)
        except:
            pass
        wrpcap(self.in_path, pkts)
        self.test.register_capture(self.cap_name)
        # FIXME this should be an API, but no such exists atm
        self.test.vapi.cli(self.input_cli)

    def generate_debug_aid(self, kind):
        """ Create a hardlink to the out file with a counter and a file
        containing stack trace to ease debugging in case of multiple capture
        files present. """
        self.test.logger.debug("Generating debug aid for %s on %s" %
                               (kind, self._name))
        link_path, stack_path = ["%s/debug_%s_%s_%s.%s" %
                                 (self.test.tempdir, self._name,
                                  self._out_assert_counter, kind, suffix)
                                 for suffix in ["pcap", "stack"]
                                 ]
        os.link(self.out_path, link_path)
        with open(stack_path, "w") as f:
            f.writelines(format_stack())
        self._out_assert_counter += 1

    def _get_capture(self, timeout, filter_out_fn=is_ipv6_misc):
        """ Helper method to get capture and filter it """
        try:
            if not self.wait_for_capture_file(timeout):
                return None
            output = rdpcap(self.out_path)
            self.test.logger.debug("Capture has %s packets" % len(output.res))
        except:
            self.test.logger.debug("Exception in scapy.rdpcap (%s): %s" %
                                   (self.out_path, format_exc()))
            return None
        before = len(output.res)
        if filter_out_fn:
            output.res = [p for p in output.res if not filter_out_fn(p)]
        removed = before - len(output.res)
        if removed:
            self.test.logger.debug(
                "Filtered out %s packets from capture (returning %s)" %
                (removed, len(output.res)))
        return output

    def get_capture(self, expected_count=None, remark=None, timeout=1,
                    filter_out_fn=is_ipv6_misc):
        """ Get captured packets

        :param expected_count: expected number of packets to capture, if None,
                               then self.test.packet_count_for_dst_pg_idx is
                               used to lookup the expected count
        :param remark: remark printed into debug logs
        :param timeout: how long to wait for packets
        :param filter_out_fn: filter applied to each packet, packets for which
                              the filter returns True are removed from capture
        :returns: iterable packets
        """
        remaining_time = timeout
        capture = None
        name = self.name if remark is None else "%s (%s)" % (self.name, remark)
        based_on = "based on provided argument"
        if expected_count is None:
            expected_count = \
                self.test.get_packet_count_for_if_idx(self.sw_if_index)
            based_on = "based on stored packet_infos"
            if expected_count == 0:
                raise Exception(
                    "Internal error, expected packet count for %s is 0!" %
                    name)
        self.test.logger.debug("Expecting to capture %s (%s) packets on %s" % (
            expected_count, based_on, name))
        while remaining_time > 0:
            before = time.time()
            capture = self._get_capture(remaining_time, filter_out_fn)
            elapsed_time = time.time() - before
            if capture:
                if len(capture.res) == expected_count:
                    # bingo, got the packets we expected
                    return capture
                elif len(capture.res) > expected_count:
                    self.test.logger.error(
                        ppc("Unexpected packets captured:", capture))
                    break
                else:
                    self.test.logger.debug("Partial capture containing %s "
                                           "packets doesn't match expected "
                                           "count %s (yet?)" %
                                           (len(capture.res), expected_count))
            elif expected_count == 0:
                # bingo, got None as we expected - return empty capture
                return PacketList()
            remaining_time -= elapsed_time
        if capture:
            self.generate_debug_aid("count-mismatch")
            raise Exception("Captured packets mismatch, captured %s packets, "
                            "expected %s packets on %s" %
                            (len(capture.res), expected_count, name))
        else:
            raise Exception("No packets captured on %s" % name)

    def assert_nothing_captured(self, remark=None, filter_out_fn=is_ipv6_misc):
        """ Assert that nothing unfiltered was captured on interface

        :param remark: remark printed into debug logs
        :param filter_out_fn: filter applied to each packet, packets for which
                              the filter returns True are removed from capture
        """
        if os.path.isfile(self.out_path):
            try:
                capture = self.get_capture(
                    0, remark=remark, filter_out_fn=filter_out_fn)
                if not capture or len(capture.res) == 0:
                    # junk filtered out, we're good
                    return
            except:
                pass
            self.generate_debug_aid("empty-assert")
            if remark:
                raise AssertionError(
                    "Non-empty capture file present for interface %s (%s)" %
                    (self.name, remark))
            else:
                raise AssertionError("Capture file present for interface %s" %
                                     self.name)

    def wait_for_capture_file(self, timeout=1):
        """
        Wait until pcap capture file appears

        :param timeout: How long to wait for the packet (default 1s)

        :returns: True/False if the file is present or appears within timeout
        """
        deadline = time.time() + timeout
        if not os.path.isfile(self.out_path):
            self.test.logger.debug("Waiting for capture file %s to appear, "
                                   "timeout is %ss" % (self.out_path, timeout))
        else:
            self.test.logger.debug("Capture file %s already exists" %
                                   self.out_path)
            return True
        while time.time() < deadline:
            if os.path.isfile(self.out_path):
                break
            time.sleep(0)  # yield
        if os.path.isfile(self.out_path):
            self.test.logger.debug("Capture file appeared after %fs" %
                                   (time.time() - (deadline - timeout)))
        else:
            self.test.logger.debug("Timeout - capture file still nowhere")
            return False
        return True

    def verify_enough_packet_data_in_pcap(self):
        """
        Check if enough data is available in file handled by internal pcap
        reader so that a whole packet can be read.

        :returns: True if enough data present, else False
        """
        orig_pos = self._pcap_reader.f.tell()  # save file position
        enough_data = False
        # read packet header from pcap
        packet_header_size = 16
        caplen = None
        end_pos = None
        hdr = self._pcap_reader.f.read(packet_header_size)
        if len(hdr) == packet_header_size:
            # parse the capture length - caplen
            sec, usec, caplen, wirelen = struct.unpack(
                self._pcap_reader.endian + "IIII", hdr)
            self._pcap_reader.f.seek(0, 2)  # seek to end of file
            end_pos = self._pcap_reader.f.tell()  # get position at end
            if end_pos >= orig_pos + len(hdr) + caplen:
                enough_data = True  # yay, we have enough data
        self._pcap_reader.f.seek(orig_pos, 0)  # restore original position
        return enough_data

    def wait_for_packet(self, timeout, filter_out_fn=is_ipv6_misc):
        """
        Wait for next packet captured with a timeout

        :param timeout: How long to wait for the packet

        :returns: Captured packet if no packet arrived within timeout
        :raises Exception: if no packet arrives within timeout
        """
        deadline = time.time() + timeout
        if self._pcap_reader is None:
            if not self.wait_for_capture_file(timeout):
                raise CaptureTimeoutError("Capture file %s did not appear "
                                          "within timeout" % self.out_path)
            while time.time() < deadline:
                try:
                    self._pcap_reader = PcapReader(self.out_path)
                    break
                except:
                    self.test.logger.debug(
                        "Exception in scapy.PcapReader(%s): %s" %
                        (self.out_path, format_exc()))
        if not self._pcap_reader:
            raise CaptureTimeoutError("Capture file %s did not appear within "
                                      "timeout" % self.out_path)

        poll = False
        if timeout > 0:
            self.test.logger.debug("Waiting for packet")
        else:
            poll = True
            self.test.logger.debug("Polling for packet")
        while time.time() < deadline or poll:
            if not self.verify_enough_packet_data_in_pcap():
                time.sleep(0)  # yield
                poll = False
                continue
            p = self._pcap_reader.recv()
            if p is not None:
                if filter_out_fn is not None and filter_out_fn(p):
                    self.test.logger.debug(
                        "Packet received after %ss was filtered out" %
                        (time.time() - (deadline - timeout)))
                else:
                    self.test.logger.debug(
                        "Packet received after %fs" %
                        (time.time() - (deadline - timeout)))
                    return p
            time.sleep(0)  # yield
            poll = False
        self.test.logger.debug("Timeout - no packets received")
        raise CaptureTimeoutError("Packet didn't arrive within timeout")

    def create_arp_req(self):
        """Create ARP request applicable for this interface"""
        return (Ether(dst="ff:ff:ff:ff:ff:ff", src=self.remote_mac) /
                ARP(op=ARP.who_has, pdst=self.local_ip4,
                    psrc=self.remote_ip4, hwsrc=self.remote_mac))

    def create_ndp_req(self):
        """Create NDP - NS applicable for this interface"""
        nsma = in6_getnsma(inet_pton(socket.AF_INET6, self.local_ip6))
        d = inet_ntop(socket.AF_INET6, nsma)

        return (Ether(dst=in6_getnsmac(nsma)) /
                IPv6(dst=d, src=self.remote_ip6) /
                ICMPv6ND_NS(tgt=self.local_ip6) /
                ICMPv6NDOptSrcLLAddr(lladdr=self.remote_mac))

    def resolve_arp(self, pg_interface=None):
        """Resolve ARP using provided packet-generator interface

        :param pg_interface: interface used to resolve, if None then this
            interface is used

        """
        if pg_interface is None:
            pg_interface = self
        self.test.logger.info("Sending ARP request for %s on port %s" %
                              (self.local_ip4, pg_interface.name))
        arp_req = self.create_arp_req()
        pg_interface.add_stream(arp_req)
        pg_interface.enable_capture()
        self.test.pg_start()
        self.test.logger.info(self.test.vapi.cli("show trace"))
        try:
            captured_packet = pg_interface.wait_for_packet(1)
        except:
            self.test.logger.info("No ARP received on port %s" %
                                  pg_interface.name)
            return
        arp_reply = captured_packet.copy()  # keep original for exception
        # Make Dot1AD packet content recognizable to scapy
        if arp_reply.type == 0x88a8:
            arp_reply.type = 0x8100
            arp_reply = Ether(str(arp_reply))
        try:
            if arp_reply[ARP].op == ARP.is_at:
                self.test.logger.info("VPP %s MAC address is %s " %
                                      (self.name, arp_reply[ARP].hwsrc))
                self._local_mac = arp_reply[ARP].hwsrc
            else:
                self.test.logger.info("No ARP received on port %s" %
                                      pg_interface.name)
        except:
            self.test.logger.error(
                ppp("Unexpected response to ARP request:", captured_packet))
            raise

    def resolve_ndp(self, pg_interface=None, timeout=1):
        """Resolve NDP using provided packet-generator interface

        :param pg_interface: interface used to resolve, if None then this
            interface is used
        :param timeout: how long to wait for response before giving up

        """
        if pg_interface is None:
            pg_interface = self
        self.test.logger.info("Sending NDP request for %s on port %s" %
                              (self.local_ip6, pg_interface.name))
        ndp_req = self.create_ndp_req()
        pg_interface.add_stream(ndp_req)
        pg_interface.enable_capture()
        self.test.pg_start()
        now = time.time()
        deadline = now + timeout
        # Enabling IPv6 on an interface can generate more than the
        # ND reply we are looking for (namely MLD). So loop through
        # the replies to look for want we want.
        while now < deadline:
            try:
                captured_packet = pg_interface.wait_for_packet(
                    deadline - now, filter_out_fn=None)
            except:
                self.test.logger.error(
                    "Timeout while waiting for NDP response")
                raise
            ndp_reply = captured_packet.copy()  # keep original for exception
            # Make Dot1AD packet content recognizable to scapy
            if ndp_reply.type == 0x88a8:
                ndp_reply.type = 0x8100
                ndp_reply = Ether(str(ndp_reply))
            try:
                ndp_na = ndp_reply[ICMPv6ND_NA]
                opt = ndp_na[ICMPv6NDOptDstLLAddr]
                self.test.logger.info("VPP %s MAC address is %s " %
                                      (self.name, opt.lladdr))
                self._local_mac = opt.lladdr
                self.test.logger.debug(self.test.vapi.cli("show trace"))
                # we now have the MAC we've been after
                return
            except:
                self.test.logger.info(
                    ppp("Unexpected response to NDP request:",
                        captured_packet))
            now = time.time()

        self.test.logger.debug(self.test.vapi.cli("show trace"))
        raise Exception("Timeout while waiting for NDP response")