diff options
Diffstat (limited to 'test')
-rw-r--r-- | test/bfd.py | 216 | ||||
-rw-r--r-- | test/framework.py | 28 | ||||
-rw-r--r-- | test/hook.py | 20 | ||||
-rw-r--r-- | test/template_bd.py | 3 | ||||
-rw-r--r-- | test/test_bfd.py | 273 | ||||
-rw-r--r-- | test/util.py | 22 | ||||
-rw-r--r-- | test/vpp_object.py | 79 | ||||
-rw-r--r-- | test/vpp_papi_provider.py | 435 | ||||
-rw-r--r-- | test/vpp_pg_interface.py | 40 |
9 files changed, 926 insertions, 190 deletions
diff --git a/test/bfd.py b/test/bfd.py new file mode 100644 index 00000000..beacd80f --- /dev/null +++ b/test/bfd.py @@ -0,0 +1,216 @@ +from socket import AF_INET, AF_INET6 +from scapy.all import * +from scapy.packet import * +from scapy.fields import * +from framework import * +from vpp_object import * +from util import NumericConstant + + +class BFDDiagCode(NumericConstant): + """ BFD Diagnostic Code """ + no_diagnostic = 0 + control_detection_time_expired = 1 + echo_function_failed = 2 + neighbor_signaled_session_down = 3 + forwarding_plane_reset = 4 + path_down = 5 + concatenated_path_down = 6 + administratively_down = 7 + reverse_concatenated_path_down = 8 + + desc_dict = { + no_diagnostic: "No diagnostic", + control_detection_time_expired: "Control Detection Time Expired", + echo_function_failed: "Echo Function Failed", + neighbor_signaled_session_down: "Neighbor Signaled Session Down", + forwarding_plane_reset: "Forwarding Plane Reset", + path_down: "Path Down", + concatenated_path_down: "Concatenated Path Down", + administratively_down: "Administratively Down", + reverse_concatenated_path_down: "Reverse Concatenated Path Down", + } + + def __init__(self, value): + NumericConstant.__init__(self, value) + + +class BFDState(NumericConstant): + """ BFD State """ + admin_down = 0 + down = 1 + init = 2 + up = 3 + + desc_dict = { + admin_down: "AdminDown", + down: "Down", + init: "Init", + up: "Up", + } + + def __init__(self, value): + NumericConstant.__init__(self, value) + + +class BFD(Packet): + + udp_dport = 3784 #: BFD destination port per RFC 5881 + udp_sport_min = 49152 #: BFD source port min value per RFC 5881 + udp_sport_max = 65535 #: BFD source port max value per RFC 5881 + + name = "BFD" + + fields_desc = [ + BitField("version", 1, 3), + BitEnumField("diag", 0, 5, BFDDiagCode.desc_dict), + BitEnumField("state", 0, 2, BFDState.desc_dict), + FlagsField("flags", 0, 6, ['P', 'F', 'C', 'A', 'D', 'M']), + XByteField("detect_mult", 0), + XByteField("length", 24), + BitField("my_discriminator", 0, 32), + BitField("your_discriminator", 0, 32), + BitField("desired_min_tx_interval", 0, 32), + BitField("required_min_rx_interval", 0, 32), + BitField("required_min_echo_rx_interval", 0, 32)] + + def mysummary(self): + return self.sprintf("BFD(my_disc=%BFD.my_discriminator%," + "your_disc=%BFD.your_discriminator%)") + +# glue the BFD packet class to scapy parser +bind_layers(UDP, BFD, dport=BFD.udp_dport) + + +class VppBFDUDPSession(VppObject): + """ Represents BFD UDP session in VPP """ + + @property + def test(self): + """ Test which created this session """ + return self._test + + @property + def interface(self): + """ Interface on which this session lives """ + return self._interface + + @property + def af(self): + """ Address family - AF_INET or AF_INET6 """ + return self._af + + @property + def bs_index(self): + """ BFD session index from VPP """ + if self._bs_index is not None: + return self._bs_index + raise NotConfiguredException("not configured") + + @property + def local_addr(self): + """ BFD session local address (VPP address) """ + if self._local_addr is None: + return self._interface.local_ip4 + return self._local_addr + + @property + def local_addr_n(self): + """ BFD session local address (VPP address) - raw, suitable for API """ + if self._local_addr is None: + return self._interface.local_ip4n + return self._local_addr_n + + @property + def peer_addr(self): + """ BFD session peer address """ + return self._peer_addr + + @property + def peer_addr_n(self): + """ BFD session peer address - raw, suitable for API """ + return self._peer_addr_n + + @property + def state(self): + """ BFD session state """ + result = self.test.vapi.bfd_udp_session_dump() + session = None + for s in result: + if s.sw_if_index == self.interface.sw_if_index: + if self.af == AF_INET \ + and s.is_ipv6 == 0 \ + and self.interface.local_ip4n == s.local_addr[:4] \ + and self.interface.remote_ip4n == s.peer_addr[:4]: + session = s + break + if session is None: + raise Exception( + "Could not find BFD session in VPP response: %s" % repr(result)) + return session.state + + @property + def desired_min_tx(self): + return self._desired_min_tx + + @property + def required_min_rx(self): + return self._required_min_rx + + @property + def detect_mult(self): + return self._detect_mult + + def __init__(self, test, interface, peer_addr, local_addr=None, af=AF_INET): + self._test = test + self._interface = interface + self._af = af + self._local_addr = local_addr + self._peer_addr = peer_addr + self._peer_addr_n = socket.inet_pton(af, peer_addr) + self._bs_index = None + self._desired_min_tx = 200000 # 0.2s + self._required_min_rx = 200000 # 0.2s + self._detect_mult = 3 # 3 packets need to be missed + + def add_vpp_config(self): + is_ipv6 = 1 if AF_INET6 == self.af else 0 + result = self.test.vapi.bfd_udp_add( + self._interface.sw_if_index, + self.desired_min_tx, + self.required_min_rx, + self.detect_mult, + self.local_addr_n, + self.peer_addr_n, + is_ipv6=is_ipv6) + self._bs_index = result.bs_index + + def query_vpp_config(self): + result = self.test.vapi.bfd_udp_session_dump() + session = None + for s in result: + if s.sw_if_index == self.interface.sw_if_index: + if self.af == AF_INET \ + and s.is_ipv6 == 0 \ + and self.interface.local_ip4n == s.local_addr[:4] \ + and self.interface.remote_ip4n == s.peer_addr[:4]: + session = s + break + if session is None: + return False + return True + + def remove_vpp_config(self): + if hasattr(self, '_bs_index'): + is_ipv6 = 1 if AF_INET6 == self._af else 0 + self.test.vapi.bfd_udp_del( + self._interface.sw_if_index, + self.local_addr_n, + self.peer_addr_n, + is_ipv6=is_ipv6) + + def object_id(self): + return "bfd-udp-%d" % self.bs_index + + def admin_up(self): + self.test.vapi.bfd_session_set_flags(self.bs_index, 1) diff --git a/test/framework.py b/test/framework.py index b3cbb08a..aa4f2fdf 100644 --- a/test/framework.py +++ b/test/framework.py @@ -462,6 +462,34 @@ class VppTestCase(unittest.TestCase): if info.dst == dst_index: return info + def assert_equal(self, real_value, expected_value, name_or_class=None): + if name_or_class is None: + self.assertEqual(real_value, expected_value, msg) + return + try: + msg = "Invalid %s: %d('%s') does not match expected value %d('%s')" + msg = msg % (getdoc(name_or_class).strip(), + real_value, str(name_or_class(real_value)), + expected_value, str(name_or_class(expected_value))) + except: + msg = "Invalid %s: %s does not match expected value %s" % ( + name_or_class, real_value, expected_value) + + self.assertEqual(real_value, expected_value, msg) + + def assert_in_range( + self, + real_value, + expected_min, + expected_max, + name=None): + if name is None: + msg = None + else: + msg = "Invalid %s: %s out of range <%s,%s>" % ( + name, real_value, expected_min, expected_max) + self.assertTrue(expected_min <= real_value <= expected_max, msg) + class VppTestResult(unittest.TestResult): """ diff --git a/test/hook.py b/test/hook.py index 90e9bbf5..f3e5f880 100644 --- a/test/hook.py +++ b/test/hook.py @@ -100,9 +100,13 @@ class PollHook(Hook): signaldict = dict( (k, v) for v, k in reversed(sorted(signal.__dict__.items())) if v.startswith('SIG') and not v.startswith('SIG_')) + + if self.testcase.vpp.returncode in signaldict: + s = signaldict[abs(self.testcase.vpp.returncode)] + else: + s = "unknown" msg = "VPP subprocess died unexpectedly with returncode %d [%s]" % ( - self.testcase.vpp.returncode, - signaldict[abs(self.testcase.vpp.returncode)]) + self.testcase.vpp.returncode, s) self.logger.critical(msg) core_path = self.testcase.tempdir + '/core' if os.path.isfile(core_path): @@ -110,27 +114,27 @@ class PollHook(Hook): self.testcase.vpp_dead = True raise VppDiedError(msg) - def after_api(self, api_name, api_args): + def before_api(self, api_name, api_args): """ - Check if VPP died after executing an API + Check if VPP died before executing an API :param api_name: name of the API :param api_args: tuple containing the API arguments :raises VppDiedError: exception if VPP is not running anymore """ - super(PollHook, self).after_api(api_name, api_args) + super(PollHook, self).before_api(api_name, api_args) self.poll_vpp() - def after_cli(self, cli): + def before_cli(self, cli): """ - Check if VPP died after executing a CLI + Check if VPP died before executing a CLI :param cli: CLI string :raises Exception: exception if VPP is not running anymore """ - super(PollHook, self).after_cli(cli) + super(PollHook, self).before_cli(cli) self.poll_vpp() diff --git a/test/template_bd.py b/test/template_bd.py index 6c6fb3da..01e8b855 100644 --- a/test/template_bd.py +++ b/test/template_bd.py @@ -1,6 +1,6 @@ #!/usr/bin/env python -from abc import abstractmethod +from abc import abstractmethod, ABCMeta from scapy.layers.l2 import Ether, Raw from scapy.layers.inet import IP, UDP @@ -8,6 +8,7 @@ from scapy.layers.inet import IP, UDP class BridgeDomain(object): """ Bridge domain abstraction """ + __metaclass__ = ABCMeta @property def frame_pg0_to_pg1(self): diff --git a/test/test_bfd.py b/test/test_bfd.py new file mode 100644 index 00000000..20d3aea5 --- /dev/null +++ b/test/test_bfd.py @@ -0,0 +1,273 @@ +#!/usr/bin/env python + +import unittest +import time +from random import randint +from bfd import * +from framework import * +from util import ppp + + +class BFDCLITestCase(VppTestCase): + """Bidirectional Forwarding Detection (BFD) - CLI""" + + @classmethod + def setUpClass(cls): + super(BFDCLITestCase, cls).setUpClass() + + try: + cls.create_pg_interfaces([0]) + cls.pg0.config_ip4() + cls.pg0.resolve_arp() + + except Exception: + super(BFDCLITestCase, cls).tearDownClass() + raise + + def test_add_bfd(self): + """ create a BFD session """ + session = VppBFDUDPSession(self, self.pg0, self.pg0.remote_ip4) + session.add_vpp_config() + self.logger.debug("Session state is %s" % str(session.state)) + session.remove_vpp_config() + session = VppBFDUDPSession(self, self.pg0, self.pg0.remote_ip4) + session.add_vpp_config() + self.logger.debug("Session state is %s" % str(session.state)) + session.remove_vpp_config() + + def test_double_add(self): + """ create the same BFD session twice (negative case) """ + session = VppBFDUDPSession(self, self.pg0, self.pg0.remote_ip4) + session.add_vpp_config() + try: + session.add_vpp_config() + except: + session.remove_vpp_config() + return + session.remove_vpp_config() + raise Exception("Expected failure while adding duplicate " + "configuration") + + +def create_packet(interface, ttl=255, src_port=50000, **kwargs): + p = (Ether(src=interface.remote_mac, dst=interface.local_mac) / + IP(src=interface.remote_ip4, dst=interface.local_ip4, ttl=ttl) / + UDP(sport=src_port, dport=BFD.udp_dport) / + BFD(*kwargs)) + return p + + +def verify_ip(test, packet, local_ip, remote_ip): + """ Verify correctness of IP layer. """ + ip = packet[IP] + test.assert_equal(ip.src, local_ip, "IP source address") + test.assert_equal(ip.dst, remote_ip, "IP destination address") + test.assert_equal(ip.ttl, 255, "IP TTL") + + +def verify_udp(test, packet): + """ Verify correctness of UDP layer. """ + udp = packet[UDP] + test.assert_equal(udp.dport, BFD.udp_dport, "UDP destination port") + test.assert_in_range(udp.sport, BFD.udp_sport_min, BFD.udp_sport_max, + "UDP source port") + + +class BFDTestSession(object): + + def __init__(self, test, interface, detect_mult=3): + self.test = test + self.interface = interface + self.bfd_values = { + 'my_discriminator': 0, + 'desired_min_tx_interval': 500000, + 'detect_mult': detect_mult, + 'diag': BFDDiagCode.no_diagnostic, + } + + def update(self, **kwargs): + self.bfd_values.update(kwargs) + + def create_packet(self): + packet = create_packet(self.interface) + for name, value in self.bfd_values.iteritems(): + packet[BFD].setfieldval(name, value) + return packet + + def send_packet(self): + p = self.create_packet() + self.test.logger.debug(ppp("Sending packet:", p)) + self.test.pg0.add_stream([p]) + self.test.pg_start() + + def verify_packet(self, packet): + """ Verify correctness of BFD layer. """ + bfd = packet[BFD] + self.test.assert_equal(bfd.version, 1, "BFD version") + self.test.assert_equal(bfd.your_discriminator, + self.bfd_values['my_discriminator'], + "BFD - your discriminator") + + +class BFDTestCase(VppTestCase): + """Bidirectional Forwarding Detection (BFD)""" + + @classmethod + def setUpClass(cls): + super(BFDTestCase, cls).setUpClass() + try: + cls.create_pg_interfaces([0, 1]) + cls.pg0.config_ip4() + cls.pg0.generate_remote_hosts() + cls.pg0.configure_ipv4_neighbors() + cls.pg0.admin_up() + cls.pg0.resolve_arp() + + except Exception: + super(BFDTestCase, cls).tearDownClass() + raise + + def setUp(self): + self.vapi.want_bfd_events() + self.vpp_session = VppBFDUDPSession(self, self.pg0, self.pg0.remote_ip4) + self.vpp_session.add_vpp_config() + self.vpp_session.admin_up() + self.test_session = BFDTestSession(self, self.pg0) + + def tearDown(self): + self.vapi.want_bfd_events(enable_disable=0) + if not self.vpp_dead: + self.vpp_session.remove_vpp_config() + super(BFDTestCase, self).tearDown() + + def verify_event(self, event, expected_state): + """ Verify correctness of event values. """ + e = event + self.logger.debug("Event: %s" % repr(e)) + self.assert_equal(e.bs_index, self.vpp_session.bs_index, + "BFD session index") + self.assert_equal(e.sw_if_index, self.vpp_session.interface.sw_if_index, + "BFD interface index") + is_ipv6 = 0 + if self.vpp_session.af == AF_INET6: + is_ipv6 = 1 + self.assert_equal(e.is_ipv6, is_ipv6, "is_ipv6") + if self.vpp_session.af == AF_INET: + self.assert_equal(e.local_addr[:4], self.vpp_session.local_addr_n, + "Local IPv4 address") + self.assert_equal(e.peer_addr[:4], self.vpp_session.peer_addr_n, + "Peer IPv4 address") + else: + self.assert_equal(e.local_addr, self.vpp_session.local_addr_n, + "Local IPv6 address") + self.assert_equal(e.peer_addr, self.vpp_session.peer_addr_n, + "Peer IPv6 address") + self.assert_equal(e.state, expected_state, BFDState) + + def wait_for_bfd_packet(self, timeout=1): + p = self.pg0.wait_for_packet(timeout=timeout) + bfd = p[BFD] + if bfd is None: + raise Exception(ppp("Unexpected or invalid BFD packet:", p)) + if bfd.payload: + raise Exception(ppp("Unexpected payload in BFD packet:", bfd)) + verify_ip(self, p, self.pg0.local_ip4, self.pg0.remote_ip4) + verify_udp(self, p) + self.test_session.verify_packet(p) + return p + + def test_slow_timer(self): + """ Slow timer """ + + self.pg_enable_capture([self.pg0]) + expected_packets = 10 + self.logger.info("Waiting for %d BFD packets" % expected_packets) + self.wait_for_bfd_packet() + for i in range(expected_packets): + before = time.time() + self.wait_for_bfd_packet() + after = time.time() + self.assert_in_range( + after - before, 0.75, 1, "time between slow packets") + before = after + + def test_zero_remote_min_rx(self): + """ Zero RemoteMinRxInterval """ + self.pg_enable_capture([self.pg0]) + p = self.wait_for_bfd_packet() + self.test_session.update(my_discriminator=randint(0, 40000000), + your_discriminator=p[BFD].my_discriminator, + state=BFDState.init, + required_min_rx_interval=0) + self.test_session.send_packet() + e = self.vapi.wait_for_event(1, "bfd_udp_session_details") + self.verify_event(e, expected_state=BFDState.up) + + try: + p = self.pg0.wait_for_packet(timeout=1) + except: + return + raise Exception(ppp("Received unexpected BFD packet:", p)) + + def bfd_conn_up(self): + self.pg_enable_capture([self.pg0]) + self.logger.info("Waiting for slow hello") + p = self.wait_for_bfd_packet() + self.logger.info("Sending Init") + self.test_session.update(my_discriminator=randint(0, 40000000), + your_discriminator=p[BFD].my_discriminator, + state=BFDState.init, + required_min_rx_interval=500000) + self.test_session.send_packet() + self.logger.info("Waiting for event") + e = self.vapi.wait_for_event(1, "bfd_udp_session_details") + self.verify_event(e, expected_state=BFDState.up) + self.logger.info("Session is Up") + self.test_session.update(state=BFDState.up) + + def test_conn_up(self): + """ Basic connection up """ + self.bfd_conn_up() + + def test_hold_up(self): + """ Hold BFD up """ + self.bfd_conn_up() + for i in range(5): + self.wait_for_bfd_packet() + self.test_session.send_packet() + + def test_conn_down(self): + """ Session down after inactivity """ + self.bfd_conn_up() + self.wait_for_bfd_packet() + self.assert_equal( + 0, len(self.vapi.collect_events()), + "number of bfd events") + self.wait_for_bfd_packet() + self.assert_equal( + 0, len(self.vapi.collect_events()), + "number of bfd events") + e = self.vapi.wait_for_event(1, "bfd_udp_session_details") + self.verify_event(e, expected_state=BFDState.down) + + @unittest.skip("this test is not working yet") + def test_large_required_min_rx(self): + self.bfd_conn_up() + interval = 5000000 + self.test_session.update(required_min_rx_interval=interval) + self.test_session.send_packet() + now = time.time() + count = 1 + while time.time() < now + interval / 1000000: + try: + p = self.wait_for_bfd_packet() + if count > 1: + self.logger.error(ppp("Received unexpected packet:", p)) + count += 1 + except: + pass + self.assert_equal(count, 1, "number of packets received") + + +if __name__ == '__main__': + unittest.main(testRunner=VppTestRunner) diff --git a/test/util.py b/test/util.py index 643377f5..f6c6acd4 100644 --- a/test/util.py +++ b/test/util.py @@ -1,5 +1,6 @@ import socket import sys +from abc import abstractmethod, ABCMeta from cStringIO import StringIO @@ -14,6 +15,27 @@ def ppp(headline, packet): return o.getvalue() +class NumericConstant(object): + __metaclass__ = ABCMeta + + desc_dict = {} + + @abstractmethod + def __init__(self, value): + self._value = value + + def __int__(self): + return self._value + + def __long__(self): + return self._value + + def __str__(self): + if self._value in self.desc_dict: + return self.desc_dict[self._value] + return "" + + class Host(object): """ Generic test host "connected" to VPPs interface. """ diff --git a/test/vpp_object.py b/test/vpp_object.py new file mode 100644 index 00000000..2b71fc1f --- /dev/null +++ b/test/vpp_object.py @@ -0,0 +1,79 @@ +from abc import ABCMeta, abstractmethod + + +class VppObject(object): + """ Abstract vpp object """ + __metaclass__ = ABCMeta + + def __init__(self): + VppObjectRegistry().register(self) + + @abstractmethod + def add_vpp_config(self): + """ Add the configuration for this object to vpp. """ + pass + + @abstractmethod + def query_vpp_config(self): + """Query the vpp configuration. + + :return: True if the object is configured""" + pass + + @abstractmethod + def remove_vpp_config(self): + """ Remove the configuration for this object from vpp. """ + pass + + @abstractmethod + def object_id(self): + """ Return a unique string representing this object. """ + pass + + +class VppObjectRegistry(object): + """ Class which handles automatic configuration cleanup. """ + _shared_state = {} + + def __init__(self): + self.__dict__ = self._shared_state + if not hasattr(self, "_object_registry"): + self._object_registry = [] + if not hasattr(self, "_object_dict"): + self._object_dict = dict() + + def register(self, o): + """ Register an object in the registry. """ + if not o.unique_id() in self._object_dict: + self._object_registry.append(o) + self._object_dict[o.unique_id()] = o + else: + print "not adding duplicate %s" % o + + def remove_vpp_config(self, logger): + """ + Remove configuration (if present) from vpp and then remove all objects + from the registry. + """ + if not self._object_registry: + logger.info("No objects registered for auto-cleanup.") + return + logger.info("Removing VPP configuration for registered objects") + for o in reversed(self._object_registry): + if o.query_vpp_config(): + logger.info("Removing %s", o) + o.remove_vpp_config() + else: + logger.info("Skipping %s, configuration not present", o) + failed = [] + for o in self._object_registry: + if o.query_vpp_config(): + failed.append(o) + self._object_registry = [] + self._object_dict = dict() + if failed: + logger.error("Couldn't remove configuration for object(s):") + for x in failed: + logger.error(repr(x)) + raise Exception("Couldn't remove configuration for object(s): %s" % + (", ".join(str(x) for x in failed))) diff --git a/test/vpp_papi_provider.py b/test/vpp_papi_provider.py index 2f931803..5e80a03e 100644 --- a/test/vpp_papi_provider.py +++ b/test/vpp_papi_provider.py @@ -1,7 +1,12 @@ -import os, fnmatch -import array +import os +import fnmatch +import time from hook import Hook +# Sphinx creates auto-generated documentation by importing the python source +# files and collecting the docstrings from them. The NO_VPP_PAPI flag allows the +# vpp_papi_provider.py file to be importable without having to build the whole +# vpp api if the user only wishes to generate the test documentation. do_import = True try: no_vpp_papi = os.getenv("NO_VPP_PAPI") @@ -17,9 +22,11 @@ if do_import: MPLS_IETF_MAX_LABEL = 0xfffff MPLS_LABEL_INVALID = MPLS_IETF_MAX_LABEL + 1 + class L2_VTR_OP: L2_POP_1 = 3 + class VppPapiProvider(object): """VPP-api provider using vpp-papi @@ -35,12 +42,13 @@ class VppPapiProvider(object): self.test_class = test_class jsonfiles = [] - install_dir=os.getenv('VPP_TEST_INSTALL_PATH') + install_dir = os.getenv('VPP_TEST_INSTALL_PATH') for root, dirnames, filenames in os.walk(install_dir): for filename in fnmatch.filter(filenames, '*.api.json'): jsonfiles.append(os.path.join(root, filename)) self.papi = VPP(jsonfiles) + self._events = list() def register_hook(self, hook): """Replace hook registration with new hook @@ -50,9 +58,35 @@ class VppPapiProvider(object): """ self.hook = hook + def collect_events(self): + e = self._events + self._events = list() + return e + + def wait_for_event(self, timeout, name=None): + limit = time.time() + timeout + while time.time() < limit: + if self._events: + e = self._events.pop(0) + if name and type(e).__name__ != name: + raise Exception( + "Unexpected event received: %s, expected: %s" % + (type(e).__name__, name)) + return e + time.sleep(0) # yield + if name is not None: + raise Exception("Event %s did not occur within timeout" % name) + raise Exception("Event did not occur within timeout") + + def __call__(self, name, event): + # FIXME use the name instead of relying on type(e).__name__ ? + # FIXME #2 if this throws, it is eaten silently, Ole? + self._events.append(event) + def connect(self): """Connect the API to VPP""" self.papi.connect(self.name, self.shm_prefix) + self.papi.register_event_callback(self) def disconnect(self): """Disconnect the API from VPP""" @@ -73,7 +107,7 @@ class VppPapiProvider(object): if hasattr(reply, 'retval') and reply.retval != expected_retval: msg = "API call failed, expected retval == %d, got %s" % ( expected_retval, repr(reply)) - self.test_class.test_instance.logger.error(msg) + self.test_class.logger.error(msg) raise Exception(msg) self.hook.after_api(api_fn.__name__, api_args) return reply @@ -116,7 +150,7 @@ class VppPapiProvider(object): """ return self.api(self.papi.pg_create_interface, - { "interface_id" : pg_index }) + {"interface_id": pg_index}) def sw_interface_dump(self, filter=None): """ @@ -125,7 +159,7 @@ class VppPapiProvider(object): """ if filter is not None: - args = {"name_filter_valid" : 1, "name_filter" : filter} + args = {"name_filter_valid": 1, "name_filter": filter} else: args = {} return self.api(self.papi.sw_interface_dump, args) @@ -140,8 +174,8 @@ class VppPapiProvider(object): """ return self.api(self.papi.sw_interface_set_table, - { 'sw_if_index' : sw_if_index, 'is_ipv6' : is_ipv6, - 'vrf_id' : table_id}) + {'sw_if_index': sw_if_index, 'is_ipv6': is_ipv6, + 'vrf_id': table_id}) def sw_interface_add_del_address(self, sw_if_index, addr, addr_len, is_ipv6=0, is_add=1, del_all=0): @@ -156,12 +190,12 @@ class VppPapiProvider(object): """ return self.api(self.papi.sw_interface_add_del_address, - { 'sw_if_index' : sw_if_index, - 'is_add' : is_add, - 'is_ipv6' : is_ipv6, - 'del_all' : del_all, - 'address_length' : addr_len, - 'address' : addr}) + {'sw_if_index': sw_if_index, + 'is_add': is_add, + 'is_ipv6': is_ipv6, + 'del_all': del_all, + 'address_length': addr_len, + 'address': addr}) def sw_interface_enable_disable_mpls(self, sw_if_index, is_enable=1): @@ -172,12 +206,12 @@ class VppPapiProvider(object): """ return self.api(self.papi.sw_interface_set_mpls_enable, - {'sw_if_index' : sw_if_index, - 'enable' : is_enable }) + {'sw_if_index': sw_if_index, + 'enable': is_enable}) def sw_interface_ra_suppress(self, sw_if_index): return self.api(self.papi.sw_interface_ip6nd_ra_config, - {'sw_if_index' : sw_if_index }) + {'sw_if_index': sw_if_index}) def vxlan_add_del_tunnel( self, @@ -202,14 +236,14 @@ class VppPapiProvider(object): """ return self.api(self.papi.vxlan_add_del_tunnel, - {'is_add' : is_add, - 'is_ipv6' : is_ipv6, - 'src_address' : src_addr, - 'dst_address' : dst_addr, - 'mcast_sw_if_index' : mcast_sw_if_index, - 'encap_vrf_id' : encap_vrf_id, - 'decap_next_index' : decap_next_index, - 'vni' : vni}) + {'is_add': is_add, + 'is_ipv6': is_ipv6, + 'src_address': src_addr, + 'dst_address': dst_addr, + 'mcast_sw_if_index': mcast_sw_if_index, + 'encap_vrf_id': encap_vrf_id, + 'decap_next_index': decap_next_index, + 'vni': vni}) def bridge_domain_add_del(self, bd_id, flood=1, uu_flood=1, forward=1, learn=1, arp_term=0, is_add=1): @@ -229,13 +263,13 @@ class VppPapiProvider(object): :param int is_add: Add or delete flag. (Default value = 1) """ return self.api(self.papi.bridge_domain_add_del, - { 'bd_id' : bd_id, - 'flood' : flood, - 'uu_flood' : uu_flood, - 'forward' : forward, - 'learn' : learn, - 'arp_term' : arp_term, - 'is_add' : is_add}) + {'bd_id': bd_id, + 'flood': flood, + 'uu_flood': uu_flood, + 'forward': forward, + 'learn': learn, + 'arp_term': arp_term, + 'is_add': is_add}) def l2fib_add_del(self, mac, bd_id, sw_if_index, is_add=1, static_mac=0, filter_mac=0, bvi_mac=0): @@ -254,13 +288,13 @@ class VppPapiProvider(object): interface. (Default value = 0) """ return self.api(self.papi.l2fib_add_del, - { 'mac' : self._convert_mac(mac), - 'bd_id' : bd_id, - 'sw_if_index' : sw_if_index, - 'is_add' : is_add, - 'static_mac' : static_mac, - 'filter_mac' : filter_mac, - 'bvi_mac' : bvi_mac }) + {'mac': self._convert_mac(mac), + 'bd_id': bd_id, + 'sw_if_index': sw_if_index, + 'is_add': is_add, + 'static_mac': static_mac, + 'filter_mac': filter_mac, + 'bvi_mac': bvi_mac}) def sw_interface_set_l2_bridge(self, sw_if_index, bd_id, shg=0, bvi=0, enable=1): @@ -274,11 +308,11 @@ class VppPapiProvider(object): :param int enable: Add or remove interface. (Default value = 1) """ return self.api(self.papi.sw_interface_set_l2_bridge, - { 'rx_sw_if_index' : sw_if_index, - 'bd_id' : bd_id, - 'shg' : shg, - 'bvi' : bvi, - 'enable' : enable }) + {'rx_sw_if_index': sw_if_index, + 'bd_id': bd_id, + 'shg': shg, + 'bvi': bvi, + 'enable': enable}) def bridge_flags(self, bd_id, is_set, feature_bitmap): """Enable/disable required feature of the bridge domain with defined ID. @@ -293,9 +327,9 @@ class VppPapiProvider(object): - arp-term (1 << 4). """ return self.api(self.papi.bridge_flags, - {'bd_id' : bd_id, - 'is_set' : is_set, - 'feature_bitmap' : feature_bitmap }) + {'bd_id': bd_id, + 'is_set': is_set, + 'feature_bitmap': feature_bitmap}) def bridge_domain_dump(self, bd_id=0): """ @@ -305,7 +339,7 @@ class VppPapiProvider(object): :return: Dictionary of bridge domain(s) data. """ return self.api(self.papi.bridge_domain_dump, - {'bd_id' : bd_id }) + {'bd_id': bd_id}) def sw_interface_set_l2_xconnect(self, rx_sw_if_index, tx_sw_if_index, enable): @@ -319,11 +353,17 @@ class VppPapiProvider(object): """ return self.api(self.papi.sw_interface_set_l2_xconnect, - { 'rx_sw_if_index' : rx_sw_if_index, - 'tx_sw_if_index' : tx_sw_if_index, - 'enable' : enable }) + {'rx_sw_if_index': rx_sw_if_index, + 'tx_sw_if_index': tx_sw_if_index, + 'enable': enable}) - def sw_interface_set_l2_tag_rewrite(self, sw_if_index, vtr_oper, push=0, tag1=0, tag2=0): + def sw_interface_set_l2_tag_rewrite( + self, + sw_if_index, + vtr_oper, + push=0, + tag1=0, + tag2=0): """L2 interface vlan tag rewrite configure request :param client_index - opaque cookie to identify the sender :param context - sender context, to match reply w/ request @@ -335,11 +375,11 @@ class VppPapiProvider(object): """ return self.api(self.papi.l2_interface_vlan_tag_rewrite, - { 'sw_if_index' : sw_if_index, - 'vtr_op' : vtr_oper, - 'push_dot1q' : push, - 'tag1' : tag1, - 'tag2' : tag2 }) + {'sw_if_index': sw_if_index, + 'vtr_op': vtr_oper, + 'push_dot1q': push, + 'tag1': tag1, + 'tag2': tag2}) def sw_interface_set_flags(self, sw_if_index, admin_up_down, link_up_down=0, deleted=0): @@ -352,10 +392,10 @@ class VppPapiProvider(object): """ return self.api(self.papi.sw_interface_set_flags, - { 'sw_if_index' : sw_if_index, - 'admin_up_down' : admin_up_down, - 'link_up_down' : link_up_down, - 'deleted' : deleted }) + {'sw_if_index': sw_if_index, + 'admin_up_down': admin_up_down, + 'link_up_down': link_up_down, + 'deleted': deleted}) def create_subif(self, sw_if_index, sub_id, outer_vlan, inner_vlan, no_tags=0, one_tag=0, two_tags=0, dot1ad=0, exact_match=0, @@ -379,18 +419,18 @@ class VppPapiProvider(object): """ return self.api( self.papi.create_subif, - { 'sw_if_index' : sw_if_index, - 'sub_id' : sub_id, - 'no_tags' : no_tags, - 'one_tag' : one_tag, - 'two_tags' : two_tags, - 'dot1ad' : dot1ad, - 'exact_match' : exact_match, - 'default_sub' : default_sub, - 'outer_vlan_id_any' : outer_vlan_id_any, - 'inner_vlan_id_any' : inner_vlan_id_any, - 'outer_vlan_id' : outer_vlan, - 'inner_vlan_id' : inner_vlan }) + {'sw_if_index': sw_if_index, + 'sub_id': sub_id, + 'no_tags': no_tags, + 'one_tag': one_tag, + 'two_tags': two_tags, + 'dot1ad': dot1ad, + 'exact_match': exact_match, + 'default_sub': default_sub, + 'outer_vlan_id_any': outer_vlan_id_any, + 'inner_vlan_id_any': inner_vlan_id_any, + 'outer_vlan_id': outer_vlan, + 'inner_vlan_id': inner_vlan}) def delete_subif(self, sw_if_index): """Delete subinterface @@ -398,7 +438,7 @@ class VppPapiProvider(object): :param sw_if_index: """ return self.api(self.papi.delete_subif, - { 'sw_if_index' : sw_if_index }) + {'sw_if_index': sw_if_index}) def create_vlan_subif(self, sw_if_index, vlan): """ @@ -408,8 +448,8 @@ class VppPapiProvider(object): """ return self.api(self.papi.create_vlan_subif, - {'sw_if_index' : sw_if_index, - 'vlan_id' : vlan }) + {'sw_if_index': sw_if_index, + 'vlan_id': vlan}) def create_loopback(self, mac=''): """ @@ -417,7 +457,7 @@ class VppPapiProvider(object): :param mac: (Optional) """ return self.api(self.papi.create_loopback, - { 'mac_address' : mac }) + {'mac_address': mac}) def ip_add_del_route( self, @@ -428,9 +468,9 @@ class VppPapiProvider(object): table_id=0, next_hop_table_id=0, next_hop_weight=1, - next_hop_n_out_labels = 0, - next_hop_out_label_stack = [], - next_hop_via_label = MPLS_LABEL_INVALID, + next_hop_n_out_labels=0, + next_hop_out_label_stack=[], + next_hop_via_label=MPLS_LABEL_INVALID, create_vrf_if_needed=0, is_resolve_host=0, is_resolve_attached=0, @@ -470,29 +510,29 @@ class VppPapiProvider(object): return self.api( self.papi.ip_add_del_route, - { 'next_hop_sw_if_index' : next_hop_sw_if_index, - 'table_id' : table_id, - 'classify_table_index' : classify_table_index, - 'next_hop_table_id' : next_hop_table_id, - 'create_vrf_if_needed' : create_vrf_if_needed, - 'is_add' : is_add, - 'is_drop' : is_drop, - 'is_unreach' : is_unreach, - 'is_prohibit' : is_prohibit, - 'is_ipv6' : is_ipv6, - 'is_local' : is_local, - 'is_classify' : is_classify, - 'is_multipath' : is_multipath, - 'is_resolve_host' : is_resolve_host, - 'is_resolve_attached' : is_resolve_attached, - 'not_last' : not_last, - 'next_hop_weight' : next_hop_weight, - 'dst_address_length' : dst_address_length, - 'dst_address' : dst_address, - 'next_hop_address' : next_hop_address, - 'next_hop_n_out_labels' : next_hop_n_out_labels, - 'next_hop_via_label' : next_hop_via_label, - 'next_hop_out_label_stack' : next_hop_out_label_stack }) + {'next_hop_sw_if_index': next_hop_sw_if_index, + 'table_id': table_id, + 'classify_table_index': classify_table_index, + 'next_hop_table_id': next_hop_table_id, + 'create_vrf_if_needed': create_vrf_if_needed, + 'is_add': is_add, + 'is_drop': is_drop, + 'is_unreach': is_unreach, + 'is_prohibit': is_prohibit, + 'is_ipv6': is_ipv6, + 'is_local': is_local, + 'is_classify': is_classify, + 'is_multipath': is_multipath, + 'is_resolve_host': is_resolve_host, + 'is_resolve_attached': is_resolve_attached, + 'not_last': not_last, + 'next_hop_weight': next_hop_weight, + 'dst_address_length': dst_address_length, + 'dst_address': dst_address, + 'next_hop_address': next_hop_address, + 'next_hop_n_out_labels': next_hop_n_out_labels, + 'next_hop_via_label': next_hop_via_label, + 'next_hop_out_label_stack': next_hop_out_label_stack}) def ip_neighbor_add_del(self, sw_if_index, @@ -516,13 +556,13 @@ class VppPapiProvider(object): return self.api( self.papi.ip_neighbor_add_del, - { 'vrf_id' : vrf_id, - 'sw_if_index' : sw_if_index, - 'is_add' : is_add, - 'is_ipv6' : is_ipv6, - 'is_static' : is_static, - 'mac_address' : mac_address, - 'dst_address' : dst_address + {'vrf_id': vrf_id, + 'sw_if_index': sw_if_index, + 'is_add': is_add, + 'is_ipv6': is_ipv6, + 'is_static': is_static, + 'mac_address': mac_address, + 'dst_address': dst_address } ) @@ -536,9 +576,9 @@ class VppPapiProvider(object): """ return self.api(self.papi.sw_interface_span_enable_disable, - { 'sw_if_index_from' : sw_if_index_from, - 'sw_if_index_to' : sw_if_index_to, - 'state' : state }) + {'sw_if_index_from': sw_if_index_from, + 'sw_if_index_to': sw_if_index_to, + 'state': state}) def gre_tunnel_add_del(self, src_address, @@ -559,12 +599,12 @@ class VppPapiProvider(object): return self.api( self.papi.gre_add_del_tunnel, - { 'is_add' : is_add, - 'is_ipv6' : is_ip6, - 'teb' : is_teb, - 'src_address' : src_address, - 'dst_address' : dst_address, - 'outer_fib_id' : outer_fib_id } + {'is_add': is_add, + 'is_ipv6': is_ip6, + 'teb': is_teb, + 'src_address': src_address, + 'dst_address': dst_address, + 'outer_fib_id': outer_fib_id} ) def mpls_route_add_del( @@ -577,9 +617,9 @@ class VppPapiProvider(object): table_id=0, next_hop_table_id=0, next_hop_weight=1, - next_hop_n_out_labels = 0, - next_hop_out_label_stack = [], - next_hop_via_label = MPLS_LABEL_INVALID, + next_hop_n_out_labels=0, + next_hop_out_label_stack=[], + next_hop_via_label=MPLS_LABEL_INVALID, create_vrf_if_needed=0, is_resolve_host=0, is_resolve_attached=0, @@ -615,24 +655,24 @@ class VppPapiProvider(object): return self.api( self.papi.mpls_route_add_del, - { 'mr_label' : label, - 'mr_eos' : eos, - 'mr_table_id' : table_id, - 'mr_classify_table_index' : classify_table_index, - 'mr_create_table_if_needed' : create_vrf_if_needed, - 'mr_is_add' : is_add, - 'mr_is_classify' : is_classify, - 'mr_is_multipath' : is_multipath, - 'mr_is_resolve_host' : is_resolve_host, - 'mr_is_resolve_attached' : is_resolve_attached, - 'mr_next_hop_proto_is_ip4' : next_hop_proto_is_ip4, - 'mr_next_hop_weight' : next_hop_weight, - 'mr_next_hop' : next_hop_address, - 'mr_next_hop_n_out_labels' : next_hop_n_out_labels, - 'mr_next_hop_sw_if_index' : next_hop_sw_if_index, - 'mr_next_hop_table_id' : next_hop_table_id, - 'mr_next_hop_via_label' : next_hop_via_label, - 'mr_next_hop_out_label_stack' : next_hop_out_label_stack }) + {'mr_label': label, + 'mr_eos': eos, + 'mr_table_id': table_id, + 'mr_classify_table_index': classify_table_index, + 'mr_create_table_if_needed': create_vrf_if_needed, + 'mr_is_add': is_add, + 'mr_is_classify': is_classify, + 'mr_is_multipath': is_multipath, + 'mr_is_resolve_host': is_resolve_host, + 'mr_is_resolve_attached': is_resolve_attached, + 'mr_next_hop_proto_is_ip4': next_hop_proto_is_ip4, + 'mr_next_hop_weight': next_hop_weight, + 'mr_next_hop': next_hop_address, + 'mr_next_hop_n_out_labels': next_hop_n_out_labels, + 'mr_next_hop_sw_if_index': next_hop_sw_if_index, + 'mr_next_hop_table_id': next_hop_table_id, + 'mr_next_hop_via_label': next_hop_via_label, + 'mr_next_hop_out_label_stack': next_hop_out_label_stack}) def mpls_ip_bind_unbind( self, @@ -648,14 +688,14 @@ class VppPapiProvider(object): """ return self.api( self.papi.mpls_ip_bind_unbind, - {'mb_mpls_table_id' : table_id, - 'mb_label' : label, - 'mb_ip_table_id' : ip_table_id, - 'mb_create_table_if_needed' : create_vrf_if_needed, - 'mb_is_bind' : is_bind, - 'mb_is_ip4' : is_ip4, - 'mb_address_length' : dst_address_length, - 'mb_address' : dst_address}) + {'mb_mpls_table_id': table_id, + 'mb_label': label, + 'mb_ip_table_id': ip_table_id, + 'mb_create_table_if_needed': create_vrf_if_needed, + 'mb_is_bind': is_bind, + 'mb_is_ip4': is_ip4, + 'mb_address_length': dst_address_length, + 'mb_address': dst_address}) def mpls_tunnel_add_del( self, @@ -665,9 +705,9 @@ class VppPapiProvider(object): next_hop_sw_if_index=0xFFFFFFFF, next_hop_table_id=0, next_hop_weight=1, - next_hop_n_out_labels = 0, - next_hop_out_label_stack = [], - next_hop_via_label = MPLS_LABEL_INVALID, + next_hop_n_out_labels=0, + next_hop_out_label_stack=[], + next_hop_via_label=MPLS_LABEL_INVALID, create_vrf_if_needed=0, is_add=1, l2_only=0): @@ -696,19 +736,16 @@ class VppPapiProvider(object): """ return self.api( self.papi.mpls_tunnel_add_del, - {'mt_sw_if_index' : tun_sw_if_index, - 'mt_is_add' : is_add, - 'mt_l2_only' : l2_only, - 'mt_next_hop_proto_is_ip4' : next_hop_proto_is_ip4, - 'mt_next_hop_weight' : next_hop_weight, - 'mt_next_hop' : next_hop_address, - 'mt_next_hop_n_out_labels' : next_hop_n_out_labels, - 'mt_next_hop_sw_if_index' :next_hop_sw_if_index, - 'mt_next_hop_table_id' : next_hop_table_id, - 'mt_next_hop_out_label_stack' : next_hop_out_label_stack }) - - return self.api(vpp_papi.sw_interface_span_enable_disable, - (sw_if_index_from, sw_if_index_to, enable)) + {'mt_sw_if_index': tun_sw_if_index, + 'mt_is_add': is_add, + 'mt_l2_only': l2_only, + 'mt_next_hop_proto_is_ip4': next_hop_proto_is_ip4, + 'mt_next_hop_weight': next_hop_weight, + 'mt_next_hop': next_hop_address, + 'mt_next_hop_n_out_labels': next_hop_n_out_labels, + 'mt_next_hop_sw_if_index': next_hop_sw_if_index, + 'mt_next_hop_table_id': next_hop_table_id, + 'mt_next_hop_out_label_stack': next_hop_out_label_stack}) def snat_interface_add_del_feature( self, @@ -723,9 +760,9 @@ class VppPapiProvider(object): """ return self.api( self.papi.snat_interface_add_del_feature, - {'is_add' : is_add, - 'is_inside' : is_inside, - 'sw_if_index' : sw_if_index}) + {'is_add': is_add, + 'is_inside': is_inside, + 'sw_if_index': sw_if_index}) def snat_add_static_mapping( self, @@ -750,14 +787,14 @@ class VppPapiProvider(object): """ return self.api( self.papi.snat_add_static_mapping, - {'is_add' : is_add, - 'is_ip4' : is_ip4, - 'addr_only' : addr_only, - 'local_ip_address' : local_ip, - 'external_ip_address' : external_ip, - 'local_port' : local_port, - 'external_port' : external_port, - 'vrf_id' : vrf_id}) + {'is_add': is_add, + 'is_ip4': is_ip4, + 'addr_only': addr_only, + 'local_ip_address': local_ip, + 'external_ip_address': external_ip, + 'local_port': local_port, + 'external_port': external_port, + 'vrf_id': vrf_id}) def snat_add_address_range( self, @@ -774,10 +811,10 @@ class VppPapiProvider(object): """ return self.api( self.papi.snat_add_address_range, - {'is_ip4' : is_ip4, - 'first_ip_address' : first_ip_address, - 'last_ip_address' : last_ip_address, - 'is_add' : is_add}) + {'is_ip4': is_ip4, + 'first_ip_address': first_ip_address, + 'last_ip_address': last_ip_address, + 'is_add': is_add}) def snat_address_dump(self): """Dump S-NAT addresses @@ -796,3 +833,43 @@ class VppPapiProvider(object): :return: Dictionary of S-NAT static mappings """ return self.api(self.papi.snat_static_mapping_dump, {}) + + def control_ping(self): + self.api(self.papi.control_ping) + + def bfd_udp_add(self, sw_if_index, desired_min_tx, required_min_rx, + detect_mult, local_addr, peer_addr, is_ipv6=0): + return self.api(self.papi.bfd_udp_add, + { + 'sw_if_index': sw_if_index, + 'desired_min_tx': desired_min_tx, + 'required_min_rx': required_min_rx, + 'local_addr': local_addr, + 'peer_addr': peer_addr, + 'is_ipv6': is_ipv6, + 'detect_mult': detect_mult, + }) + + def bfd_udp_del(self, sw_if_index, local_addr, peer_addr, is_ipv6=0): + return self.api(self.papi.bfd_udp_del, + { + 'sw_if_index': sw_if_index, + 'local_addr': local_addr, + 'peer_addr': peer_addr, + 'is_ipv6': is_ipv6, + }) + + def bfd_udp_session_dump(self): + return self.api(self.papi.bfd_udp_session_dump, {}) + + def bfd_session_set_flags(self, bs_idx, admin_up_down): + return self.api(self.papi.bfd_session_set_flags, { + 'bs_index': bs_idx, + 'admin_up_down': admin_up_down, + }) + + def want_bfd_events(self, enable_disable=1): + return self.api(self.papi.want_bfd_events, { + 'enable_disable': enable_disable, + 'pid': os.getpid(), + }) diff --git a/test/vpp_pg_interface.py b/test/vpp_pg_interface.py index 533c4603..012f5768 100644 --- a/test/vpp_pg_interface.py +++ b/test/vpp_pg_interface.py @@ -1,10 +1,10 @@ import os import time -from scapy.utils import wrpcap, rdpcap +from scapy.utils import wrpcap, rdpcap, PcapReader from vpp_interface import VppInterface from scapy.layers.l2 import Ether, ARP -from scapy.layers.inet6 import IPv6, ICMPv6ND_NS, ICMPv6ND_NA, \ +from scapy.layers.inet6 import IPv6, ICMPv6ND_NS, ICMPv6ND_NA,\ ICMPv6NDOptSrcLLAddr, ICMPv6NDOptDstLLAddr from util import ppp @@ -93,6 +93,7 @@ class VppPGInterface(VppInterface): 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): """ @@ -132,6 +133,41 @@ class VppPGInterface(VppInterface): return [] return output + def wait_for_packet(self, timeout): + """ + 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 + """ + limit = time.time() + timeout + if self._pcap_reader is None: + self.test.logger.debug("Waiting for the capture file to appear") + while time.time() < limit: + 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() - (limit - timeout))) + self._pcap_reader = PcapReader(self.out_path) + else: + self.test.logger.debug("Timeout - capture file still nowhere") + raise Exception("Packet didn't arrive within timeout") + + self.test.logger.debug("Waiting for packet") + while time.time() < limit: + p = self._pcap_reader.recv() + if p is not None: + self.test.logger.debug("Packet received after %fs", + (time.time() - (limit - timeout))) + return p + time.sleep(0) # yield + self.test.logger.debug("Timeout - no packets received") + raise Exception("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) / |