aboutsummaryrefslogtreecommitdiffstats
path: root/test/vpp_pg_interface.py
diff options
context:
space:
mode:
Diffstat (limited to 'test/vpp_pg_interface.py')
-rw-r--r--[-rwxr-xr-x]test/vpp_pg_interface.py342
1 files changed, 207 insertions, 135 deletions
diff --git a/test/vpp_pg_interface.py b/test/vpp_pg_interface.py
index 567b81de7c0..cdb91ed1e41 100755..100644
--- a/test/vpp_pg_interface.py
+++ b/test/vpp_pg_interface.py
@@ -1,31 +1,42 @@
import os
+import shutil
import socket
from socket import inet_pton, inet_ntop
import struct
import time
from traceback import format_exc, format_stack
+from sh import tshark
+from pathlib import Path
-import scapy.compat
+from config import config
from scapy.utils import wrpcap, rdpcap, PcapReader
from scapy.plist import PacketList
from vpp_interface import VppInterface
from vpp_papi import VppEnum
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.layers.inet6 import (
+ IPv6,
+ ICMPv6ND_NS,
+ ICMPv6ND_NA,
+ ICMPv6NDOptSrcLLAddr,
+ ICMPv6NDOptDstLLAddr,
+ ICMPv6ND_RA,
+ RouterAlert,
+ IPv6ExtHdrHopByHop,
+)
+from util import ppp, ppc, UnexpectedPacketError
from scapy.utils6 import in6_getnsma, in6_getnsmac, in6_ismaddr
class CaptureTimeoutError(Exception):
- """ Exception raised if capture or packet doesn't appear within timeout """
+ """Exception raised if capture or packet doesn't appear within timeout"""
+
pass
def is_ipv6_misc(p):
- """ Is packet one of uninteresting IPv6 broadcasts? """
+ """Is packet one of uninteresting IPv6 broadcasts?"""
if p.haslayer(ICMPv6ND_RA):
if in6_ismaddr(p[IPv6].dst):
return True
@@ -71,10 +82,9 @@ class VppPGInterface(VppInterface):
return self._out_path
def get_in_path(self, worker):
- """ pcap file path - injected packets"""
+ """pcap file path - injected packets"""
if worker is not None:
- return "%s/pg%u_wrk%u_in.pcap" % (self.test.tempdir, self.pg_index,
- worker)
+ return "%s/pg%u_wrk%u_in.pcap" % (self.test.tempdir, self.pg_index, worker)
return "%s/pg%u_in.pcap" % (self.test.tempdir, self.pg_index)
@property
@@ -91,7 +101,10 @@ class VppPGInterface(VppInterface):
def get_input_cli(self, nb_replays=None, worker=None):
"""return CLI string to load the injected packets"""
input_cli = "packet-generator new pcap %s source pg%u name %s" % (
- self.get_in_path(worker), self.pg_index, self.get_cap_name(worker))
+ self.get_in_path(worker),
+ self.pg_index,
+ self.get_cap_name(worker),
+ )
if nb_replays is not None:
return "%s limit %d" % (input_cli, nb_replays)
if worker is not None:
@@ -113,7 +126,7 @@ class VppPGInterface(VppInterface):
return v
def __init__(self, test, pg_index, gso, gso_size, mode):
- """ Create VPP packet-generator interface """
+ """Create VPP packet-generator interface"""
super().__init__(test)
r = test.vapi.pg_create_interface_v2(pg_index, gso, gso_size, mode)
@@ -129,35 +142,67 @@ class VppPGInterface(VppInterface):
self._out_file = "pg%u_out.pcap" % self.pg_index
self._out_path = self.test.tempdir + "/" + self._out_file
self._capture_cli = "packet-generator capture pg%u pcap %s" % (
- self.pg_index, self.out_path)
- self._cap_name = "pcap%u-sw_if_index-%s" % (
- self.pg_index, self.sw_if_index)
+ self.pg_index,
+ self.out_path,
+ )
+ self._cap_name = "pcap%u-sw_if_index-%s" % (self.pg_index, self.sw_if_index)
- def rename_old_pcap_file(self, path, counter):
+ def link_pcap_file(self, path, direction, counter):
+ if not config.keep_pcaps:
+ return
filename = os.path.basename(path)
+ test_name = (
+ self.test_name
+ if hasattr(self, "test_name")
+ else f"suite{self.test.__name__}"
+ )
+ name = f"{self.test.tempdir}/{test_name}.[timestamp:{time.time():.8f}].{self.name}-{direction}-{counter:04}.{filename}"
+ if os.path.isfile(name):
+ self.test.logger.debug(
+ f"Skipping hard link creation: {name} already exists!"
+ )
+ return
try:
if os.path.isfile(path):
- name = "%s/history.[timestamp:%f].[%s-counter:%04d].%s" % \
- (self.test.tempdir,
- time.time(),
- self.name,
- counter,
- filename)
- self.test.logger.debug("Renaming %s->%s" %
- (path, name))
- os.rename(path, name)
+ self.test.logger.debug(f"Creating hard link {path}->{name}")
+ os.link(path, name)
except OSError:
- self.test.logger.debug("OSError: Could not rename %s %s" %
- (path, filename))
+ self.test.logger.debug(
+ f"OSError: Could not create hard link {path}->{name}"
+ )
+
+ def remove_old_pcap_file(self, path):
+ self.wait_for_pg_stop()
+ self.test.unlink_testcase_file(self.test, Path(path))
+ return
+
+ def decode_pcap_files(self, pcap_dir, filename_prefix):
+ # Generate tshark packet trace of testcase pcap files
+ pg_decode = f"{pcap_dir}/pcap-decode-{filename_prefix}.txt"
+ if os.path.isfile(pg_decode):
+ self.test.logger.debug(
+ f"The pg streams decode file already exists: {pg_decode}"
+ )
+ return
+ self.test.logger.debug(
+ f"Generating testcase pg streams decode file: {pg_decode}"
+ )
+ ts_opts = "-Vr"
+ for p in sorted(Path(pcap_dir).glob(f"{filename_prefix}*.pcap")):
+ self.test.logger.debug(f"Decoding {p}")
+ with open(f"{pg_decode}", "a", buffering=1) as f:
+ print(f"tshark {ts_opts} {p}", file=f)
+ tshark(ts_opts, f"{p}", _out=f)
+ print("", file=f)
def enable_capture(self):
- """ Enable capture on this packet-generator interface
- of at most n packets.
- If n < 0, this is no limit
+ """Enable capture on this packet-generator interface
+ of at most n packets.
+ If n < 0, this is no limit
"""
# disable the capture to flush the capture
self.disable_capture()
- self.rename_old_pcap_file(self.out_path, self.out_history_counter)
+ self.remove_old_pcap_file(self.out_path)
# FIXME this should be an API, but no such exists atm
self.test.vapi.cli(self.capture_cli)
self._pcap_reader = None
@@ -166,16 +211,14 @@ class VppPGInterface(VppInterface):
self.test.vapi.cli("%s disable" % self.capture_cli)
def coalesce_enable(self):
- """ Enable packet coalesce on this packet-generator interface"""
+ """Enable packet coalesce on this packet-generator interface"""
self._coalesce_enabled = 1
- self.test.vapi.pg_interface_enable_disable_coalesce(self.sw_if_index,
- 1)
+ self.test.vapi.pg_interface_enable_disable_coalesce(self.sw_if_index, 1)
def coalesce_disable(self):
- """ Disable packet coalesce on this packet-generator interface"""
+ """Disable packet coalesce on this packet-generator interface"""
self._coalesce_enabled = 0
- self.test.vapi.pg_interface_enable_disable_coalesce(self.sw_if_index,
- 0)
+ self.test.vapi.pg_interface_enable_disable_coalesce(self.sw_if_index, 0)
def add_stream(self, pkts, nb_replays=None, worker=None):
"""
@@ -184,37 +227,41 @@ class VppPGInterface(VppInterface):
:param pkts: iterable packets
"""
- wrpcap(self.get_in_path(worker), pkts)
+ in_pcap = self.get_in_path(worker)
+ if os.path.isfile(in_pcap):
+ self.remove_old_pcap_file(in_pcap)
+ wrpcap(in_pcap, pkts)
self.test.register_pcap(self, worker)
# FIXME this should be an API, but no such exists atm
self.test.vapi.cli(self.get_input_cli(nb_replays, worker))
+ self.link_pcap_file(self.get_in_path(worker), "inp", self.in_history_counter)
def generate_debug_aid(self, kind):
- """ Create a hardlink to the out file with a counter and a file
+ """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"]
- ]
+ 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 """
+ """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))
+ self.test.logger.debug(f"Capture has {len(output.res)} packets")
except:
- self.test.logger.debug("Exception in scapy.rdpcap (%s): %s" %
- (self.out_path, format_exc()))
+ 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:
@@ -222,13 +269,15 @@ class VppPGInterface(VppInterface):
removed = before - len(output.res)
if removed:
self.test.logger.debug(
- "Filtered out %s packets from capture (returning %s)" %
- (removed, len(output.res)))
+ "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
+ 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
@@ -244,15 +293,16 @@ class VppPGInterface(VppInterface):
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)
+ 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))
+ "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)
@@ -263,49 +313,52 @@ class VppPGInterface(VppInterface):
return capture
elif len(capture.res) > expected_count:
self.test.logger.error(
- ppc("Unexpected packets captured:", capture))
+ ppc(
+ f"Unexpected packets captured, got {len(capture.res)}, expected {expected_count}:",
+ capture,
+ )
+ )
break
else:
- self.test.logger.debug("Partial capture containing %s "
- "packets doesn't match expected "
- "count %s (yet?)" %
- (len(capture.res), expected_count))
+ 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))
+ if len(capture) > 0 and 0 == expected_count:
+ rem = f"\n{remark}" if remark else ""
+ raise UnexpectedPacketError(
+ capture[0],
+ f"\n({len(capture)} packets captured in total){rem} on {name}",
+ )
+ msg = f"Captured packets mismatch, captured {len(capture.res)} packets, expected {expected_count} packets on {name}:"
+ raise Exception(f"{ppc(msg, capture)}")
else:
- raise Exception("No packets captured on %s" % name)
+ if 0 == expected_count:
+ return
+ raise Exception(f"No packets captured on {name} (timeout = {timeout}s)")
- def assert_nothing_captured(self, remark=None, filter_out_fn=is_ipv6_misc):
- """ Assert that nothing unfiltered was captured on interface
+ def assert_nothing_captured(
+ self, timeout=1, 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)
+ capture = self.get_capture(
+ 0, timeout=timeout, remark=remark, filter_out_fn=filter_out_fn
+ )
+ if not capture or len(capture.res) == 0:
+ # junk filtered out, we're good
+ return
def wait_for_pg_stop(self):
# wait till packet-generator is stopped
@@ -315,7 +368,7 @@ class VppPGInterface(VppInterface):
#
# also have a 5-minute timeout just in case things go terribly wrong...
deadline = time.time() + 300
- while self.test.vapi.cli('show packet-generator').find("Yes") != -1:
+ while self.test.vapi.cli("show packet-generator").find("Yes") != -1:
self._test.sleep(0.01) # yield
if time.time() > deadline:
self.test.logger.debug("Timeout waiting for pg to stop")
@@ -332,22 +385,26 @@ class VppPGInterface(VppInterface):
self.wait_for_pg_stop()
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))
+ self.test.logger.debug(
+ f"Waiting for capture file {self.out_path} to appear, timeout is {timeout}s\n"
+ f"{' '.join(format_stack(limit=10))}"
+ )
else:
- self.test.logger.debug("Capture file %s already exists" %
- self.out_path)
+ self.test.logger.debug("Capture file %s already exists" % self.out_path)
+ self.link_pcap_file(self.out_path, "out", self.out_history_counter)
return True
while time.time() < deadline:
if os.path.isfile(self.out_path):
break
self._test.sleep(0) # yield
if os.path.isfile(self.out_path):
- self.test.logger.debug("Capture file appeared after %fs" %
- (time.time() - (deadline - timeout)))
+ 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
+ self.link_pcap_file(self.out_path, "out", self.out_history_counter)
return True
def verify_enough_packet_data_in_pcap(self):
@@ -367,7 +424,8 @@ class VppPGInterface(VppInterface):
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.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:
@@ -387,19 +445,22 @@ class VppPGInterface(VppInterface):
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)
+ 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()))
+ "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)
+ raise CaptureTimeoutError(
+ "Capture file %s did not appear within timeout" % self.out_path
+ )
poll = False
if timeout > 0:
@@ -416,23 +477,28 @@ class VppPGInterface(VppInterface):
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)))
+ "Packet received after %ss was filtered out"
+ % (time.time() - (deadline - timeout))
+ )
else:
self.test.logger.debug(
- "Packet received after %fs" %
- (time.time() - (deadline - timeout)))
+ "Packet received after %fs"
+ % (time.time() - (deadline - timeout))
+ )
return p
self._test.sleep(0) # yield
poll = False
- self.test.logger.debug("Timeout - no packets received")
- raise CaptureTimeoutError("Packet didn't arrive within timeout")
+ self.test.logger.debug(f"Timeout ({timeout}) - no packets received")
+ raise CaptureTimeoutError(f"Packet didn't arrive within timeout ({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))
+ 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, addr=None):
"""Create NDP - NS applicable for this interface"""
@@ -441,10 +507,12 @@ class VppPGInterface(VppInterface):
nsma = in6_getnsma(inet_pton(socket.AF_INET6, addr))
d = inet_ntop(socket.AF_INET6, nsma)
- return (Ether(dst=in6_getnsmac(nsma)) /
- IPv6(dst=d, src=self.remote_ip6) /
- ICMPv6ND_NS(tgt=addr) /
- ICMPv6NDOptSrcLLAddr(lladdr=self.remote_mac))
+ return (
+ Ether(dst=in6_getnsmac(nsma))
+ / IPv6(dst=d, src=self.remote_ip6)
+ / ICMPv6ND_NS(tgt=addr)
+ / ICMPv6NDOptSrcLLAddr(lladdr=self.remote_mac)
+ )
def resolve_arp(self, pg_interface=None):
"""Resolve ARP using provided packet-generator interface
@@ -455,8 +523,10 @@ class VppPGInterface(VppInterface):
"""
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))
+ 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()
@@ -465,21 +535,21 @@ class VppPGInterface(VppInterface):
try:
captured_packet = pg_interface.wait_for_packet(1)
except:
- self.test.logger.info("No ARP received on port %s" %
- pg_interface.name)
+ self.test.logger.info("No ARP received on port %s" % pg_interface.name)
return
arp_reply = captured_packet.copy() # keep original for exception
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.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)
+ 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))
+ ppp("Unexpected response to ARP request:", captured_packet)
+ )
raise
def resolve_ndp(self, pg_interface=None, timeout=1, link_layer=False):
@@ -495,8 +565,9 @@ class VppPGInterface(VppInterface):
if pg_interface is None:
pg_interface = self
addr = self.local_ip6_ll if link_layer else self.local_ip6
- self.test.logger.info("Sending NDP request for %s on port %s" %
- (addr, pg_interface.name))
+ self.test.logger.info(
+ "Sending NDP request for %s on port %s" % (addr, pg_interface.name)
+ )
ndp_req = self.create_ndp_req(addr)
pg_interface.add_stream(ndp_req)
pg_interface.enable_capture()
@@ -509,25 +580,26 @@ class VppPGInterface(VppInterface):
while now < deadline:
try:
captured_packet = pg_interface.wait_for_packet(
- deadline - now, filter_out_fn=None)
+ deadline - now, filter_out_fn=None
+ )
except:
- self.test.logger.error(
- "Timeout while waiting for NDP response")
+ self.test.logger.error("Timeout while waiting for NDP response")
raise
ndp_reply = captured_packet.copy() # keep original for exception
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.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))
+ ppp("Unexpected response to NDP request:", captured_packet)
+ )
now = time.time()
self.test.logger.debug(self.test.vapi.cli("show trace"))