aboutsummaryrefslogtreecommitdiffstats
path: root/test
diff options
context:
space:
mode:
authorSachin Saxena <sachin.saxena@freescale.com>2018-02-28 20:28:52 +0530
committerSachin Saxena <sachin.saxena@nxp.com>2018-02-28 20:34:56 +0530
commit0689fce93ba269c48f83a2f70f971b3976d04c90 (patch)
tree4cc2908df3598507cc1828ac19d8c43b22450ffa /test
parent746b57564deede624261ab8a96c94f562f24d22c (diff)
parentd594711a5d79859a7d0bde83a516f7ab52051d9b (diff)
Merge branch 'stable/1710' of https://gerrit.fd.io/r/vpp into 17101710
Diffstat (limited to 'test')
-rw-r--r--test/Makefile241
-rw-r--r--test/bfd.py459
-rw-r--r--test/debug.py23
-rwxr-xr-xtest/discover_tests.py57
-rw-r--r--test/doc/Makefile243
-rw-r--r--test/doc/conf.py342
-rw-r--r--test/doc/index.rst11
-rw-r--r--test/doc/indices.rst6
-rw-r--r--test/doc/overview.rst418
-rw-r--r--test/ext/Makefile31
-rw-r--r--test/ext/fake.api.json35
-rw-r--r--test/ext/vapi_c_test.c1168
-rw-r--r--test/ext/vapi_cpp_test.cpp591
-rw-r--r--test/framework.py1017
-rw-r--r--test/hook.py212
-rw-r--r--test/ipfix.py539
-rw-r--r--test/lisp.py326
-rw-r--r--test/log.py73
-rw-r--r--test/patches/scapy-2.3.3/dhcp6-options.patch58
-rw-r--r--test/patches/scapy-2.3.3/gre-layers.patch25
-rw-r--r--test/patches/scapy-2.3.3/inet6.py.patch185
-rw-r--r--test/patches/scapy-2.3.3/mpls.py.patch18
-rw-r--r--test/run_tests.py125
-rw-r--r--test/sanity_import_vpp_papi.py4
-rw-r--r--test/sanity_run_vpp.py32
-rw-r--r--test/scapy_handlers/__init__.py0
-rwxr-xr-xtest/scripts/compress_failed.sh37
-rwxr-xr-xtest/scripts/git_pull_or_clean.sh8
-rwxr-xr-xtest/scripts/run_in_venv_with_cleanup.sh39
-rwxr-xr-xtest/scripts/setsid_wrapper.sh12
-rwxr-xr-xtest/scripts/socket_test.sh855
-rwxr-xr-xtest/scripts/test-loop.sh123
-rw-r--r--test/template_bd.py158
-rw-r--r--test/test_acl_plugin.py1313
-rw-r--r--test/test_acl_plugin_conns.py403
-rw-r--r--test/test_acl_plugin_l2l3.py686
-rw-r--r--test/test_acl_plugin_macip.py961
-rw-r--r--test/test_bfd.py2591
-rw-r--r--test/test_classifier.py385
-rw-r--r--test/test_dhcp.py1209
-rw-r--r--test/test_fib.py30
-rw-r--r--test/test_flowprobe.py1021
-rw-r--r--test/test_gre.py814
-rw-r--r--test/test_gtpu.py289
-rw-r--r--test/test_interface_crud.py151
-rw-r--r--test/test_ip4.py1013
-rw-r--r--test/test_ip4_irb.py263
-rw-r--r--test/test_ip4_vrf_multi_instance.py414
-rw-r--r--test/test_ip6.py1510
-rw-r--r--test/test_ip6_vrf_multi_instance.py446
-rw-r--r--test/test_ip_mcast.py760
-rw-r--r--test/test_jvpp.py135
-rw-r--r--test/test_l2_fib.py542
-rw-r--r--test/test_l2bd.py285
-rw-r--r--test/test_l2bd_arp_term.py492
-rw-r--r--test/test_l2bd_multi_instance.py479
-rw-r--r--test/test_l2xc.py226
-rw-r--r--test/test_l2xc_multi_instance.py348
-rw-r--r--test/test_lb.py216
-rw-r--r--test/test_lisp.py166
-rw-r--r--test/test_map.py173
-rw-r--r--test/test_mpls.py1771
-rw-r--r--test/test_nat.py3846
-rw-r--r--test/test_neighbor.py1147
-rw-r--r--test/test_p2p_ethernet.py538
-rw-r--r--test/test_papi.py31
-rw-r--r--test/test_ping.py118
-rw-r--r--test/test_pppoe.py606
-rw-r--r--test/test_span.py481
-rw-r--r--test/test_srv6.py1997
-rw-r--r--test/test_vapi.py106
-rw-r--r--test/test_vtr.py332
-rw-r--r--test/test_vxlan.py234
-rw-r--r--test/test_vxlan_gpe.py313
-rw-r--r--test/util.py231
-rw-r--r--test/vpp_gre_interface.py71
-rw-r--r--test/vpp_interface.py374
-rw-r--r--test/vpp_ip_route.py529
-rw-r--r--test/vpp_lo_interface.py35
-rw-r--r--test/vpp_mpls_tunnel_interface.py48
-rw-r--r--test/vpp_neighbor.py79
-rw-r--r--test/vpp_object.py87
-rw-r--r--test/vpp_papi_provider.py2390
-rw-r--r--test/vpp_pg_interface.py481
-rw-r--r--test/vpp_pppoe_interface.py79
-rw-r--r--test/vpp_srv6.py238
-rw-r--r--test/vpp_sub_interface.py213
87 files changed, 41167 insertions, 0 deletions
diff --git a/test/Makefile b/test/Makefile
new file mode 100644
index 00000000..da77accc
--- /dev/null
+++ b/test/Makefile
@@ -0,0 +1,241 @@
+.PHONY: verify-python-path
+
+VPP_TEST_FAILED_DIR=/tmp/vpp-failed-unittests/
+
+verify-python-path:
+ifndef VPP_PYTHON_PREFIX
+ $(error VPP_PYTHON_PREFIX is not set)
+endif
+
+.PHONY: verify-no-running-vpp
+
+ifdef VPP_ZOMBIE_NOCHECK
+VPP_PIDS=
+else
+VPP_PIDS=$(shell pgrep -d, -x vpp_main)
+endif
+
+ifeq ($(DEBUG),gdb)
+FORCE_FOREGROUND=1
+else ifeq ($(DEBUG),gdbserver)
+FORCE_FOREGROUND=1
+else
+FORCE_FOREGROUND=0
+endif
+
+verify-no-running-vpp:
+ @if [ "$(VPP_PIDS)" != "" ]; then \
+ echo; \
+ echo "*** Existing vpp processes detected (PID(s): $(VPP_PIDS)). Running tests under these conditions is not supported. ***"; \
+ echo; \
+ ps -fp $(VPP_PIDS);\
+ echo; \
+ false; \
+ fi
+
+UNITTEST_EXTRA_OPTS=
+UNITTEST_FAILFAST_OPTS=
+
+ifeq ($(FAILFAST),1)
+UNITTEST_EXTRA_OPTS=-f
+endif
+
+ifneq ($(EXTERN_TESTS),)
+UNITTEST_EXTRA_OPTS=$(UNITTEST_FAILFAST_OPTS) -d $(EXTERN_TESTS)
+endif
+
+PYTHON_VENV_PATH=$(VPP_PYTHON_PREFIX)/virtualenv
+PYTHON_DEPENDS=faulthandler six scapy==2.3.3 pexpect subprocess32 cffi git+https://github.com/klement/py-lispnetworking@setup
+SCAPY_SOURCE=$(shell find $(PYTHON_VENV_PATH) -name site-packages)
+BUILD_COV_DIR=$(BR)/test-cov
+
+GET_PIP_SCRIPT=$(VPP_PYTHON_PREFIX)/get-pip.py
+PIP_INSTALL_DONE=$(VPP_PYTHON_PREFIX)/pip-install.done
+PIP_PATCH_DONE=$(VPP_PYTHON_PREFIX)/pip-patch.done
+PAPI_INSTALL_DONE=$(VPP_PYTHON_PREFIX)/papi-install.done
+
+PAPI_INSTALL_FLAGS=$(PIP_INSTALL_DONE) $(PIP_PATCH_DONE) $(PAPI_INSTALL_DONE)
+
+ifeq ($(PYTHON),)
+PYTHON_INTERP=python2.7
+else
+PYTHON_INTERP=$(PYTHON)
+endif
+
+$(GET_PIP_SCRIPT):
+ @mkdir -p $(VPP_PYTHON_PREFIX)
+ @bash -c "cd $(VPP_PYTHON_PREFIX) && curl -O https://bootstrap.pypa.io/get-pip.py"
+
+$(PIP_INSTALL_DONE): $(GET_PIP_SCRIPT)
+ @virtualenv $(PYTHON_VENV_PATH) -p $(PYTHON_INTERP)
+ @bash -c "source $(PYTHON_VENV_PATH)/bin/activate && python $(GET_PIP_SCRIPT)"
+ @bash -c "source $(PYTHON_VENV_PATH)/bin/activate && pip install $(PYTHON_DEPENDS)"
+ @touch $@
+
+$(PIP_PATCH_DONE): $(PIP_INSTALL_DONE)
+ @echo --- patching ---
+ @sleep 1 # Ensure python recompiles patched *.py files -> *.pyc
+ for f in $(CURDIR)/patches/scapy-2.3.3/*.patch ; do \
+ echo Applying patch: $$(basename $$f) ; \
+ patch -p1 -d $(SCAPY_SOURCE) < $$f ; \
+ done
+ @touch $@
+
+$(PAPI_INSTALL_DONE): $(PIP_PATCH_DONE)
+ @bash -c "source $(PYTHON_VENV_PATH)/bin/activate && cd $(WS_ROOT)/src/vpp-api/python && python setup.py install"
+ @touch $@
+
+define retest-func
+ @env VPP_TEST_FAILED_DIR=$(VPP_TEST_FAILED_DIR) scripts/setsid_wrapper.sh $(FORCE_FOREGROUND) $(PYTHON_VENV_PATH)/bin/activate python run_tests.py -d $(TEST_DIR) $(UNITTEST_EXTRA_OPTS) || env VPP_TEST_FAILED_DIR=$(VPP_TEST_FAILED_DIR) COMPRESS_FAILED_TEST_LOGS=$(COMPRESS_FAILED_TEST_LOGS) scripts/compress_failed.sh
+endef
+
+.PHONY: sanity
+
+sanity: verify-no-running-vpp
+ @bash -c "source $(PYTHON_VENV_PATH)/bin/activate && python sanity_import_vpp_papi.py ||\
+ (echo \"*******************************************************************\" &&\
+ echo \"* Sanity check failed, cannot import vpp_papi\" &&\
+ echo \"* to debug: \" &&\
+ echo \"* 1. enter test shell: make test-shell\" &&\
+ echo \"* 2. execute debugger: gdb python -ex 'run sanity_import_vpp_papi.py'\" &&\
+ echo \"*******************************************************************\" &&\
+ false)"
+ @bash -c "source $(PYTHON_VENV_PATH)/bin/activate && python sanity_run_vpp.py ||\
+ (echo \"*******************************************************************\" &&\
+ echo \"* Sanity check failed, cannot run vpp\" &&\
+ echo \"*******************************************************************\" &&\
+ false)"
+
+.PHONY: ext
+ext:
+ make -C ext
+
+test: verify-python-path $(PAPI_INSTALL_DONE) ext sanity reset
+ $(call retest-func)
+
+retest: verify-python-path sanity reset
+ $(call retest-func)
+
+shell: verify-python-path $(PAPI_INSTALL_DONE)
+ @echo "source $(PYTHON_VENV_PATH)/bin/activate;\
+ echo '***';\
+ echo VPP_TEST_BUILD_DIR=$(VPP_TEST_BUILD_DIR);\
+ echo VPP_TEST_BIN=$(VPP_TEST_BIN);\
+ echo VPP_TEST_PLUGIN_PATH=$(VPP_TEST_PLUGIN_PATH);\
+ echo VPP_TEST_INSTALL_PATH=$(VPP_TEST_INSTALL_PATH);\
+ echo EXTERN_TESTS=$(EXTERN_TESTS);\
+ echo EXTERN_PLUGINS=$(EXTERN_PLUGINS);\
+ echo EXTERN_COV_DIR=$(EXTERN_COV_DIR);\
+ echo LD_LIBRARY_PATH=$(LD_LIBRARY_PATH);\
+ echo '***';\
+ exec </dev/tty" | bash -i
+
+.PHONY: wipe doc
+
+reset:
+ @rm -f /dev/shm/vpp-unittest-*
+ @rm -rf /tmp/vpp-unittest-*
+ @rm -rf $(VPP_TEST_FAILED_DIR)
+ @mkdir $(VPP_TEST_FAILED_DIR)
+
+wipe: reset
+ @make -C ext clean
+ @rm -rf $(PYTHON_VENV_PATH)
+ @rm -f $(PAPI_INSTALL_FLAGS)
+
+doc: verify-python-path $(PIP_PATCH_DONE)
+ @virtualenv $(PYTHON_VENV_PATH) -p $(PYTHON_INTERP)
+ @bash -c "source $(PYTHON_VENV_PATH)/bin/activate && pip install sphinx sphinx-rtd-theme"
+ @bash -c "source $(PYTHON_VENV_PATH)/bin/activate && make -C doc WS_ROOT=$(WS_ROOT) BR=$(BR) NO_VPP_PAPI=1 html"
+
+.PHONY: wipe-doc
+
+wipe-doc:
+ @make -C doc wipe BR=$(BR)
+
+cov: wipe-cov reset verify-python-path $(PAPI_INSTALL_DONE)
+ @lcov --zerocounters --directory $(VPP_TEST_BUILD_DIR)
+ @test -z "$(EXTERN_COV_DIR)" || lcov --zerocounters --directory $(EXTERN_COV_DIR)
+ $(call retest-func)
+ @mkdir $(BUILD_COV_DIR)
+ @lcov --capture --directory $(VPP_TEST_BUILD_DIR) --output-file $(BUILD_COV_DIR)/coverage.info
+ @test -z "$(EXTERN_COV_DIR)" || lcov --capture --directory $(EXTERN_COV_DIR) --output-file $(BUILD_COV_DIR)/extern-coverage.info
+ @genhtml $(BUILD_COV_DIR)/coverage.info --output-directory $(BUILD_COV_DIR)/html
+ @test -z "$(EXTERN_COV_DIR)" || genhtml $(BUILD_COV_DIR)/extern-coverage.info --output-directory $(BUILD_COV_DIR)/extern-html
+ @echo
+ @echo "Build finished. Code coverage report is in $(BUILD_COV_DIR)/html/index.html"
+ @test -z "$(EXTERN_COV_DIR)" || echo "Code coverage report for out-of-tree objects is in $(BUILD_COV_DIR)/extern-html/index.html"
+
+.PHONY: wipe-cov
+
+wipe-cov: wipe
+ @rm -rf $(BUILD_COV_DIR)
+
+.PHONY: checkstyle
+checkstyle: verify-python-path
+ @virtualenv $(PYTHON_VENV_PATH) -p $(PYTHON_INTERP)
+ @bash -c "source $(PYTHON_VENV_PATH)/bin/activate && pip install pep8"
+ @bash -c "source $(PYTHON_VENV_PATH)/bin/activate &&\
+ pep8 --show-source -v $(WS_ROOT)/test/*.py ||\
+ (echo \"*******************************************************************\" &&\
+ echo \"* Test framework PEP8 compliance check FAILED \" &&\
+ echo \"*******************************************************************\" &&\
+ false)"
+ @echo "*******************************************************************"
+ @echo "* Test framework PEP8 compliance check passed"
+ @echo "*******************************************************************"
+
+help:
+ @echo "Running tests:"
+ @echo ""
+ @echo " test - build and run (basic) functional tests"
+ @echo " test-debug - build and run (basic) functional tests (debug build)"
+ @echo " test-all - build and run (all) functional tests"
+ @echo " test-all-debug - build and run (all) functional tests (debug build)"
+ @echo " retest - run functional tests"
+ @echo " retest-debug - run functional tests (debug build)"
+ @echo " test-wipe - wipe (temporary) files generated by unit tests"
+ @echo " test-shell - enter shell with test environment"
+ @echo " test-shell-debug - enter shell with test environment (debug build)"
+ @echo ""
+ @echo "Arguments controlling test runs:"
+ @echo " V=[0|1|2] - set test verbosity level"
+ @echo " FAILFAST=[0|1] - fail fast if 1, complete all tests if 0"
+ @echo " TIMEOUT=<timeout> - fail test suite if any single test takes longer than <timeout> to finish"
+ @echo " DEBUG=<type> - set VPP debugging kind"
+ @echo " DEBUG=core - detect coredump and load it in gdb on crash"
+ @echo " DEBUG=gdb - allow easy debugging by printing VPP PID "
+ @echo " and waiting for user input before running "
+ @echo " and tearing down a testcase"
+ @echo " DEBUG=gdbserver - run gdb inside a gdb server, otherwise "
+ @echo " same as above"
+ @echo " STEP=[yes|no] - ease debugging by stepping through a testcase "
+ @echo " TEST=<filter> - filter the set of tests:"
+ @echo " by file-name - only run tests from specified file, e.g. TEST=test_bfd selects all tests from test_bfd.py"
+ @echo " by file-suffix - same as file-name, but 'test_' is omitted e.g. TEST=bfd selects all tests from test_bfd.py"
+ @echo " by wildcard - wildcard filter is <file>.<class>.<test function>, each can be replaced by '*'"
+ @echo " e.g. TEST='test_bfd.*.*' is equivalent to above example of filter by file-name"
+ @echo " TEST='bfd.*.*' is equivalent to above example of filter by file-suffix"
+ @echo " TEST='bfd.BFDAPITestCase.*' selects all tests from test_bfd.py which are part of BFDAPITestCase class"
+ @echo " TEST='bfd.BFDAPITestCase.test_add_bfd' selects a single test named test_add_bfd from test_bfd.py/BFDAPITestCase"
+ @echo " TEST='*.*.test_add_bfd' selects all test functions named test_add_bfd from all files/classes"
+ @echo ""
+ @echo " VPP_ZOMBIE_NOCHECK=1 - skip checking for vpp (zombie) processes (CAUTION)"
+ @echo " COREDUMP_SIZE=<size> - pass <size> as unix { coredump-size <size> } argument to vpp"
+ @echo " e.g. COREDUMP_SIZE=4g"
+ @echo " COREDUMP_SIZE=unlimited"
+ @echo " EXTERN_TESTS=<path> - path to out-of-tree test_<name>.py files containing test cases"
+ @echo " EXTERN_PLUGINS=<path>- path to out-of-tree plugins to be loaded by vpp under test"
+ @echo " EXTERN_COV_DIR=<path>- path to out-of-tree prefix, where source, object and .gcda files can be found for coverage report"
+ @echo ""
+ @echo "Creating test documentation"
+ @echo " test-doc - generate documentation for test framework"
+ @echo " test-wipe-doc - wipe documentation for test framework"
+ @echo ""
+ @echo "Creating test code coverage report"
+ @echo " test-cov - generate code coverage report for test framework"
+ @echo " test-wipe-cov - wipe code coverage report for test framework"
+ @echo ""
+ @echo "Verifying code-style"
+ @echo " test-checkstyle - check PEP8 compliance"
+ @echo ""
diff --git a/test/bfd.py b/test/bfd.py
new file mode 100644
index 00000000..452a1804
--- /dev/null
+++ b/test/bfd.py
@@ -0,0 +1,459 @@
+""" BFD protocol implementation """
+
+from random import randint
+from socket import AF_INET, AF_INET6, inet_pton
+from scapy.all import bind_layers
+from scapy.layers.inet import UDP
+from scapy.packet import Packet
+from scapy.fields import BitField, BitEnumField, XByteField, FlagsField,\
+ ConditionalField, StrField
+from vpp_object import VppObject
+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 BFDAuthType(NumericConstant):
+ """ BFD Authentication Type """
+ no_auth = 0
+ simple_pwd = 1
+ keyed_md5 = 2
+ meticulous_keyed_md5 = 3
+ keyed_sha1 = 4
+ meticulous_keyed_sha1 = 5
+
+ desc_dict = {
+ no_auth: "No authentication",
+ simple_pwd: "Simple Password",
+ keyed_md5: "Keyed MD5",
+ meticulous_keyed_md5: "Meticulous Keyed MD5",
+ keyed_sha1: "Keyed SHA1",
+ meticulous_keyed_sha1: "Meticulous Keyed SHA1",
+ }
+
+ def __init__(self, value):
+ NumericConstant.__init__(self, value)
+
+
+def bfd_is_auth_used(pkt):
+ """ is packet authenticated? """
+ return "A" in pkt.sprintf("%BFD.flags%")
+
+
+def bfd_is_simple_pwd_used(pkt):
+ """ is simple password authentication used? """
+ return bfd_is_auth_used(pkt) and pkt.auth_type == BFDAuthType.simple_pwd
+
+
+def bfd_is_sha1_used(pkt):
+ """ is sha1 authentication used? """
+ return bfd_is_auth_used(pkt) and pkt.auth_type in \
+ (BFDAuthType.keyed_sha1, BFDAuthType.meticulous_keyed_sha1)
+
+
+def bfd_is_md5_used(pkt):
+ """ is md5 authentication used? """
+ return bfd_is_auth_used(pkt) and pkt.auth_type in \
+ (BFDAuthType.keyed_md5, BFDAuthType.meticulous_keyed_md5)
+
+
+def bfd_is_md5_or_sha1_used(pkt):
+ """ is md5 or sha1 used? """
+ return bfd_is_md5_used(pkt) or bfd_is_sha1_used(pkt)
+
+
+class BFD(Packet):
+ """ BFD protocol layer for scapy """
+
+ udp_dport = 3784 #: BFD destination port per RFC 5881
+ udp_dport_echo = 3785 # : BFD destination port for ECHO 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
+ bfd_pkt_len = 24 # : length of BFD pkt without authentication section
+ sha1_auth_len = 28 # : length of authentication section if SHA1 used
+
+ 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, ['M', 'D', 'A', 'C', 'F', 'P']),
+ XByteField("detect_mult", 0),
+ BitField("length", bfd_pkt_len, 8),
+ 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),
+ ConditionalField(
+ BitEnumField("auth_type", 0, 8, BFDAuthType.desc_dict),
+ bfd_is_auth_used),
+ ConditionalField(BitField("auth_len", 0, 8), bfd_is_auth_used),
+ ConditionalField(BitField("auth_key_id", 0, 8), bfd_is_auth_used),
+ ConditionalField(BitField("auth_reserved", 0, 8),
+ bfd_is_md5_or_sha1_used),
+ ConditionalField(
+ BitField("auth_seq_num", 0, 32), bfd_is_md5_or_sha1_used),
+ ConditionalField(StrField("auth_key_hash", "0" * 16), bfd_is_md5_used),
+ ConditionalField(
+ StrField("auth_key_hash", "0" * 20), bfd_is_sha1_used),
+ ]
+
+ 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 BFD_vpp_echo(Packet):
+ """ BFD echo packet as used by VPP (non-rfc, as rfc doesn't define one) """
+
+ udp_dport = 3785 #: BFD echo destination port per RFC 5881
+ name = "BFD_VPP_ECHO"
+
+ fields_desc = [
+ BitField("discriminator", 0, 32),
+ BitField("expire_time_clocks", 0, 64),
+ BitField("checksum", 0, 64)
+ ]
+
+ def mysummary(self):
+ return self.sprintf(
+ "BFD_VPP_ECHO(disc=%BFD_VPP_ECHO.discriminator%,"
+ "expire_time_clocks=%BFD_VPP_ECHO.expire_time_clocks%)")
+
+# glue the BFD echo packet class to scapy parser
+bind_layers(UDP, BFD_vpp_echo, dport=BFD_vpp_echo.udp_dport)
+
+
+class VppBFDAuthKey(VppObject):
+ """ Represents BFD authentication key in VPP """
+
+ def __init__(self, test, conf_key_id, auth_type, key):
+ self._test = test
+ self._key = key
+ self._auth_type = auth_type
+ test.assertIn(auth_type, BFDAuthType.desc_dict)
+ self._conf_key_id = conf_key_id
+
+ @property
+ def test(self):
+ """ Test which created this key """
+ return self._test
+
+ @property
+ def auth_type(self):
+ """ Authentication type for this key """
+ return self._auth_type
+
+ @property
+ def key(self):
+ """ key data """
+ return self._key
+
+ @key.setter
+ def key(self, value):
+ self._key = value
+
+ @property
+ def conf_key_id(self):
+ """ configuration key ID """
+ return self._conf_key_id
+
+ def add_vpp_config(self):
+ self.test.vapi.bfd_auth_set_key(
+ self._conf_key_id, self._auth_type, self._key)
+ self._test.registry.register(self, self.test.logger)
+
+ def get_bfd_auth_keys_dump_entry(self):
+ """ get the entry in the auth keys dump corresponding to this key """
+ result = self.test.vapi.bfd_auth_keys_dump()
+ for k in result:
+ if k.conf_key_id == self._conf_key_id:
+ return k
+ return None
+
+ def query_vpp_config(self):
+ return self.get_bfd_auth_keys_dump_entry() is not None
+
+ def remove_vpp_config(self):
+ self.test.vapi.bfd_auth_del_key(self._conf_key_id)
+
+ def object_id(self):
+ return "bfd-auth-key-%s" % self._conf_key_id
+
+ def __str__(self):
+ return self.object_id()
+
+
+class VppBFDUDPSession(VppObject):
+ """ Represents BFD UDP session in VPP """
+
+ def __init__(self, test, interface, peer_addr, local_addr=None, af=AF_INET,
+ desired_min_tx=300000, required_min_rx=300000, detect_mult=3,
+ sha1_key=None, bfd_key_id=None):
+ self._test = test
+ self._interface = interface
+ self._af = af
+ self._local_addr = local_addr
+ if local_addr is not None:
+ self._local_addr_n = inet_pton(af, local_addr)
+ else:
+ self._local_addr_n = None
+ self._peer_addr = peer_addr
+ self._peer_addr_n = inet_pton(af, peer_addr)
+ self._desired_min_tx = desired_min_tx
+ self._required_min_rx = required_min_rx
+ self._detect_mult = detect_mult
+ self._sha1_key = sha1_key
+ if bfd_key_id is not None:
+ self._bfd_key_id = bfd_key_id
+ else:
+ self._bfd_key_id = randint(0, 255)
+
+ @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 local_addr(self):
+ """ BFD session local address (VPP address) """
+ if self._local_addr is None:
+ if self.af == AF_INET:
+ return self._interface.local_ip4
+ elif self.af == AF_INET6:
+ return self._interface.local_ip6
+ else:
+ raise Exception("Unexpected af '%s'" % self.af)
+ 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:
+ if self.af == AF_INET:
+ return self._interface.local_ip4n
+ elif self.af == AF_INET6:
+ return self._interface.local_ip6n
+ else:
+ raise Exception("Unexpected af '%s'" % self.af)
+ 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
+
+ def get_bfd_udp_session_dump_entry(self):
+ """ get the namedtuple entry from bfd udp session dump """
+ result = self.test.vapi.bfd_udp_session_dump()
+ for s in result:
+ self.test.logger.debug("session entry: %s" % str(s))
+ 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]:
+ return s
+ if self.af == AF_INET6 \
+ and s.is_ipv6 == 1 \
+ and self.interface.local_ip6n == s.local_addr \
+ and self.interface.remote_ip6n == s.peer_addr:
+ return s
+ return None
+
+ @property
+ def state(self):
+ """ BFD session state """
+ session = self.get_bfd_udp_session_dump_entry()
+ if session is None:
+ raise Exception("Could not find BFD session in VPP response")
+ return session.state
+
+ @property
+ def desired_min_tx(self):
+ """ desired minimum tx interval """
+ return self._desired_min_tx
+
+ @property
+ def required_min_rx(self):
+ """ required minimum rx interval """
+ return self._required_min_rx
+
+ @property
+ def detect_mult(self):
+ """ detect multiplier """
+ return self._detect_mult
+
+ @property
+ def sha1_key(self):
+ """ sha1 key """
+ return self._sha1_key
+
+ @property
+ def bfd_key_id(self):
+ """ bfd key id in use """
+ return self._bfd_key_id
+
+ def activate_auth(self, key, bfd_key_id=None, delayed=False):
+ """ activate authentication for this session """
+ self._bfd_key_id = bfd_key_id if bfd_key_id else randint(0, 255)
+ self._sha1_key = key
+ is_ipv6 = 1 if AF_INET6 == self.af else 0
+ conf_key_id = self._sha1_key.conf_key_id
+ is_delayed = 1 if delayed else 0
+ self.test.vapi.bfd_udp_auth_activate(self._interface.sw_if_index,
+ self.local_addr_n,
+ self.peer_addr_n,
+ is_ipv6=is_ipv6,
+ bfd_key_id=self._bfd_key_id,
+ conf_key_id=conf_key_id,
+ is_delayed=is_delayed)
+
+ def deactivate_auth(self, delayed=False):
+ """ deactivate authentication """
+ self._bfd_key_id = None
+ self._sha1_key = None
+ is_delayed = 1 if delayed else 0
+ is_ipv6 = 1 if AF_INET6 == self.af else 0
+ self.test.vapi.bfd_udp_auth_deactivate(self._interface.sw_if_index,
+ self.local_addr_n,
+ self.peer_addr_n,
+ is_ipv6=is_ipv6,
+ is_delayed=is_delayed)
+
+ def modify_parameters(self,
+ detect_mult=None,
+ desired_min_tx=None,
+ required_min_rx=None):
+ """ modify session parameters """
+ if detect_mult:
+ self._detect_mult = detect_mult
+ if desired_min_tx:
+ self._desired_min_tx = desired_min_tx
+ if required_min_rx:
+ self._required_min_rx = required_min_rx
+ is_ipv6 = 1 if AF_INET6 == self.af else 0
+ self.test.vapi.bfd_udp_mod(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)
+
+ def add_vpp_config(self):
+ is_ipv6 = 1 if AF_INET6 == self.af else 0
+ bfd_key_id = self._bfd_key_id if self._sha1_key else None
+ conf_key_id = self._sha1_key.conf_key_id if self._sha1_key else None
+ 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,
+ bfd_key_id=bfd_key_id,
+ conf_key_id=conf_key_id)
+ self._test.registry.register(self, self.test.logger)
+
+ def query_vpp_config(self):
+ session = self.get_bfd_udp_session_dump_entry()
+ return session is not None
+
+ def remove_vpp_config(self):
+ 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-%s-%s-%s-%s" % (self._interface.sw_if_index,
+ self.local_addr,
+ self.peer_addr,
+ self.af)
+
+ def __str__(self):
+ return self.object_id()
+
+ def admin_up(self):
+ """ set bfd session admin-up """
+ is_ipv6 = 1 if AF_INET6 == self._af else 0
+ self.test.vapi.bfd_udp_session_set_flags(1,
+ self._interface.sw_if_index,
+ self.local_addr_n,
+ self.peer_addr_n,
+ is_ipv6=is_ipv6)
+
+ def admin_down(self):
+ """ set bfd session admin-down """
+ is_ipv6 = 1 if AF_INET6 == self._af else 0
+ self.test.vapi.bfd_udp_session_set_flags(0,
+ self._interface.sw_if_index,
+ self.local_addr_n,
+ self.peer_addr_n,
+ is_ipv6=is_ipv6)
diff --git a/test/debug.py b/test/debug.py
new file mode 100644
index 00000000..4516b8c1
--- /dev/null
+++ b/test/debug.py
@@ -0,0 +1,23 @@
+""" debug utilities """
+
+import os
+import pexpect
+
+gdb_path = '/usr/bin/gdb'
+
+
+def spawn_gdb(binary_path, core_path, logger):
+ if os.path.isfile(gdb_path) and os.access(gdb_path, os.X_OK):
+ # automatically attach gdb
+ gdb_cmdline = "%s %s %s" % (gdb_path, binary_path, core_path)
+ gdb = pexpect.spawn(gdb_cmdline)
+ gdb.interact()
+ try:
+ gdb.terminate(True)
+ except:
+ pass
+ if gdb.isalive():
+ raise Exception("GDB refused to die...")
+ else:
+ logger.error("Debugger '%s' does not exist or is not an "
+ "executable.." % gdb_path)
diff --git a/test/discover_tests.py b/test/discover_tests.py
new file mode 100755
index 00000000..eea59410
--- /dev/null
+++ b/test/discover_tests.py
@@ -0,0 +1,57 @@
+#!/usr/bin/env python
+
+import sys
+import os
+import unittest
+import importlib
+import argparse
+
+
+def discover_tests(directory, callback):
+ do_insert = True
+ for _f in os.listdir(directory):
+ f = "%s/%s" % (directory, _f)
+ if os.path.isdir(f):
+ discover_tests(f, callback)
+ continue
+ if not os.path.isfile(f):
+ continue
+ if do_insert:
+ sys.path.insert(0, directory)
+ do_insert = False
+ if not _f.startswith("test_") or not _f.endswith(".py"):
+ continue
+ name = "".join(f.split("/")[-1].split(".")[:-1])
+ if name in sys.modules:
+ raise Exception("Duplicate test module `%s' found!" % name)
+ module = importlib.import_module(name)
+ for name, cls in module.__dict__.items():
+ if not isinstance(cls, type):
+ continue
+ if not issubclass(cls, unittest.TestCase):
+ continue
+ if name == "VppTestCase":
+ continue
+ for method in dir(cls):
+ if not callable(getattr(cls, method)):
+ continue
+ if method.startswith("test_"):
+ callback(_f, cls, method)
+
+
+def print_callback(file_name, cls, method):
+ print("%s.%s.%s" % (file_name, cls.__name__, method))
+
+
+if __name__ == '__main__':
+ parser = argparse.ArgumentParser(description="Discover VPP unit tests")
+ parser.add_argument("-d", "--dir", action='append', type=str,
+ help="directory containing test files "
+ "(may be specified multiple times)")
+ args = parser.parse_args()
+ if args.dir is None:
+ args.dir = "."
+
+ suite = unittest.TestSuite()
+ for d in args.dir:
+ discover_tests(d, print_callback)
diff --git a/test/doc/Makefile b/test/doc/Makefile
new file mode 100644
index 00000000..ff96b5fd
--- /dev/null
+++ b/test/doc/Makefile
@@ -0,0 +1,243 @@
+# Makefile for Sphinx documentation
+#
+
+# You can set these variables from the command line.
+SPHINXOPTS =
+SRC_DOC_DIR = $(WS_ROOT)/test/doc
+SPHINXBUILD = sphinx-build
+PAPER =
+BUILD_DOC_ROOT = $(BR)/test-doc
+BUILD_DOC_DIR = $(BUILD_DOC_ROOT)/build
+API_DOC_GEN_DIR = $(BUILD_DOC_ROOT)/apidoc
+
+# Internal variables.
+PAPEROPT_a4 = -D latex_paper_size=a4
+PAPEROPT_letter = -D latex_paper_size=letter
+ALLSPHINXOPTS = -d $(BUILD_DOC_DIR)/.sphinx-cache $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) $(API_DOC_GEN_DIR) -c $(SRC_DOC_DIR)
+# the i18n builder cannot share the environment and doctrees with the others
+I18NSPHINXOPTS = $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) .
+IN_VENV:=$(shell if pip -V | grep "virtualenv" 2>&1 > /dev/null; then echo 1; else echo 0; fi)
+
+.PHONY: verify-virtualenv
+verify-virtualenv:
+ifeq ($(IN_VENV),0)
+ $(error "Not running inside virtualenv (are you running 'make test-doc' from root?)")
+endif
+
+.PHONY: regen-api-doc
+regen-api-doc: verify-virtualenv
+ @mkdir -p $(API_DOC_GEN_DIR)
+ @cp $(SRC_DOC_DIR)/index.rst $(API_DOC_GEN_DIR)
+ @cp $(SRC_DOC_DIR)/indices.rst $(API_DOC_GEN_DIR)
+ @cp $(SRC_DOC_DIR)/overview.rst $(API_DOC_GEN_DIR)
+ sphinx-apidoc -o $(API_DOC_GEN_DIR) -H "Module documentation" ..
+
+.PHONY: help
+help:
+ @echo "Please use \`make <target>' where <target> is one of"
+ @echo " html to make standalone HTML files"
+ @echo " dirhtml to make HTML files named index.html in directories"
+ @echo " singlehtml to make a single large HTML file"
+ @echo " pickle to make pickle files"
+ @echo " json to make JSON files"
+ @echo " htmlhelp to make HTML files and a HTML help project"
+ @echo " qthelp to make HTML files and a qthelp project"
+ @echo " applehelp to make an Apple Help Book"
+ @echo " devhelp to make HTML files and a Devhelp project"
+ @echo " epub to make an epub"
+ @echo " epub3 to make an epub3"
+ @echo " latex to make LaTeX files, you can set PAPER=a4 or PAPER=letter"
+ @echo " latexpdf to make LaTeX files and run them through pdflatex"
+ @echo " latexpdfja to make LaTeX files and run them through platex/dvipdfmx"
+ @echo " text to make text files"
+ @echo " man to make manual pages"
+ @echo " texinfo to make Texinfo files"
+ @echo " info to make Texinfo files and run them through makeinfo"
+ @echo " gettext to make PO message catalogs"
+ @echo " changes to make an overview of all changed/added/deprecated items"
+ @echo " xml to make Docutils-native XML files"
+ @echo " pseudoxml to make pseudoxml-XML files for display purposes"
+ @echo " linkcheck to check all external links for integrity"
+ @echo " doctest to run all doctests embedded in the documentation (if enabled)"
+ @echo " coverage to run coverage check of the documentation (if enabled)"
+ @echo " dummy to check syntax errors of document sources"
+
+.PHONY: wipe
+wipe:
+ rm -rf $(BUILD_DOC_ROOT)
+
+.PHONY: html
+html: regen-api-doc verify-virtualenv
+ $(SPHINXBUILD) -b html $(ALLSPHINXOPTS) $(BUILD_DOC_DIR)/html
+ @echo
+ @echo "Build finished. The HTML pages are in $(BUILD_DOC_DIR)/html."
+
+.PHONY: dirhtml
+dirhtml: regen-api-doc verify-virtualenv
+ $(SPHINXBUILD) -b dirhtml $(ALLSPHINXOPTS) $(BUILD_DOC_DIR)/dirhtml
+ @echo
+ @echo "Build finished. The HTML pages are in $(BUILD_DOC_DIR)/dirhtml."
+
+.PHONY: singlehtml
+singlehtml: regen-api-doc verify-virtualenv
+ $(SPHINXBUILD) -b singlehtml $(ALLSPHINXOPTS) $(BUILD_DOC_DIR)/singlehtml
+ @echo
+ @echo "Build finished. The HTML page is in $(BUILD_DOC_DIR)/singlehtml."
+
+.PHONY: pickle
+pickle: regen-api-doc verify-virtualenv
+ $(SPHINXBUILD) -b pickle $(ALLSPHINXOPTS) $(BUILD_DOC_DIR)/pickle
+ @echo
+ @echo "Build finished; now you can process the pickle files."
+
+.PHONY: json
+json: regen-api-doc verify-virtualenv
+ $(SPHINXBUILD) -b json $(ALLSPHINXOPTS) $(BUILD_DOC_DIR)/json
+ @echo
+ @echo "Build finished; now you can process the JSON files."
+
+.PHONY: htmlhelp
+htmlhelp: regen-api-doc verify-virtualenv
+ $(SPHINXBUILD) -b htmlhelp $(ALLSPHINXOPTS) $(BUILD_DOC_DIR)/htmlhelp
+ @echo
+ @echo "Build finished; now you can run HTML Help Workshop with the" \
+ ".hhp project file in $(BUILD_DOC_DIR)/htmlhelp."
+
+.PHONY: qthelp
+qthelp: regen-api-doc verify-virtualenv
+ $(SPHINXBUILD) -b qthelp $(ALLSPHINXOPTS) $(BUILD_DOC_DIR)/qthelp
+ @echo
+ @echo "Build finished; now you can run "qcollectiongenerator" with the" \
+ ".qhcp project file in $(BUILD_DOC_DIR)/qthelp, like this:"
+ @echo "# qcollectiongenerator $(BUILD_DOC_DIR)/qthelp/VPPtestframework.qhcp"
+ @echo "To view the help file:"
+ @echo "# assistant -collectionFile $(BUILD_DOC_DIR)/qthelp/VPPtestframework.qhc"
+
+.PHONY: applehelp
+applehelp: regen-api-doc verify-virtualenv
+ $(SPHINXBUILD) -b applehelp $(ALLSPHINXOPTS) $(BUILD_DOC_DIR)/applehelp
+ @echo
+ @echo "Build finished. The help book is in $(BUILD_DOC_DIR)/applehelp."
+ @echo "N.B. You won't be able to view it unless you put it in" \
+ "~/Library/Documentation/Help or install it in your application" \
+ "bundle."
+
+.PHONY: devhelp
+devhelp: regen-api-doc verify-virtualenv
+ $(SPHINXBUILD) -b devhelp $(ALLSPHINXOPTS) $(BUILD_DOC_DIR)/devhelp
+ @echo
+ @echo "Build finished."
+ @echo "To view the help file:"
+ @echo "# mkdir -p $$HOME/.local/share/devhelp/VPPtestframework"
+ @echo "# ln -s $(BUILD_DOC_DIR)/devhelp $$HOME/.local/share/devhelp/VPPtestframework"
+ @echo "# devhelp"
+
+.PHONY: epub
+epub: regen-api-doc verify-virtualenv
+ $(SPHINXBUILD) -b epub $(ALLSPHINXOPTS) $(BUILD_DOC_DIR)/epub
+ @echo
+ @echo "Build finished. The epub file is in $(BUILD_DOC_DIR)/epub."
+
+.PHONY: epub3
+epub3:
+ $(SPHINXBUILD) -b epub3 $(ALLSPHINXOPTS) $(BUILD_DOC_DIR)/epub3
+ @echo
+ @echo "Build finished. The epub3 file is in $(BUILD_DOC_DIR)/epub3."
+
+.PHONY: latex
+latex: regen-api-doc verify-virtualenv
+ $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILD_DOC_DIR)/latex
+ @echo
+ @echo "Build finished; the LaTeX files are in $(BUILD_DOC_DIR)/latex."
+ @echo "Run \`make' in that directory to run these through (pdf)latex" \
+ "(use \`make latexpdf' here to do that automatically)."
+
+.PHONY: latexpdf
+latexpdf: regen-api-doc verify-virtualenv
+ $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILD_DOC_DIR)/latex
+ @echo "Running LaTeX files through pdflatex..."
+ $(MAKE) -C $(BUILD_DOC_DIR)/latex all-pdf
+ @echo "pdflatex finished; the PDF files are in $(BUILD_DOC_DIR)/latex."
+
+.PHONY: latexpdfja
+latexpdfja: regen-api-doc verify-virtualenv
+ $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILD_DOC_DIR)/latex
+ @echo "Running LaTeX files through platex and dvipdfmx..."
+ $(MAKE) -C $(BUILD_DOC_DIR)/latex all-pdf-ja
+ @echo "pdflatex finished; the PDF files are in $(BUILD_DOC_DIR)/latex."
+
+.PHONY: text
+text: regen-api-doc verify-virtualenv
+ $(SPHINXBUILD) -b text $(ALLSPHINXOPTS) $(BUILD_DOC_DIR)/text
+ @echo
+ @echo "Build finished. The text files are in $(BUILD_DOC_DIR)/text."
+
+.PHONY: man
+man: regen-api-doc verify-virtualenv
+ $(SPHINXBUILD) -b man $(ALLSPHINXOPTS) $(BUILD_DOC_DIR)/man
+ @echo
+ @echo "Build finished. The manual pages are in $(BUILD_DOC_DIR)/man."
+
+.PHONY: texinfo
+texinfo: regen-api-doc verify-virtualenv
+ $(SPHINXBUILD) -b texinfo $(ALLSPHINXOPTS) $(BUILD_DOC_DIR)/texinfo
+ @echo
+ @echo "Build finished. The Texinfo files are in $(BUILD_DOC_DIR)/texinfo."
+ @echo "Run \`make' in that directory to run these through makeinfo" \
+ "(use \`make info' here to do that automatically)."
+
+.PHONY: info
+info: regen-api-doc verify-virtualenv
+ $(SPHINXBUILD) -b texinfo $(ALLSPHINXOPTS) $(BUILD_DOC_DIR)/texinfo
+ @echo "Running Texinfo files through makeinfo..."
+ make -C $(BUILD_DOC_DIR)/texinfo info
+ @echo "makeinfo finished; the Info files are in $(BUILD_DOC_DIR)/texinfo."
+
+.PHONY: gettext
+gettext: regen-api-doc verify-virtualenv
+ $(SPHINXBUILD) -b gettext $(I18NSPHINXOPTS) $(BUILD_DOC_DIR)/locale
+ @echo
+ @echo "Build finished. The message catalogs are in $(BUILD_DOC_DIR)/locale."
+
+.PHONY: changes
+changes: regen-api-doc verify-virtualenv
+ $(SPHINXBUILD) -b changes $(ALLSPHINXOPTS) $(BUILD_DOC_DIR)/changes
+ @echo
+ @echo "The overview file is in $(BUILD_DOC_DIR)/changes."
+
+.PHONY: linkcheck
+linkcheck: regen-api-doc verify-virtualenv
+ $(SPHINXBUILD) -b linkcheck $(ALLSPHINXOPTS) $(BUILD_DOC_DIR)/linkcheck
+ @echo
+ @echo "Link check complete; look for any errors in the above output " \
+ "or in $(BUILD_DOC_DIR)/linkcheck/output.txt."
+
+.PHONY: doctest
+doctest: regen-api-doc verify-virtualenv
+ $(SPHINXBUILD) -b doctest $(ALLSPHINXOPTS) $(BUILD_DOC_DIR)/doctest
+ @echo "Testing of doctests in the sources finished, look at the " \
+ "results in $(BUILD_DOC_DIR)/doctest/output.txt."
+
+.PHONY: coverage
+coverage: regen-api-doc verify-virtualenv
+ $(SPHINXBUILD) -b coverage $(ALLSPHINXOPTS) $(BUILD_DOC_DIR)/coverage
+ @echo "Testing of coverage in the sources finished, look at the " \
+ "results in $(BUILD_DOC_DIR)/coverage/python.txt."
+
+.PHONY: xml
+xml: regen-api-doc verify-virtualenv
+ $(SPHINXBUILD) -b xml $(ALLSPHINXOPTS) $(BUILD_DOC_DIR)/xml
+ @echo
+ @echo "Build finished. The XML files are in $(BUILD_DOC_DIR)/xml."
+
+.PHONY: pseudoxml
+pseudoxml: regen-api-doc verify-virtualenv
+ $(SPHINXBUILD) -b pseudoxml $(ALLSPHINXOPTS) $(BUILD_DOC_DIR)/pseudoxml
+ @echo
+ @echo "Build finished. The pseudo-XML files are in $(BUILD_DOC_DIR)/pseudoxml."
+
+.PHONY: dummy
+dummy: regen-api-doc verify-virtualenv
+ $(SPHINXBUILD) -b dummy $(ALLSPHINXOPTS) $(BUILD_DOC_DIR)/dummy
+ @echo
+ @echo "Build finished. Dummy builder generates no files."
diff --git a/test/doc/conf.py b/test/doc/conf.py
new file mode 100644
index 00000000..ec8958ea
--- /dev/null
+++ b/test/doc/conf.py
@@ -0,0 +1,342 @@
+# -*- coding: utf-8 -*-
+#
+# VPP test framework documentation build configuration file, created by
+# sphinx-quickstart on Thu Oct 13 08:45:03 2016.
+#
+# This file is execfile()d with the current directory set to its
+# containing dir.
+#
+# Note that not all possible configuration values are present in this
+# autogenerated file.
+#
+# All configuration values have a default; values that are commented out
+# serve to show the default.
+
+# If extensions (or modules to document with autodoc) are in another directory,
+# add these directories to sys.path here. If the directory is relative to the
+# documentation root, use os.path.abspath to make it absolute, like shown here.
+#
+import os
+import sys
+sys.path.insert(0, os.path.abspath('..'))
+
+
+# -- General configuration ------------------------------------------------
+
+# If your documentation needs a minimal Sphinx version, state it here.
+#
+# needs_sphinx = '1.0'
+
+# Add any Sphinx extension module names here, as strings. They can be
+# extensions coming with Sphinx (named 'sphinx.ext.*') or your custom
+# ones.
+extensions = [
+ 'sphinx.ext.autodoc',
+]
+
+# Add any paths that contain templates here, relative to this directory.
+templates_path = ['_templates']
+
+# The suffix(es) of source filenames.
+# You can specify multiple suffix as a list of string:
+#
+# source_suffix = ['.rst', '.md']
+source_suffix = '.rst'
+
+# The encoding of source files.
+#
+# source_encoding = 'utf-8-sig'
+
+# The master toctree document.
+master_doc = 'index'
+
+# General information about the project.
+project = u'VPP test framework'
+copyright = u'2016, VPP team'
+author = u'VPP team'
+
+# The version info for the project you're documenting, acts as replacement for
+# |version| and |release|, also used in various other places throughout the
+# built documents.
+#
+# The short X.Y version.
+version = u'0.1'
+# The full version, including alpha/beta/rc tags.
+release = u'0.1'
+
+# The language for content autogenerated by Sphinx. Refer to documentation
+# for a list of supported languages.
+#
+# This is also used if you do content translation via gettext catalogs.
+# Usually you set "language" from the command line for these cases.
+language = None
+
+# There are two options for replacing |today|: either, you set today to some
+# non-false value, then it is used:
+#
+# today = ''
+#
+# Else, today_fmt is used as the format for a strftime call.
+#
+# today_fmt = '%B %d, %Y'
+
+# List of patterns, relative to source directory, that match files and
+# directories to ignore when looking for source files.
+# This patterns also effect to html_static_path and html_extra_path
+exclude_patterns = ['_build', 'Thumbs.db', '.DS_Store']
+
+# The reST default role (used for this markup: `text`) to use for all
+# documents.
+#
+default_role = 'any'
+
+# If true, '()' will be appended to :func: etc. cross-reference text.
+#
+add_function_parentheses = True
+
+# If true, the current module name will be prepended to all description
+# unit titles (such as .. function::).
+#
+# add_module_names = True
+
+# If true, sectionauthor and moduleauthor directives will be shown in the
+# output. They are ignored by default.
+#
+# show_authors = False
+
+# The name of the Pygments (syntax highlighting) style to use.
+pygments_style = 'sphinx'
+
+# A list of ignored prefixes for module index sorting.
+# modindex_common_prefix = []
+
+# If true, keep warnings as "system message" paragraphs in the built documents.
+# keep_warnings = False
+
+# If true, `todo` and `todoList` produce output, else they produce nothing.
+todo_include_todos = False
+
+
+# -- Options for HTML output ----------------------------------------------
+
+# The theme to use for HTML and HTML Help pages. See the documentation for
+# a list of builtin themes.
+#
+#html_theme = 'alabaster'
+html_theme = 'sphinx_rtd_theme'
+
+# Theme options are theme-specific and customize the look and feel of a theme
+# further. For a list of options available for each theme, see the
+# documentation.
+#
+# html_theme_options = {}
+
+# Add any paths that contain custom themes here, relative to this directory.
+# html_theme_path = []
+
+# The name for this set of Sphinx documents.
+# "<project> v<release> documentation" by default.
+#
+# html_title = u'VPP test framework v0.1'
+
+# A shorter title for the navigation bar. Default is the same as html_title.
+#
+# html_short_title = None
+
+# The name of an image file (relative to this directory) to place at the top
+# of the sidebar.
+#
+# html_logo = None
+
+# The name of an image file (relative to this directory) to use as a favicon of
+# the docs. This file should be a Windows icon file (.ico) being 16x16 or 32x32
+# pixels large.
+#
+# html_favicon = None
+
+# Add any paths that contain custom static files (such as style sheets) here,
+# relative to this directory. They are copied after the builtin static files,
+# so a file named "default.css" will overwrite the builtin "default.css".
+# html_static_path = []
+
+# Add any extra paths that contain custom files (such as robots.txt or
+# .htaccess) here, relative to this directory. These files are copied
+# directly to the root of the documentation.
+#
+# html_extra_path = []
+
+# If not None, a 'Last updated on:' timestamp is inserted at every page
+# bottom, using the given strftime format.
+# The empty string is equivalent to '%b %d, %Y'.
+#
+# html_last_updated_fmt = None
+
+# If true, SmartyPants will be used to convert quotes and dashes to
+# typographically correct entities.
+#
+# html_use_smartypants = True
+
+# Custom sidebar templates, maps document names to template names.
+#
+# html_sidebars = {}
+
+# Additional templates that should be rendered to pages, maps page names to
+# template names.
+#
+# html_additional_pages = {}
+
+# If false, no module index is generated.
+#
+# html_domain_indices = True
+
+# If false, no index is generated.
+#
+# html_use_index = True
+
+# If true, the index is split into individual pages for each letter.
+#
+# html_split_index = False
+
+# If true, links to the reST sources are added to the pages.
+#
+# html_show_sourcelink = True
+
+# If true, "Created using Sphinx" is shown in the HTML footer. Default is True.
+#
+# html_show_sphinx = True
+
+# If true, "(C) Copyright ..." is shown in the HTML footer. Default is True.
+#
+# html_show_copyright = True
+
+# If true, an OpenSearch description file will be output, and all pages will
+# contain a <link> tag referring to it. The value of this option must be the
+# base URL from which the finished HTML is served.
+#
+# html_use_opensearch = ''
+
+# This is the file name suffix for HTML files (e.g. ".xhtml").
+# html_file_suffix = None
+
+# Language to be used for generating the HTML full-text search index.
+# Sphinx supports the following languages:
+# 'da', 'de', 'en', 'es', 'fi', 'fr', 'hu', 'it', 'ja'
+# 'nl', 'no', 'pt', 'ro', 'ru', 'sv', 'tr', 'zh'
+#
+# html_search_language = 'en'
+
+# A dictionary with options for the search language support, empty by default.
+# 'ja' uses this config value.
+# 'zh' user can custom change `jieba` dictionary path.
+#
+# html_search_options = {'type': 'default'}
+
+# The name of a javascript file (relative to the configuration directory) that
+# implements a search results scorer. If empty, the default will be used.
+#
+# html_search_scorer = 'scorer.js'
+
+# Output file base name for HTML help builder.
+htmlhelp_basename = 'VPPtestframeworkdoc'
+
+# -- Options for LaTeX output ---------------------------------------------
+
+latex_elements = {
+ # The paper size ('letterpaper' or 'a4paper').
+ #
+ # 'papersize': 'letterpaper',
+
+ # The font size ('10pt', '11pt' or '12pt').
+ #
+ # 'pointsize': '10pt',
+
+ # Additional stuff for the LaTeX preamble.
+ #
+ # 'preamble': '',
+
+ # Latex figure (float) alignment
+ #
+ # 'figure_align': 'htbp',
+}
+
+# Grouping the document tree into LaTeX files. List of tuples
+# (source start file, target name, title,
+# author, documentclass [howto, manual, or own class]).
+latex_documents = [
+ (master_doc, 'VPPtestframework.tex', u'VPP test framework Documentation',
+ u'VPP team', 'manual'),
+]
+
+# The name of an image file (relative to this directory) to place at the top of
+# the title page.
+#
+# latex_logo = None
+
+# For "manual" documents, if this is true, then toplevel headings are parts,
+# not chapters.
+#
+# latex_use_parts = False
+
+# If true, show page references after internal links.
+#
+# latex_show_pagerefs = False
+
+# If true, show URL addresses after external links.
+#
+# latex_show_urls = False
+
+# Documents to append as an appendix to all manuals.
+#
+# latex_appendices = []
+
+# It false, will not define \strong, \code, itleref, \crossref ... but only
+# \sphinxstrong, ..., \sphinxtitleref, ... To help avoid clash with user added
+# packages.
+#
+# latex_keep_old_macro_names = True
+
+# If false, no module index is generated.
+#
+# latex_domain_indices = True
+
+
+# -- Options for manual page output ---------------------------------------
+
+# One entry per manual page. List of tuples
+# (source start file, name, description, authors, manual section).
+man_pages = [
+ (master_doc, 'vpptestframework', u'VPP test framework Documentation',
+ [author], 1)
+]
+
+# If true, show URL addresses after external links.
+#
+# man_show_urls = False
+
+
+# -- Options for Texinfo output -------------------------------------------
+
+# Grouping the document tree into Texinfo files. List of tuples
+# (source start file, target name, title, author,
+# dir menu entry, description, category)
+texinfo_documents = [
+ (master_doc, 'VPPtestframework', u'VPP test framework Documentation',
+ author, 'VPPtestframework', 'One line description of project.',
+ 'Miscellaneous'),
+]
+
+# Documents to append as an appendix to all manuals.
+#
+# texinfo_appendices = []
+
+# If false, no module index is generated.
+#
+# texinfo_domain_indices = True
+
+# How to display URL addresses: 'footnote', 'no', or 'inline'.
+#
+# texinfo_show_urls = 'footnote'
+
+# If true, do not generate a @detailmenu in the "Top" node's menu.
+#
+# texinfo_no_detailmenu = False
diff --git a/test/doc/index.rst b/test/doc/index.rst
new file mode 100644
index 00000000..62e348cd
--- /dev/null
+++ b/test/doc/index.rst
@@ -0,0 +1,11 @@
+Contents
+========
+
+.. toctree::
+ :numbered:
+ :maxdepth: 2
+ :glob:
+
+ overview
+ modules
+ indices
diff --git a/test/doc/indices.rst b/test/doc/indices.rst
new file mode 100644
index 00000000..d46b839f
--- /dev/null
+++ b/test/doc/indices.rst
@@ -0,0 +1,6 @@
+Indices and tables
+==================
+
+* :ref:`genindex`
+* :ref:`modindex`
+* :ref:`search`
diff --git a/test/doc/overview.rst b/test/doc/overview.rst
new file mode 100644
index 00000000..7b70eded
--- /dev/null
+++ b/test/doc/overview.rst
@@ -0,0 +1,418 @@
+.. _unittest: https://docs.python.org/2/library/unittest.html
+.. _TestCase: https://docs.python.org/2/library/unittest.html#unittest.TestCase
+.. _AssertionError: https://docs.python.org/2/library/exceptions.html#exceptions.AssertionError
+.. _SkipTest: https://docs.python.org/2/library/unittest.html#unittest.SkipTest
+.. _virtualenv: http://docs.python-guide.org/en/latest/dev/virtualenvs/
+.. _scapy: http://www.secdev.org/projects/scapy/
+.. _logging: https://docs.python.org/2/library/logging.html
+
+.. |vtf| replace:: VPP Test Framework
+
+|vtf|
+=====
+
+.. contents::
+ :local:
+ :depth: 1
+
+Overview
+########
+
+The goal of the |vtf| is to ease writing, running and debugging
+unit tests for the VPP. For this, python was chosen as a high level language
+allowing rapid development with scapy_ providing the necessary tool for creating
+and dissecting packets.
+
+Anatomy of a test case
+######################
+
+Python's unittest_ is used as the base framework upon which the VPP test
+framework is built. A test suite in the |vtf| consists of multiple classes
+derived from `VppTestCase`, which is itself derived from TestCase_.
+The test class defines one or more test functions, which act as test cases.
+
+Function flow when running a test case is:
+
+1. `setUpClass <VppTestCase.setUpClass>`:
+ This function is called once for each test class, allowing a one-time test
+ setup to be executed. If this functions throws an exception,
+ none of the test functions are executed.
+2. `setUp <VppTestCase.setUp>`:
+ The setUp function runs before each of the test functions. If this function
+ throws an exception other than AssertionError_ or SkipTest_, then this is
+ considered an error, not a test failure.
+3. *test_<name>*:
+ This is the guts of the test case. It should execute the test scenario
+ and use the various assert functions from the unittest framework to check
+ necessary. Multiple test_<name> methods can exist in a test case.
+4. `tearDown <VppTestCase.tearDown>`:
+ The tearDown function is called after each test function with the purpose
+ of doing partial cleanup.
+5. `tearDownClass <VppTestCase.tearDownClass>`:
+ Method called once after running all of the test functions to perform
+ the final cleanup.
+
+Logging
+#######
+
+Each test case has a logger automatically created for it, stored in
+'logger' property, based on logging_. Use the logger's standard methods
+debug(), info(), error(), ... to emit log messages to the logger.
+
+All the log messages go always into a log file in temporary directory
+(see below).
+
+To control the messages printed to console, specify the V= parameter.
+
+.. code-block:: shell
+
+ make test # minimum verbosity
+ make test V=1 # moderate verbosity
+ make test V=2 # maximum verbosity
+
+Test temporary directory and VPP life cycle
+###########################################
+
+Test separation is achieved by separating the test files and vpp instances.
+Each test creates a temporary directory and it's name is used to create
+a shared memory prefix which is used to run a VPP instance.
+The temporary directory name contains the testcase class name for easy
+reference, so for testcase named 'TestVxlan' the directory could be named
+e.g. vpp-unittest-TestVxlan-UNUP3j.
+This way, there is no conflict between any other VPP instances running
+on the box and the test VPP. Any temporary files created by the test case
+are stored in this temporary test directory.
+
+The test temporary directory holds the following interesting files:
+
+* log.txt - this contains the logger output on max verbosity
+* pg*_in.pcap - last injected packet stream into VPP, named after the interface,
+ so for pg0, the file will be named pg0_in.pcap
+* pg*_out.pcap - last capture file created by VPP for interface, similarly,
+ named after the interface, so for e.g. pg1, the file will be named
+ pg1_out.pcap
+* history files - whenever the capture is restarted or a new stream is added,
+ the existing files are rotated and renamed, soo all the pcap files
+ are always saved for later debugging if needed
+* core - if vpp dumps a core, it'll be stored in the temporary directory
+* vpp_stdout.txt - file containing output which vpp printed to stdout
+* vpp_stderr.txt - file containing output which vpp printed to stderr
+
+*NOTE*: existing temporary directories named vpp-unittest-* are automatically
+removed when invoking 'make test*' or 'make retest*' to keep the temporary
+directory clean.
+
+Virtual environment
+###################
+
+Virtualenv_ is a python module which provides a means to create an environment
+containing the dependencies required by the |vtf|, allowing a separation
+from any existing system-wide packages. |vtf|'s Makefile automatically
+creates a virtualenv_ inside build-root and installs the required packages
+in that environment. The environment is entered whenever executing a test
+via one of the make test targets.
+
+Naming conventions
+##################
+
+Most unit tests do some kind of packet manipulation - sending and receiving
+packets between VPP and virtual hosts connected to the VPP. Referring
+to the sides, addresses, etc. is always done as if looking from the VPP side,
+thus:
+
+* *local_* prefix is used for the VPP side.
+ So e.g. `local_ip4 <VppInterface.local_ip4>` address is the IPv4 address
+ assigned to the VPP interface.
+* *remote_* prefix is used for the virtual host side.
+ So e.g. `remote_mac <VppInterface.remote_mac>` address is the MAC address
+ assigned to the virtual host connected to the VPP.
+
+Automatically generated addresses
+#################################
+
+To send packets, one needs to typically provide some addresses, otherwise
+the packets will be dropped. The interface objects in |vtf| automatically
+provide addresses based on (typically) their indexes, which ensures
+there are no conflicts and eases debugging by making the addressing scheme
+consistent.
+
+The developer of a test case typically doesn't need to work with the actual
+numbers, rather using the properties of the objects. The addresses typically
+come in two flavors: '<address>' and '<address>n' - note the 'n' suffix.
+The former address is a Python string, while the latter is translated using
+socket.inet_pton to raw format in network byte order - this format is suitable
+for passing as an argument to VPP APIs.
+
+e.g. for the IPv4 address assigned to the VPP interface:
+
+* local_ip4 - Local IPv4 address on VPP interface (string)
+* local_ip4n - Local IPv4 address - raw, suitable as API parameter.
+
+These addresses need to be configured in VPP to be usable using e.g.
+`config_ip4` API. Please see the documentation to `VppInterface` for more
+details.
+
+By default, there is one remote address of each kind created for L3:
+remote_ip4 and remote_ip6. If the test needs more addresses, because it's
+simulating more remote hosts, they can be generated using
+`generate_remote_hosts` API and the entries for them inserted into the ARP
+table using `configure_ipv4_neighbors` API.
+
+Packet flow in the |vtf|
+########################
+
+Test framework -> VPP
+~~~~~~~~~~~~~~~~~~~~~
+
+|vtf| doesn't send any packets to VPP directly. Traffic is instead injected
+using packet-generator interfaces, represented by the `VppPGInterface` class.
+Packets are written into a temporary .pcap file, which is then read by the VPP
+and the packets are injected into the VPP world.
+
+To add a list of packets to an interface, call the `add_stream` method on that
+interface. Once everything is prepared, call `pg_start` method to start
+the packet generator on the VPP side.
+
+VPP -> test framework
+~~~~~~~~~~~~~~~~~~~~~
+
+Similarly, VPP doesn't send any packets to |vtf| directly. Instead, packet
+capture feature is used to capture and write traffic to a temporary .pcap file,
+which is then read and analyzed by the |vtf|.
+
+The following APIs are available to the test case for reading pcap files.
+
+* `get_capture`: this API is suitable for bulk & batch style of test, where
+ a list of packets is prepared & sent, then the received packets are read
+ and verified. The API needs the number of packets which are expected to
+ be captured (ignoring filtered packets - see below) to know when the pcap
+ file is completely written by the VPP. If using packet infos for verifying
+ packets, then the counts of the packet infos can be automatically used
+ by `get_capture` to get the proper count (in this case the default value
+ None can be supplied as expected_count or ommitted altogether).
+* `wait_for_packet`: this API is suitable for interactive style of test,
+ e.g. when doing session management, three-way handsakes, etc. This API waits
+ for and returns a single packet, keeping the capture file in place
+ and remembering context. Repeated invocations return following packets
+ (or raise Exception if timeout is reached) from the same capture file
+ (= packets arriving on the same interface).
+
+*NOTE*: it is not recommended to mix these APIs unless you understand how they
+work internally. None of these APIs rotate the pcap capture file, so calling
+e.g. `get_capture` after `wait_for_packet` will return already read packets.
+It is safe to switch from one API to another after calling `enable_capture`
+as that API rotates the capture file.
+
+Automatic filtering of packets:
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+Both APIs (`get_capture` and `wait_for_packet`) by default filter the packet
+capture, removing known uninteresting packets from it - these are IPv6 Router
+Advertisments and IPv6 Router Alerts. These packets are unsolicitated
+and from the point of |vtf| are random. If a test wants to receive these
+packets, it should specify either None or a custom filtering function
+as the value to the 'filter_out_fn' argument.
+
+Common API flow for sending/receiving packets:
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+We will describe a simple scenario, where packets are sent from pg0 to pg1
+interface, assuming that the interfaces were created using
+`create_pg_interfaces` API.
+
+1. Create a list of packets for pg0::
+
+ packet_count = 10
+ packets = create_packets(src=self.pg0, dst=self.pg1,
+ count=packet_count)
+
+2. Add that list of packets to the source interface::
+
+ self.pg0.add_stream(packets)
+
+3. Enable capture on the destination interface::
+
+ self.pg1.enable_capture()
+
+4. Start the packet generator::
+
+ self.pg_start()
+
+5. Wait for capture file to appear and read it::
+
+ capture = self.pg1.get_capture(expected_count=packet_count)
+
+6. Verify packets match sent packets::
+
+ self.verify_capture(send=packets, captured=capture)
+
+Test framework objects
+######################
+
+The following objects provide VPP abstraction and provide a means to do
+common tasks easily in the test cases.
+
+* `VppInterface`: abstract class representing generic VPP interface
+ and contains some common functionality, which is then used by derived classes
+* `VppPGInterface`: class representing VPP packet-generator interface.
+ The interface is created/destroyed when the object is created/destroyed.
+* `VppSubInterface`: VPP sub-interface abstract class, containing common
+ functionality for e.g. `VppDot1QSubint` and `VppDot1ADSubint` classes
+
+How VPP APIs/CLIs are called
+############################
+
+Vpp provides python bindings in a python module called vpp-papi, which the test
+framework installs in the virtual environment. A shim layer represented by
+the `VppPapiProvider` class is built on top of the vpp-papi, serving these
+purposes:
+
+1. Automatic return value checks:
+ After each API is called, the return value is checked against the expected
+ return value (by default 0, but can be overridden) and an exception
+ is raised if the check fails.
+2. Automatic call of hooks:
+
+ a. `before_cli <Hook.before_cli>` and `before_api <Hook.before_api>` hooks
+ are used for debug logging and stepping through the test
+ b. `after_cli <Hook.after_cli>` and `after_api <Hook.after_api>` hooks
+ are used for monitoring the vpp process for crashes
+3. Simplification of API calls:
+ Many of the VPP APIs take a lot of parameters and by providing sane defaults
+ for these, the API is much easier to use in the common case and the code is
+ more readable. E.g. ip_add_del_route API takes ~25 parameters, of which
+ in the common case, only 3 are needed.
+
+Utility methods
+###############
+
+Some interesting utility methods are:
+
+* `ppp`: 'Pretty Print Packet' - returns a string containing the same output
+ as Scapy's packet.show() would print
+* `ppc`: 'Pretty Print Capture' - returns a string containing printout of
+ a capture (with configurable limit on the number of packets printed from it)
+ using `ppp`
+
+*NOTE*: Do not use Scapy's packet.show() in the tests, because it prints
+the output to stdout. All output should go to the logger associated with
+the test case.
+
+Example: how to add a new test
+##############################
+
+In this example, we will describe how to add a new test case which tests
+basic IPv4 forwarding.
+
+1. Add a new file called test_ip4_fwd.py in the test directory, starting
+ with a few imports::
+
+ from framework import VppTestCase
+ from scapy.layers.l2 import Ether
+ from scapy.packet import Raw
+ from scapy.layers.inet import IP, UDP
+ from random import randint
+
+2. Create a class inherited from the VppTestCase::
+
+ class IP4FwdTestCase(VppTestCase):
+ """ IPv4 simple forwarding test case """
+
+2. Add a setUpClass function containing the setup needed for our test to run::
+
+ @classmethod
+ def setUpClass(self):
+ super(IP4FwdTestCase, self).setUpClass()
+ self.create_pg_interfaces(range(2)) # create pg0 and pg1
+ for i in self.pg_interfaces:
+ i.admin_up() # put the interface up
+ i.config_ip4() # configure IPv4 address on the interface
+ i.resolve_arp() # resolve ARP, so that we know VPP MAC
+
+3. Create a helper method to create the packets to send::
+
+ def create_stream(self, src_if, dst_if, count):
+ packets = []
+ for i in range(count):
+ # create packet info stored in the test case instance
+ info = self.create_packet_info(src_if, dst_if)
+ # convert the info into packet payload
+ payload = self.info_to_payload(info)
+ # create the packet itself
+ p = (Ether(dst=src_if.local_mac, src=src_if.remote_mac) /
+ IP(src=src_if.remote_ip4, dst=dst_if.remote_ip4) /
+ UDP(sport=randint(1000, 2000), dport=5678) /
+ Raw(payload))
+ # store a copy of the packet in the packet info
+ info.data = p.copy()
+ # append the packet to the list
+ packets.append(p)
+
+ # return the created packet list
+ return packets
+
+4. Create a helper method to verify the capture::
+
+ def verify_capture(self, src_if, dst_if, capture):
+ packet_info = None
+ for packet in capture:
+ try:
+ ip = packet[IP]
+ udp = packet[UDP]
+ # convert the payload to packet info object
+ payload_info = self.payload_to_info(str(packet[Raw]))
+ # make sure the indexes match
+ self.assert_equal(payload_info.src, src_if.sw_if_index,
+ "source sw_if_index")
+ self.assert_equal(payload_info.dst, dst_if.sw_if_index,
+ "destination sw_if_index")
+ packet_info = self.get_next_packet_info_for_interface2(
+ src_if.sw_if_index,
+ dst_if.sw_if_index,
+ packet_info)
+ # make sure we didn't run out of saved packets
+ self.assertIsNotNone(packet_info)
+ self.assert_equal(payload_info.index, packet_info.index,
+ "packet info index")
+ saved_packet = packet_info.data # fetch the saved packet
+ # assert the values match
+ self.assert_equal(ip.src, saved_packet[IP].src,
+ "IP source address")
+ # ... more assertions here
+ self.assert_equal(udp.sport, saved_packet[UDP].sport,
+ "UDP source port")
+ except:
+ self.logger.error(ppp("Unexpected or invalid packet:",
+ packet))
+ raise
+ remaining_packet = self.get_next_packet_info_for_interface2(
+ src_if.sw_if_index,
+ dst_if.sw_if_index,
+ packet_info)
+ self.assertIsNone(remaining_packet,
+ "Interface %s: Packet expected from interface "
+ "%s didn't arrive" % (dst_if.name, src_if.name))
+
+5. Add the test code to test_basic function::
+
+ def test_basic(self):
+ count = 10
+ # create the packet stream
+ packets = self.create_stream(self.pg0, self.pg1, count)
+ # add the stream to the source interface
+ self.pg0.add_stream(packets)
+ # enable capture on both interfaces
+ self.pg0.enable_capture()
+ self.pg1.enable_capture()
+ # start the packet generator
+ self.pg_start()
+ # get capture - the proper count of packets was saved by
+ # create_packet_info() based on dst_if parameter
+ capture = self.pg1.get_capture()
+ # assert nothing captured on pg0 (always do this last, so that
+ # some time has already passed since pg_start())
+ self.pg0.assert_nothing_captured()
+ # verify capture
+ self.verify_capture(self.pg0, self.pg1, capture)
+
+6. Run the test by issuing 'make test'.
diff --git a/test/ext/Makefile b/test/ext/Makefile
new file mode 100644
index 00000000..6b3cb907
--- /dev/null
+++ b/test/ext/Makefile
@@ -0,0 +1,31 @@
+BINDIR = $(BR)/vapi_test/
+CBIN = $(addprefix $(BINDIR), vapi_c_test)
+CPPBIN = $(addprefix $(BINDIR), vapi_cpp_test)
+
+LIBS = -L$(VPP_TEST_BUILD_DIR)/vpp/.libs/ -L$(VPP_TEST_BUILD_DIR)/vpp/vpp-api/vapi/.libs/ -lvppinfra -lvlibmemoryclient -lsvm -lpthread -lcheck -lsubunit -lrt -lm -lvapiclient
+CFLAGS = -std=gnu99 -g -Wall -pthread -I$(WS_ROOT)/src -I$(VPP_TEST_INSTALL_PATH)/vpp/include -I$(BINDIR)
+CPPFLAGS = -std=c++11 -g -Wall -pthread -I$(WS_ROOT)/src -I$(VPP_TEST_INSTALL_PATH)/vpp/include -I$(BINDIR)
+
+all: $(CBIN) $(CPPBIN)
+
+$(BINDIR):
+ mkdir -p $(BINDIR)
+
+CSRC = vapi_c_test.c
+
+$(BINDIR)/fake.api.vapi.h: fake.api.json $(WS_ROOT)/src/vpp-api/vapi/vapi_c_gen.py | $(BINDIR)
+ $(WS_ROOT)/src/vpp-api/vapi/vapi_c_gen.py --prefix $(BINDIR) $<
+
+$(BINDIR)/fake.api.vapi.hpp: fake.api.json $(WS_ROOT)/src/vpp-api/vapi/vapi_cpp_gen.py | $(BINDIR)
+ $(WS_ROOT)/src/vpp-api/vapi/vapi_cpp_gen.py --prefix $(BINDIR) $<
+
+$(CBIN): $(CSRC) $(VPP_TEST_BUILD_DIR)/vpp/vpp-api/vapi/.libs/libvapiclient.so $(VPP_TEST_BUILD_DIR)/vpp/.libs/libvppinfra.so $(VPP_TEST_BUILD_DIR)/vpp/.libs/libvlibmemoryclient.so $(VPP_TEST_BUILD_DIR)/vpp/.libs/libsvm.so $(BINDIR)/fake.api.vapi.h
+ $(CC) -o $@ $(CFLAGS) $(CSRC) $(LIBS)
+
+CPPSRC = vapi_cpp_test.cpp
+
+$(CPPBIN): $(CPPSRC) $(VPP_TEST_BUILD_DIR)/vpp/vpp-api/vapi/.libs/libvapiclient.so $(VPP_TEST_BUILD_DIR)/vpp/.libs/libvppinfra.so $(VPP_TEST_BUILD_DIR)/vpp/.libs/libvlibmemoryclient.so $(VPP_TEST_BUILD_DIR)/vpp/.libs/libsvm.so $(BINDIR)/fake.api.vapi.hpp
+ $(CXX) -o $@ $(CPPFLAGS) $(CPPSRC) $(LIBS)
+
+clean:
+ rm -rf $(BINDIR)
diff --git a/test/ext/fake.api.json b/test/ext/fake.api.json
new file mode 100644
index 00000000..3e8d6a95
--- /dev/null
+++ b/test/ext/fake.api.json
@@ -0,0 +1,35 @@
+{
+ "types" : [
+
+ ],
+ "messages" : [
+ ["test_fake_msg",
+ ["u16", "_vl_msg_id"],
+ ["u32", "client_index"],
+ ["u32", "context"],
+ ["u8", "dummy", 256],
+ {"crc" : "0xcafebafe"}
+ ],
+ ["test_fake_msg_reply",
+ ["u16", "_vl_msg_id"],
+ ["u32", "context"],
+ ["i32", "retval"],
+ {"crc" : "0xcafebafe"}
+ ],
+ ["test_fake_dump",
+ ["u16", "_vl_msg_id"],
+ ["u32", "client_index"],
+ ["u32", "context"],
+ ["u32", "dummy"],
+ {"crc" : "0xcafebafe"}
+ ],
+ ["test_fake_details",
+ ["u16", "_vl_msg_id"],
+ ["u32", "client_index"],
+ ["u32", "context"],
+ ["u32", "dummy"],
+ {"crc" : "0xcafebafe"}
+ ]
+ ],
+"vl_api_version" :"0x224c7aad"
+}
diff --git a/test/ext/vapi_c_test.c b/test/ext/vapi_c_test.c
new file mode 100644
index 00000000..622b617b
--- /dev/null
+++ b/test/ext/vapi_c_test.c
@@ -0,0 +1,1168 @@
+/*
+ *------------------------------------------------------------------
+ * Copyright (c) 2017 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.
+ *------------------------------------------------------------------
+ */
+
+#include <stdio.h>
+#include <endian.h>
+#include <stdlib.h>
+#include <unistd.h>
+#include <assert.h>
+#include <setjmp.h>
+#include <check.h>
+#include <vapi/vapi.h>
+#include <vapi/vpe.api.vapi.h>
+#include <vapi/interface.api.vapi.h>
+#include <vapi/l2.api.vapi.h>
+#include <vapi/stats.api.vapi.h>
+#include <fake.api.vapi.h>
+
+DEFINE_VAPI_MSG_IDS_VPE_API_JSON;
+DEFINE_VAPI_MSG_IDS_INTERFACE_API_JSON;
+DEFINE_VAPI_MSG_IDS_L2_API_JSON;
+DEFINE_VAPI_MSG_IDS_STATS_API_JSON;
+DEFINE_VAPI_MSG_IDS_FAKE_API_JSON;
+
+static char *app_name = NULL;
+static char *api_prefix = NULL;
+static const int max_outstanding_requests = 64;
+static const int response_queue_size = 32;
+
+START_TEST (test_invalid_values)
+{
+ vapi_ctx_t ctx;
+ vapi_error_e rv = vapi_ctx_alloc (&ctx);
+ ck_assert_int_eq (VAPI_OK, rv);
+ vapi_msg_show_version *sv = vapi_alloc_show_version (ctx);
+ ck_assert_ptr_eq (NULL, sv);
+ rv = vapi_send (ctx, sv);
+ ck_assert_int_eq (VAPI_EINVAL, rv);
+ rv = vapi_connect (ctx, app_name, api_prefix, max_outstanding_requests,
+ response_queue_size, VAPI_MODE_BLOCKING);
+ ck_assert_int_eq (VAPI_OK, rv);
+ rv = vapi_send (ctx, NULL);
+ ck_assert_int_eq (VAPI_EINVAL, rv);
+ rv = vapi_send (NULL, NULL);
+ ck_assert_int_eq (VAPI_EINVAL, rv);
+ rv = vapi_recv (NULL, NULL, NULL);
+ ck_assert_int_eq (VAPI_EINVAL, rv);
+ rv = vapi_recv (ctx, NULL, NULL);
+ ck_assert_int_eq (VAPI_EINVAL, rv);
+ vapi_msg_show_version_reply *reply;
+ rv = vapi_recv (ctx, (void **) &reply, NULL);
+ ck_assert_int_eq (VAPI_EINVAL, rv);
+ rv = vapi_disconnect (ctx);
+ ck_assert_int_eq (VAPI_OK, rv);
+ vapi_ctx_free (ctx);
+}
+
+END_TEST;
+
+START_TEST (test_hton_1)
+{
+ const u16 _vl_msg_id = 1;
+ vapi_type_msg_header1_t h;
+ h._vl_msg_id = _vl_msg_id;
+ vapi_type_msg_header1_t_hton (&h);
+ ck_assert_int_eq (be16toh (h._vl_msg_id), _vl_msg_id);
+}
+
+END_TEST;
+
+START_TEST (test_hton_2)
+{
+ const u16 _vl_msg_id = 1;
+ const u32 client_index = 3;
+ vapi_type_msg_header2_t h;
+ h._vl_msg_id = _vl_msg_id;
+ h.client_index = client_index;
+ vapi_type_msg_header2_t_hton (&h);
+ ck_assert_int_eq (be16toh (h._vl_msg_id), _vl_msg_id);
+ ck_assert_int_eq (h.client_index, client_index);
+}
+
+END_TEST;
+
+START_TEST (test_hton_3)
+{
+ const size_t data_size = 10;
+ vapi_msg_vnet_interface_combined_counters *m =
+ malloc (sizeof (vapi_msg_vnet_interface_combined_counters) +
+ data_size * sizeof (vapi_type_vlib_counter));
+ ck_assert_ptr_ne (NULL, m);
+ vapi_payload_vnet_interface_combined_counters *p = &m->payload;
+ const u16 _vl_msg_id = 1;
+ p->_vl_msg_id = _vl_msg_id;
+ const u32 first_sw_if_index = 2;
+ p->first_sw_if_index = first_sw_if_index;
+ p->count = data_size;
+ const u64 packets = 1234;
+ const u64 bytes = 2345;
+ int i;
+ for (i = 0; i < data_size; ++i)
+ {
+ p->data[i].packets = packets;
+ p->data[i].bytes = bytes;
+ }
+ vapi_msg_vnet_interface_combined_counters_hton (m);
+ ck_assert_int_eq (_vl_msg_id, be16toh (p->_vl_msg_id));
+ ck_assert_int_eq (first_sw_if_index, be32toh (p->first_sw_if_index));
+ ck_assert_int_eq (data_size, be32toh (p->count));
+ for (i = 0; i < data_size; ++i)
+ {
+ ck_assert_int_eq (packets, be64toh (p->data[i].packets));
+ ck_assert_int_eq (bytes, be64toh (p->data[i].bytes));
+ }
+ free (p);
+}
+
+END_TEST;
+
+#define verify_hton_swap(expr, value) \
+ if (4 == sizeof (expr)) \
+ { \
+ ck_assert_int_eq (expr, htobe32 (value)); \
+ } \
+ else if (2 == sizeof (expr)) \
+ { \
+ ck_assert_int_eq (expr, htobe16 (value)); \
+ } \
+ else \
+ { \
+ ck_assert_int_eq (expr, value); \
+ }
+
+START_TEST (test_hton_4)
+{
+ const int vla_count = 3;
+ char x[sizeof (vapi_msg_bridge_domain_details) +
+ vla_count * sizeof (vapi_type_bridge_domain_sw_if)];
+ vapi_msg_bridge_domain_details *d = (void *) x;
+ int cnt = 1;
+ d->header._vl_msg_id = cnt++;
+ d->header.context = cnt++;
+ d->payload.bd_id = cnt++;
+ d->payload.flood = cnt++;
+ d->payload.uu_flood = cnt++;
+ d->payload.forward = cnt++;
+ d->payload.learn = cnt++;
+ d->payload.arp_term = cnt++;
+ d->payload.mac_age = cnt++;
+ d->payload.bvi_sw_if_index = cnt++;
+ d->payload.n_sw_ifs = vla_count;
+ int i;
+ for (i = 0; i < vla_count; ++i)
+ {
+ vapi_type_bridge_domain_sw_if *det = &d->payload.sw_if_details[i];
+ det->context = cnt++;
+ det->sw_if_index = cnt++;
+ det->shg = cnt++;
+ }
+ ck_assert_int_eq (sizeof (x), vapi_calc_bridge_domain_details_msg_size (d));
+ vapi_msg_bridge_domain_details_hton (d);
+ int tmp = 1;
+ verify_hton_swap (d->header._vl_msg_id, tmp);
+ ++tmp;
+ ck_assert_int_eq (d->header.context, tmp);
+ ++tmp;
+ verify_hton_swap (d->payload.bd_id, tmp);
+ ++tmp;
+ verify_hton_swap (d->payload.flood, tmp);
+ ++tmp;
+ verify_hton_swap (d->payload.uu_flood, tmp);
+ ++tmp;
+ verify_hton_swap (d->payload.forward, tmp);
+ ++tmp;
+ verify_hton_swap (d->payload.learn, tmp);
+ ++tmp;
+ verify_hton_swap (d->payload.arp_term, tmp);
+ ++tmp;
+ verify_hton_swap (d->payload.mac_age, tmp);
+ ++tmp;
+ verify_hton_swap (d->payload.bvi_sw_if_index, tmp);
+ ++tmp;
+ ck_assert_int_eq (d->payload.n_sw_ifs, htobe32 (vla_count));
+ for (i = 0; i < vla_count; ++i)
+ {
+ vapi_type_bridge_domain_sw_if *det = &d->payload.sw_if_details[i];
+ verify_hton_swap (det->context, tmp);
+ ++tmp;
+ verify_hton_swap (det->sw_if_index, tmp);
+ ++tmp;
+ verify_hton_swap (det->shg, tmp);
+ ++tmp;
+ }
+ vapi_msg_bridge_domain_details_ntoh (d);
+ tmp = 1;
+ ck_assert_int_eq (d->header._vl_msg_id, tmp);
+ ++tmp;
+ ck_assert_int_eq (d->header.context, tmp);
+ ++tmp;
+ ck_assert_int_eq (d->payload.bd_id, tmp);
+ ++tmp;
+ ck_assert_int_eq (d->payload.flood, tmp);
+ ++tmp;
+ ck_assert_int_eq (d->payload.uu_flood, tmp);
+ ++tmp;
+ ck_assert_int_eq (d->payload.forward, tmp);
+ ++tmp;
+ ck_assert_int_eq (d->payload.learn, tmp);
+ ++tmp;
+ ck_assert_int_eq (d->payload.arp_term, tmp);
+ ++tmp;
+ ck_assert_int_eq (d->payload.mac_age, tmp);
+ ++tmp;
+ ck_assert_int_eq (d->payload.bvi_sw_if_index, tmp);
+ ++tmp;
+ ck_assert_int_eq (d->payload.n_sw_ifs, vla_count);
+ for (i = 0; i < vla_count; ++i)
+ {
+ vapi_type_bridge_domain_sw_if *det = &d->payload.sw_if_details[i];
+ ck_assert_int_eq (det->context, tmp);
+ ++tmp;
+ ck_assert_int_eq (det->sw_if_index, tmp);
+ ++tmp;
+ ck_assert_int_eq (det->shg, tmp);
+ ++tmp;
+ }
+ ck_assert_int_eq (sizeof (x), vapi_calc_bridge_domain_details_msg_size (d));
+}
+
+END_TEST;
+
+START_TEST (test_ntoh_1)
+{
+ const u16 _vl_msg_id = 1;
+ vapi_type_msg_header1_t h;
+ h._vl_msg_id = _vl_msg_id;
+ vapi_type_msg_header1_t_ntoh (&h);
+ ck_assert_int_eq (htobe16 (h._vl_msg_id), _vl_msg_id);
+}
+
+END_TEST;
+
+START_TEST (test_ntoh_2)
+{
+ const u16 _vl_msg_id = 1;
+ const u32 client_index = 3;
+ vapi_type_msg_header2_t h;
+ h._vl_msg_id = _vl_msg_id;
+ h.client_index = client_index;
+ vapi_type_msg_header2_t_ntoh (&h);
+ ck_assert_int_eq (htobe16 (h._vl_msg_id), _vl_msg_id);
+ ck_assert_int_eq (h.client_index, client_index);
+}
+
+END_TEST;
+
+START_TEST (test_ntoh_3)
+{
+ const size_t data_size = 10;
+ vapi_msg_vnet_interface_combined_counters *m =
+ malloc (sizeof (vapi_msg_vnet_interface_combined_counters) +
+ data_size * sizeof (vapi_type_vlib_counter));
+ ck_assert_ptr_ne (NULL, m);
+ vapi_payload_vnet_interface_combined_counters *p = &m->payload;
+ const u16 _vl_msg_id = 1;
+ p->_vl_msg_id = _vl_msg_id;
+ const u32 first_sw_if_index = 2;
+ p->first_sw_if_index = first_sw_if_index;
+ const size_t be_data_size = htobe32 (data_size);
+ p->count = be_data_size;
+ const u64 packets = 1234;
+ const u64 bytes = 2345;
+ int i;
+ for (i = 0; i < data_size; ++i)
+ {
+ p->data[i].packets = packets;
+ p->data[i].bytes = bytes;
+ }
+ vapi_msg_vnet_interface_combined_counters_ntoh (m);
+ ck_assert_int_eq (_vl_msg_id, be16toh (p->_vl_msg_id));
+ ck_assert_int_eq (first_sw_if_index, be32toh (p->first_sw_if_index));
+ ck_assert_int_eq (be_data_size, be32toh (p->count));
+ for (i = 0; i < data_size; ++i)
+ {
+ ck_assert_int_eq (packets, htobe64 (p->data[i].packets));
+ ck_assert_int_eq (bytes, htobe64 (p->data[i].bytes));
+ }
+ free (p);
+}
+
+END_TEST;
+
+#define verify_ntoh_swap(expr, value) \
+ if (4 == sizeof (expr)) \
+ { \
+ ck_assert_int_eq (expr, be32toh (value)); \
+ } \
+ else if (2 == sizeof (expr)) \
+ { \
+ ck_assert_int_eq (expr, be16toh (value)); \
+ } \
+ else \
+ { \
+ ck_assert_int_eq (expr, value); \
+ }
+
+START_TEST (test_ntoh_4)
+{
+ const int vla_count = 3;
+ char x[sizeof (vapi_msg_bridge_domain_details) +
+ vla_count * sizeof (vapi_type_bridge_domain_sw_if)];
+ vapi_msg_bridge_domain_details *d = (void *) x;
+ int cnt = 1;
+ d->header._vl_msg_id = cnt++;
+ d->header.context = cnt++;
+ d->payload.bd_id = cnt++;
+ d->payload.flood = cnt++;
+ d->payload.uu_flood = cnt++;
+ d->payload.forward = cnt++;
+ d->payload.learn = cnt++;
+ d->payload.arp_term = cnt++;
+ d->payload.mac_age = cnt++;
+ d->payload.bvi_sw_if_index = cnt++;
+ d->payload.n_sw_ifs = htobe32 (vla_count);
+ int i;
+ for (i = 0; i < vla_count; ++i)
+ {
+ vapi_type_bridge_domain_sw_if *det = &d->payload.sw_if_details[i];
+ det->context = cnt++;
+ det->sw_if_index = cnt++;
+ det->shg = cnt++;
+ }
+ vapi_msg_bridge_domain_details_ntoh (d);
+ ck_assert_int_eq (sizeof (x), vapi_calc_bridge_domain_details_msg_size (d));
+ int tmp = 1;
+ verify_ntoh_swap (d->header._vl_msg_id, tmp);
+ ++tmp;
+ ck_assert_int_eq (d->header.context, tmp);
+ ++tmp;
+ verify_ntoh_swap (d->payload.bd_id, tmp);
+ ++tmp;
+ verify_ntoh_swap (d->payload.flood, tmp);
+ ++tmp;
+ verify_ntoh_swap (d->payload.uu_flood, tmp);
+ ++tmp;
+ verify_ntoh_swap (d->payload.forward, tmp);
+ ++tmp;
+ verify_ntoh_swap (d->payload.learn, tmp);
+ ++tmp;
+ verify_ntoh_swap (d->payload.arp_term, tmp);
+ ++tmp;
+ verify_ntoh_swap (d->payload.mac_age, tmp);
+ ++tmp;
+ verify_ntoh_swap (d->payload.bvi_sw_if_index, tmp);
+ ++tmp;
+ ck_assert_int_eq (d->payload.n_sw_ifs, vla_count);
+ for (i = 0; i < vla_count; ++i)
+ {
+ vapi_type_bridge_domain_sw_if *det = &d->payload.sw_if_details[i];
+ verify_ntoh_swap (det->context, tmp);
+ ++tmp;
+ verify_ntoh_swap (det->sw_if_index, tmp);
+ ++tmp;
+ verify_ntoh_swap (det->shg, tmp);
+ ++tmp;
+ }
+ vapi_msg_bridge_domain_details_hton (d);
+ tmp = 1;
+ ck_assert_int_eq (d->header._vl_msg_id, tmp);
+ ++tmp;
+ ck_assert_int_eq (d->header.context, tmp);
+ ++tmp;
+ ck_assert_int_eq (d->payload.bd_id, tmp);
+ ++tmp;
+ ck_assert_int_eq (d->payload.flood, tmp);
+ ++tmp;
+ ck_assert_int_eq (d->payload.uu_flood, tmp);
+ ++tmp;
+ ck_assert_int_eq (d->payload.forward, tmp);
+ ++tmp;
+ ck_assert_int_eq (d->payload.learn, tmp);
+ ++tmp;
+ ck_assert_int_eq (d->payload.arp_term, tmp);
+ ++tmp;
+ ck_assert_int_eq (d->payload.mac_age, tmp);
+ ++tmp;
+ ck_assert_int_eq (d->payload.bvi_sw_if_index, tmp);
+ ++tmp;
+ ck_assert_int_eq (d->payload.n_sw_ifs, htobe32 (vla_count));
+ for (i = 0; i < vla_count; ++i)
+ {
+ vapi_type_bridge_domain_sw_if *det = &d->payload.sw_if_details[i];
+ ck_assert_int_eq (det->context, tmp);
+ ++tmp;
+ ck_assert_int_eq (det->sw_if_index, tmp);
+ ++tmp;
+ ck_assert_int_eq (det->shg, tmp);
+ ++tmp;
+ }
+}
+
+END_TEST;
+
+vapi_error_e
+show_version_cb (vapi_ctx_t ctx, void *caller_ctx,
+ vapi_error_e rv, bool is_last,
+ vapi_payload_show_version_reply * p)
+{
+ ck_assert_int_eq (VAPI_OK, rv);
+ ck_assert_int_eq (true, is_last);
+ ck_assert_str_eq ("vpe", (char *) p->program);
+ printf
+ ("show_version_reply: program: `%s', version: `%s', build directory: "
+ "`%s', build date: `%s'\n", p->program, p->version, p->build_directory,
+ p->build_date);
+ ++*(int *) caller_ctx;
+ return VAPI_OK;
+}
+
+typedef struct
+{
+ int called;
+ int expected_retval;
+ u32 *sw_if_index_storage;
+} test_create_loopback_ctx_t;
+
+vapi_error_e
+loopback_create_cb (vapi_ctx_t ctx, void *caller_ctx,
+ vapi_error_e rv, bool is_last,
+ vapi_payload_create_loopback_reply * p)
+{
+ test_create_loopback_ctx_t *clc = caller_ctx;
+ ck_assert_int_eq (clc->expected_retval, p->retval);
+ *clc->sw_if_index_storage = p->sw_if_index;
+ ++clc->called;
+ return VAPI_OK;
+}
+
+typedef struct
+{
+ int called;
+ int expected_retval;
+ u32 *sw_if_index_storage;
+} test_delete_loopback_ctx_t;
+
+vapi_error_e
+loopback_delete_cb (vapi_ctx_t ctx, void *caller_ctx,
+ vapi_error_e rv, bool is_last,
+ vapi_payload_delete_loopback_reply * p)
+{
+ test_delete_loopback_ctx_t *dlc = caller_ctx;
+ ck_assert_int_eq (dlc->expected_retval, p->retval);
+ ++dlc->called;
+ return VAPI_OK;
+}
+
+START_TEST (test_connect)
+{
+ vapi_ctx_t ctx;
+ vapi_error_e rv = vapi_ctx_alloc (&ctx);
+ ck_assert_int_eq (VAPI_OK, rv);
+ rv = vapi_connect (ctx, app_name, api_prefix, max_outstanding_requests,
+ response_queue_size, VAPI_MODE_BLOCKING);
+ ck_assert_int_eq (VAPI_OK, rv);
+ rv = vapi_disconnect (ctx);
+ ck_assert_int_eq (VAPI_OK, rv);
+ vapi_ctx_free (ctx);
+}
+
+END_TEST;
+
+vapi_ctx_t ctx;
+
+void
+setup_blocking (void)
+{
+ vapi_error_e rv = vapi_ctx_alloc (&ctx);
+ ck_assert_int_eq (VAPI_OK, rv);
+ rv = vapi_connect (ctx, app_name, api_prefix, max_outstanding_requests,
+ response_queue_size, VAPI_MODE_BLOCKING);
+ ck_assert_int_eq (VAPI_OK, rv);
+}
+
+void
+setup_nonblocking (void)
+{
+ vapi_error_e rv = vapi_ctx_alloc (&ctx);
+ ck_assert_int_eq (VAPI_OK, rv);
+ rv = vapi_connect (ctx, app_name, api_prefix, max_outstanding_requests,
+ response_queue_size, VAPI_MODE_NONBLOCKING);
+ ck_assert_int_eq (VAPI_OK, rv);
+}
+
+void
+teardown (void)
+{
+ vapi_disconnect (ctx);
+ vapi_ctx_free (ctx);
+}
+
+START_TEST (test_show_version_1)
+{
+ printf ("--- Basic show version message - reply test ---\n");
+ vapi_msg_show_version *sv = vapi_alloc_show_version (ctx);
+ ck_assert_ptr_ne (NULL, sv);
+ vapi_msg_show_version_hton (sv);
+ vapi_error_e rv = vapi_send (ctx, sv);
+ ck_assert_int_eq (VAPI_OK, rv);
+ vapi_msg_show_version_reply *resp;
+ size_t size;
+ rv = vapi_recv (ctx, (void *) &resp, &size);
+ ck_assert_int_eq (VAPI_OK, rv);
+ int dummy;
+ show_version_cb (NULL, &dummy, VAPI_OK, true, &resp->payload);
+ vapi_msg_free (ctx, resp);
+}
+
+END_TEST;
+
+START_TEST (test_show_version_2)
+{
+ int called = 0;
+ printf ("--- Show version via blocking callback API ---\n");
+ const int attempts = response_queue_size * 4;
+ int i = 0;
+ for (i = 0; i < attempts; ++i)
+ {
+ vapi_msg_show_version *sv = vapi_alloc_show_version (ctx);
+ ck_assert_ptr_ne (NULL, sv);
+ vapi_error_e rv = vapi_show_version (ctx, sv, show_version_cb, &called);
+ ck_assert_int_eq (VAPI_OK, rv);
+ }
+ ck_assert_int_eq (attempts, called);
+}
+
+END_TEST;
+
+typedef struct
+{
+ bool last_called;
+ size_t num_ifs;
+ u32 *sw_if_indexes;
+ bool *seen;
+ int called;
+} sw_interface_dump_ctx;
+
+vapi_error_e
+sw_interface_dump_cb (struct vapi_ctx_s *ctx, void *callback_ctx,
+ vapi_error_e rv, bool is_last,
+ vapi_payload_sw_interface_details * reply)
+{
+ sw_interface_dump_ctx *dctx = callback_ctx;
+ ck_assert_int_eq (false, dctx->last_called);
+ if (is_last)
+ {
+ ck_assert (NULL == reply);
+ dctx->last_called = true;
+ }
+ else
+ {
+ ck_assert (reply);
+ printf ("Interface dump entry: [%u]: %s\n", reply->sw_if_index,
+ reply->interface_name);
+ size_t i = 0;
+ for (i = 0; i < dctx->num_ifs; ++i)
+ {
+ if (dctx->sw_if_indexes[i] == reply->sw_if_index)
+ {
+ ck_assert_int_eq (false, dctx->seen[i]);
+ dctx->seen[i] = true;
+ }
+ }
+ }
+ ++dctx->called;
+ return VAPI_OK;
+}
+
+START_TEST (test_loopbacks_1)
+{
+ printf ("--- Create/delete loopbacks using blocking API ---\n");
+ const size_t num_ifs = 5;
+ u8 mac_addresses[num_ifs][6];
+ memset (&mac_addresses, 0, sizeof (mac_addresses));
+ u32 sw_if_indexes[num_ifs];
+ memset (&sw_if_indexes, 0xff, sizeof (sw_if_indexes));
+ test_create_loopback_ctx_t clcs[num_ifs];
+ memset (&clcs, 0, sizeof (clcs));
+ test_delete_loopback_ctx_t dlcs[num_ifs];
+ memset (&dlcs, 0, sizeof (dlcs));
+ int i;
+ for (i = 0; i < num_ifs; ++i)
+ {
+ memcpy (&mac_addresses[i], "\1\2\3\4\5\6", 6);
+ mac_addresses[i][5] = i;
+ clcs[i].sw_if_index_storage = &sw_if_indexes[i];
+ }
+ for (i = 0; i < num_ifs; ++i)
+ {
+ vapi_msg_create_loopback *cl = vapi_alloc_create_loopback (ctx);
+ memcpy (cl->payload.mac_address, mac_addresses[i],
+ sizeof (cl->payload.mac_address));
+ vapi_error_e rv =
+ vapi_create_loopback (ctx, cl, loopback_create_cb, &clcs[i]);
+ ck_assert_int_eq (VAPI_OK, rv);
+ }
+ for (i = 0; i < num_ifs; ++i)
+ {
+ ck_assert_int_eq (1, clcs[i].called);
+ printf ("Created loopback with MAC %02x:%02x:%02x:%02x:%02x:%02x --> "
+ "sw_if_index %u\n",
+ mac_addresses[i][0], mac_addresses[i][1], mac_addresses[i][2],
+ mac_addresses[i][3], mac_addresses[i][4], mac_addresses[i][5],
+ sw_if_indexes[i]);
+ }
+ bool seen[num_ifs];
+ sw_interface_dump_ctx dctx = { false, num_ifs, sw_if_indexes, seen, 0 };
+ vapi_msg_sw_interface_dump *dump;
+ vapi_error_e rv;
+ const int attempts = response_queue_size * 4;
+ for (i = 0; i < attempts; ++i)
+ {
+ dctx.last_called = false;
+ memset (&seen, 0, sizeof (seen));
+ dump = vapi_alloc_sw_interface_dump (ctx);
+ dump->payload.name_filter_valid = 0;
+ memset (dump->payload.name_filter, 0,
+ sizeof (dump->payload.name_filter));
+ while (VAPI_EAGAIN ==
+ (rv =
+ vapi_sw_interface_dump (ctx, dump, sw_interface_dump_cb,
+ &dctx)))
+ ;
+ ck_assert_int_eq (true, dctx.last_called);
+ int j = 0;
+ for (j = 0; j < num_ifs; ++j)
+ {
+ ck_assert_int_eq (true, seen[j]);
+ }
+ }
+ memset (&seen, 0, sizeof (seen));
+ for (i = 0; i < num_ifs; ++i)
+ {
+ vapi_msg_delete_loopback *dl = vapi_alloc_delete_loopback (ctx);
+ dl->payload.sw_if_index = sw_if_indexes[i];
+ vapi_error_e rv =
+ vapi_delete_loopback (ctx, dl, loopback_delete_cb, &dlcs[i]);
+ ck_assert_int_eq (VAPI_OK, rv);
+ }
+ for (i = 0; i < num_ifs; ++i)
+ {
+ ck_assert_int_eq (1, dlcs[i].called);
+ printf ("Deleted loopback with sw_if_index %u\n", sw_if_indexes[i]);
+ }
+ dctx.last_called = false;
+ memset (&seen, 0, sizeof (seen));
+ dump = vapi_alloc_sw_interface_dump (ctx);
+ dump->payload.name_filter_valid = 0;
+ memset (dump->payload.name_filter, 0, sizeof (dump->payload.name_filter));
+ while (VAPI_EAGAIN ==
+ (rv =
+ vapi_sw_interface_dump (ctx, dump, sw_interface_dump_cb, &dctx)))
+ ;
+ ck_assert_int_eq (true, dctx.last_called);
+ for (i = 0; i < num_ifs; ++i)
+ {
+ ck_assert_int_eq (false, seen[i]);
+ }
+}
+
+END_TEST;
+
+START_TEST (test_show_version_3)
+{
+ printf ("--- Show version via async callback ---\n");
+ int called = 0;
+ vapi_error_e rv;
+ vapi_msg_show_version *sv = vapi_alloc_show_version (ctx);
+ ck_assert_ptr_ne (NULL, sv);
+ while (VAPI_EAGAIN ==
+ (rv = vapi_show_version (ctx, sv, show_version_cb, &called)))
+ ;
+ ck_assert_int_eq (VAPI_OK, rv);
+ ck_assert_int_eq (0, called);
+ rv = vapi_dispatch (ctx);
+ ck_assert_int_eq (VAPI_OK, rv);
+ ck_assert_int_eq (1, called);
+ called = 0;
+ rv = vapi_dispatch (ctx);
+ ck_assert_int_eq (VAPI_OK, rv);
+ ck_assert_int_eq (0, called);
+}
+
+END_TEST;
+
+START_TEST (test_show_version_4)
+{
+ printf ("--- Show version via async callback - multiple messages ---\n");
+ vapi_error_e rv;
+ const size_t num_req = 5;
+ int contexts[num_req];
+ memset (contexts, 0, sizeof (contexts));
+ int i;
+ for (i = 0; i < num_req; ++i)
+ {
+ vapi_msg_show_version *sv = vapi_alloc_show_version (ctx);
+ ck_assert_ptr_ne (NULL, sv);
+ while (VAPI_EAGAIN ==
+ (rv =
+ vapi_show_version (ctx, sv, show_version_cb, &contexts[i])))
+ ;
+ ck_assert_int_eq (VAPI_OK, rv);
+ int j;
+ for (j = 0; j < num_req; ++j)
+ {
+ ck_assert_int_eq (0, contexts[j]);
+ }
+ }
+ rv = vapi_dispatch (ctx);
+ ck_assert_int_eq (VAPI_OK, rv);
+ for (i = 0; i < num_req; ++i)
+ {
+ ck_assert_int_eq (1, contexts[i]);
+ }
+ memset (contexts, 0, sizeof (contexts));
+ rv = vapi_dispatch (ctx);
+ ck_assert_int_eq (VAPI_OK, rv);
+ for (i = 0; i < num_req; ++i)
+ {
+ ck_assert_int_eq (0, contexts[i]);
+ }
+}
+
+END_TEST;
+
+START_TEST (test_loopbacks_2)
+{
+ printf ("--- Create/delete loopbacks using non-blocking API ---\n");
+ vapi_error_e rv;
+ const size_t num_ifs = 5;
+ u8 mac_addresses[num_ifs][6];
+ memset (&mac_addresses, 0, sizeof (mac_addresses));
+ u32 sw_if_indexes[num_ifs];
+ memset (&sw_if_indexes, 0xff, sizeof (sw_if_indexes));
+ test_create_loopback_ctx_t clcs[num_ifs];
+ memset (&clcs, 0, sizeof (clcs));
+ test_delete_loopback_ctx_t dlcs[num_ifs];
+ memset (&dlcs, 0, sizeof (dlcs));
+ int i;
+ for (i = 0; i < num_ifs; ++i)
+ {
+ memcpy (&mac_addresses[i], "\1\2\3\4\5\6", 6);
+ mac_addresses[i][5] = i;
+ clcs[i].sw_if_index_storage = &sw_if_indexes[i];
+ }
+ for (i = 0; i < num_ifs; ++i)
+ {
+ vapi_msg_create_loopback *cl = vapi_alloc_create_loopback (ctx);
+ memcpy (cl->payload.mac_address, mac_addresses[i],
+ sizeof (cl->payload.mac_address));
+ while (VAPI_EAGAIN ==
+ (rv =
+ vapi_create_loopback (ctx, cl, loopback_create_cb, &clcs[i])))
+ ;
+ ck_assert_int_eq (VAPI_OK, rv);
+ }
+ rv = vapi_dispatch (ctx);
+ ck_assert_int_eq (VAPI_OK, rv);
+ for (i = 0; i < num_ifs; ++i)
+ {
+ ck_assert_int_eq (1, clcs[i].called);
+ printf ("Loopback with MAC %02x:%02x:%02x:%02x:%02x:%02x --> "
+ "sw_if_index %u\n",
+ mac_addresses[i][0], mac_addresses[i][1], mac_addresses[i][2],
+ mac_addresses[i][3], mac_addresses[i][4], mac_addresses[i][5],
+ sw_if_indexes[i]);
+ }
+ bool seen[num_ifs];
+ memset (&seen, 0, sizeof (seen));
+ sw_interface_dump_ctx dctx = { false, num_ifs, sw_if_indexes, seen, 0 };
+ vapi_msg_sw_interface_dump *dump = vapi_alloc_sw_interface_dump (ctx);
+ dump->payload.name_filter_valid = 0;
+ memset (dump->payload.name_filter, 0, sizeof (dump->payload.name_filter));
+ while (VAPI_EAGAIN ==
+ (rv =
+ vapi_sw_interface_dump (ctx, dump, sw_interface_dump_cb, &dctx)))
+ ;
+ for (i = 0; i < num_ifs; ++i)
+ {
+ ck_assert_int_eq (false, seen[i]);
+ }
+ memset (&seen, 0, sizeof (seen));
+ ck_assert_int_eq (false, dctx.last_called);
+ rv = vapi_dispatch (ctx);
+ ck_assert_int_eq (VAPI_OK, rv);
+ for (i = 0; i < num_ifs; ++i)
+ {
+ ck_assert_int_eq (true, seen[i]);
+ }
+ memset (&seen, 0, sizeof (seen));
+ ck_assert_int_eq (true, dctx.last_called);
+ for (i = 0; i < num_ifs; ++i)
+ {
+ vapi_msg_delete_loopback *dl = vapi_alloc_delete_loopback (ctx);
+ dl->payload.sw_if_index = sw_if_indexes[i];
+ while (VAPI_EAGAIN ==
+ (rv =
+ vapi_delete_loopback (ctx, dl, loopback_delete_cb, &dlcs[i])))
+ ;
+ ck_assert_int_eq (VAPI_OK, rv);
+ }
+ rv = vapi_dispatch (ctx);
+ ck_assert_int_eq (VAPI_OK, rv);
+ for (i = 0; i < num_ifs; ++i)
+ {
+ ck_assert_int_eq (1, dlcs[i].called);
+ printf ("Deleted loopback with sw_if_index %u\n", sw_if_indexes[i]);
+ }
+ memset (&seen, 0, sizeof (seen));
+ dctx.last_called = false;
+ dump = vapi_alloc_sw_interface_dump (ctx);
+ dump->payload.name_filter_valid = 0;
+ memset (dump->payload.name_filter, 0, sizeof (dump->payload.name_filter));
+ while (VAPI_EAGAIN ==
+ (rv =
+ vapi_sw_interface_dump (ctx, dump, sw_interface_dump_cb, &dctx)))
+ ;
+ rv = vapi_dispatch (ctx);
+ ck_assert_int_eq (VAPI_OK, rv);
+ for (i = 0; i < num_ifs; ++i)
+ {
+ ck_assert_int_eq (false, seen[i]);
+ }
+ memset (&seen, 0, sizeof (seen));
+ ck_assert_int_eq (true, dctx.last_called);
+}
+
+END_TEST;
+
+vapi_error_e
+interface_simple_stats_cb (vapi_ctx_t ctx, void *callback_ctx,
+ vapi_error_e rv, bool is_last,
+ vapi_payload_want_interface_simple_stats_reply *
+ payload)
+{
+ return VAPI_OK;
+}
+
+vapi_error_e
+simple_counters_cb (vapi_ctx_t ctx, void *callback_ctx,
+ vapi_payload_vnet_interface_simple_counters * payload)
+{
+ int *called = callback_ctx;
+ ++*called;
+ printf ("simple counters: first_sw_if_index=%u\n",
+ payload->first_sw_if_index);
+ return VAPI_OK;
+}
+
+START_TEST (test_stats_1)
+{
+ printf ("--- Receive stats using generic blocking API ---\n");
+ vapi_msg_want_interface_simple_stats *ws =
+ vapi_alloc_want_interface_simple_stats (ctx);
+ ws->payload.enable_disable = 1;
+ ws->payload.pid = getpid ();
+ vapi_error_e rv;
+ rv = vapi_want_interface_simple_stats (ctx, ws, interface_simple_stats_cb,
+ NULL);
+ ck_assert_int_eq (VAPI_OK, rv);
+ int called = 0;
+ vapi_set_event_cb (ctx, vapi_msg_id_vnet_interface_simple_counters,
+ (vapi_event_cb) simple_counters_cb, &called);
+ rv = vapi_dispatch_one (ctx);
+ ck_assert_int_eq (VAPI_OK, rv);
+ ck_assert_int_eq (1, called);
+}
+
+END_TEST;
+
+START_TEST (test_stats_2)
+{
+ printf ("--- Receive stats using stat-specific blocking API ---\n");
+ vapi_msg_want_interface_simple_stats *ws =
+ vapi_alloc_want_interface_simple_stats (ctx);
+ ws->payload.enable_disable = 1;
+ ws->payload.pid = getpid ();
+ vapi_error_e rv;
+ rv = vapi_want_interface_simple_stats (ctx, ws, interface_simple_stats_cb,
+ NULL);
+ ck_assert_int_eq (VAPI_OK, rv);
+ int called = 0;
+ vapi_set_vapi_msg_vnet_interface_simple_counters_event_cb (ctx,
+ simple_counters_cb,
+ &called);
+ rv = vapi_dispatch_one (ctx);
+ ck_assert_int_eq (VAPI_OK, rv);
+ ck_assert_int_eq (1, called);
+}
+
+END_TEST;
+
+vapi_error_e
+generic_cb (vapi_ctx_t ctx, void *callback_ctx, vapi_msg_id_t id, void *msg)
+{
+ int *called = callback_ctx;
+ ck_assert_int_eq (0, *called);
+ ++*called;
+ ck_assert_int_eq (id, vapi_msg_id_show_version_reply);
+ ck_assert_ptr_ne (NULL, msg);
+ vapi_msg_show_version_reply *reply = msg;
+ ck_assert_str_eq ("vpe", (char *) reply->payload.program);
+ return VAPI_OK;
+}
+
+START_TEST (test_show_version_5)
+{
+ printf ("--- Receive show version using generic callback - nonblocking "
+ "API ---\n");
+ vapi_error_e rv;
+ vapi_msg_show_version *sv = vapi_alloc_show_version (ctx);
+ ck_assert_ptr_ne (NULL, sv);
+ vapi_msg_show_version_hton (sv);
+ while (VAPI_EAGAIN == (rv = vapi_send (ctx, sv)))
+ ;
+ ck_assert_int_eq (VAPI_OK, rv);
+ int called = 0;
+ vapi_set_generic_event_cb (ctx, generic_cb, &called);
+ ck_assert_int_eq (VAPI_OK, rv);
+ rv = vapi_dispatch_one (ctx);
+ ck_assert_int_eq (VAPI_OK, rv);
+ ck_assert_int_eq (1, called);
+ sv = vapi_alloc_show_version (ctx);
+ ck_assert_ptr_ne (NULL, sv);
+ vapi_msg_show_version_hton (sv);
+ while (VAPI_EAGAIN == (rv = vapi_send (ctx, sv)))
+ ;
+ ck_assert_int_eq (VAPI_OK, rv);
+ vapi_clear_generic_event_cb (ctx);
+ rv = vapi_dispatch_one (ctx);
+ ck_assert_int_eq (VAPI_OK, rv);
+ ck_assert_int_eq (1, called); /* needs to remain unchanged */
+}
+
+END_TEST;
+
+vapi_error_e
+combined_counters_cb (struct vapi_ctx_s *ctx, void *callback_ctx,
+ vapi_payload_vnet_interface_combined_counters * payload)
+{
+ int *called = callback_ctx;
+ ++*called;
+ printf ("combined counters: first_sw_if_index=%u\n",
+ payload->first_sw_if_index);
+ return VAPI_OK;
+}
+
+vapi_error_e
+stats_cb (vapi_ctx_t ctx, void *callback_ctx, vapi_error_e rv,
+ bool is_last, vapi_payload_want_stats_reply * payload)
+{
+ return VAPI_OK;
+}
+
+START_TEST (test_stats_3)
+{
+ printf ("--- Receive multiple stats using stat-specific non-blocking API "
+ "---\n");
+ vapi_msg_want_stats *ws = vapi_alloc_want_stats (ctx);
+ ws->payload.enable_disable = 1;
+ ws->payload.pid = getpid ();
+ vapi_error_e rv;
+ rv = vapi_want_stats (ctx, ws, stats_cb, NULL);
+ ck_assert_int_eq (VAPI_OK, rv);
+ int called = 0;
+ int called2 = 0;
+ vapi_set_vapi_msg_vnet_interface_simple_counters_event_cb (ctx,
+ simple_counters_cb,
+ &called);
+ vapi_set_vapi_msg_vnet_interface_combined_counters_event_cb (ctx,
+ combined_counters_cb,
+ &called2);
+ while (!called || !called2)
+ {
+ if (VAPI_EAGAIN != (rv = vapi_dispatch_one (ctx)))
+ {
+ ck_assert_int_eq (VAPI_OK, rv);
+ }
+ }
+}
+
+END_TEST;
+
+vapi_error_e
+show_version_no_cb (vapi_ctx_t ctx, void *caller_ctx,
+ vapi_error_e rv, bool is_last,
+ vapi_payload_show_version_reply * p)
+{
+ ck_assert_int_eq (VAPI_ENORESP, rv);
+ ck_assert_int_eq (true, is_last);
+ ck_assert_ptr_eq (NULL, p);
+ ++*(int *) caller_ctx;
+ return VAPI_OK;
+}
+
+START_TEST (test_no_response_1)
+{
+ printf ("--- Simulate no response to regular message ---\n");
+ vapi_error_e rv;
+ vapi_msg_show_version *sv = vapi_alloc_show_version (ctx);
+ ck_assert_ptr_ne (NULL, sv);
+ sv->header._vl_msg_id = ~0; /* malformed ID causes vpp to drop the msg */
+ int called = 0;
+ while (VAPI_EAGAIN ==
+ (rv = vapi_show_version (ctx, sv, show_version_no_cb, &called)))
+ ;
+ ck_assert_int_eq (VAPI_OK, rv);
+ sv = vapi_alloc_show_version (ctx);
+ ck_assert_ptr_ne (NULL, sv);
+ while (VAPI_EAGAIN ==
+ (rv = vapi_show_version (ctx, sv, show_version_cb, &called)))
+ ;
+ ck_assert_int_eq (VAPI_OK, rv);
+ rv = vapi_dispatch (ctx);
+ ck_assert_int_eq (VAPI_OK, rv);
+ ck_assert_int_eq (2, called);
+}
+
+END_TEST;
+
+vapi_error_e
+no_msg_cb (struct vapi_ctx_s *ctx, void *callback_ctx,
+ vapi_error_e rv, bool is_last,
+ vapi_payload_sw_interface_details * reply)
+{
+ int *called = callback_ctx;
+ ++*called;
+ ck_assert_int_eq (VAPI_OK, rv);
+ ck_assert_int_eq (true, is_last);
+ ck_assert_ptr_eq (NULL, reply);
+ return VAPI_OK;
+}
+
+START_TEST (test_no_response_2)
+{
+ printf ("--- Simulate no response to dump message ---\n");
+ vapi_error_e rv;
+ vapi_msg_sw_interface_dump *dump = vapi_alloc_sw_interface_dump (ctx);
+ dump->header._vl_msg_id = ~0; /* malformed ID causes vpp to drop the msg */
+ int no_called = 0;
+ while (VAPI_EAGAIN ==
+ (rv = vapi_sw_interface_dump (ctx, dump, no_msg_cb, &no_called)))
+ ;
+ ck_assert_int_eq (VAPI_OK, rv);
+ rv = vapi_dispatch (ctx);
+ ck_assert_int_eq (VAPI_OK, rv);
+ ck_assert_int_eq (1, no_called);
+}
+
+END_TEST;
+
+START_TEST (test_unsupported)
+{
+ printf ("--- Unsupported messages ---\n");
+ bool available = vapi_is_msg_available (ctx, vapi_msg_id_test_fake_msg);
+ ck_assert_int_eq (false, available);
+}
+
+END_TEST;
+
+Suite *
+test_suite (void)
+{
+ Suite *s = suite_create ("VAPI test");
+
+ TCase *tc_negative = tcase_create ("Negative tests");
+ tcase_add_test (tc_negative, test_invalid_values);
+ suite_add_tcase (s, tc_negative);
+
+ TCase *tc_swap = tcase_create ("Byteswap tests");
+ tcase_add_test (tc_swap, test_hton_1);
+ tcase_add_test (tc_swap, test_hton_2);
+ tcase_add_test (tc_swap, test_hton_3);
+ tcase_add_test (tc_swap, test_hton_4);
+ tcase_add_test (tc_swap, test_ntoh_1);
+ tcase_add_test (tc_swap, test_ntoh_2);
+ tcase_add_test (tc_swap, test_ntoh_3);
+ tcase_add_test (tc_swap, test_ntoh_4);
+ suite_add_tcase (s, tc_swap);
+
+ TCase *tc_connect = tcase_create ("Connect");
+ tcase_add_test (tc_connect, test_connect);
+ suite_add_tcase (s, tc_connect);
+
+ TCase *tc_block = tcase_create ("Blocking API");
+ tcase_set_timeout (tc_block, 25);
+ tcase_add_checked_fixture (tc_block, setup_blocking, teardown);
+ tcase_add_test (tc_block, test_show_version_1);
+ tcase_add_test (tc_block, test_show_version_2);
+ tcase_add_test (tc_block, test_loopbacks_1);
+ tcase_add_test (tc_block, test_stats_1);
+ tcase_add_test (tc_block, test_stats_2);
+ suite_add_tcase (s, tc_block);
+
+ TCase *tc_nonblock = tcase_create ("Nonblocking API");
+ tcase_set_timeout (tc_nonblock, 25);
+ tcase_add_checked_fixture (tc_nonblock, setup_nonblocking, teardown);
+ tcase_add_test (tc_nonblock, test_show_version_3);
+ tcase_add_test (tc_nonblock, test_show_version_4);
+ tcase_add_test (tc_nonblock, test_show_version_5);
+ tcase_add_test (tc_nonblock, test_loopbacks_2);
+ tcase_add_test (tc_nonblock, test_stats_3);
+ tcase_add_test (tc_nonblock, test_no_response_1);
+ tcase_add_test (tc_nonblock, test_no_response_2);
+ suite_add_tcase (s, tc_nonblock);
+
+ TCase *tc_unsupported = tcase_create ("Unsupported message");
+ tcase_add_checked_fixture (tc_unsupported, setup_blocking, teardown);
+ tcase_add_test (tc_unsupported, test_unsupported);
+ suite_add_tcase (s, tc_unsupported);
+
+ return s;
+}
+
+int
+main (int argc, char *argv[])
+{
+ if (3 != argc)
+ {
+ printf ("Invalid argc==`%d'\n", argc);
+ return EXIT_FAILURE;
+ }
+ app_name = argv[1];
+ api_prefix = argv[2];
+ printf ("App name: `%s', API prefix: `%s'\n", app_name, api_prefix);
+
+ int number_failed;
+ Suite *s;
+ SRunner *sr;
+
+ s = test_suite ();
+ sr = srunner_create (s);
+
+ srunner_run_all (sr, CK_NORMAL);
+ number_failed = srunner_ntests_failed (sr);
+ srunner_free (sr);
+ return (number_failed == 0) ? EXIT_SUCCESS : EXIT_FAILURE;
+}
+
+/*
+ * fd.io coding-style-patch-verification: ON
+ *
+ * Local Variables:
+ * eval: (c-set-style "gnu")
+ * End:
+ */
diff --git a/test/ext/vapi_cpp_test.cpp b/test/ext/vapi_cpp_test.cpp
new file mode 100644
index 00000000..14c35d5b
--- /dev/null
+++ b/test/ext/vapi_cpp_test.cpp
@@ -0,0 +1,591 @@
+/*
+ *------------------------------------------------------------------
+ * Copyright (c) 2017 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.
+ *------------------------------------------------------------------
+ */
+
+#include <memory>
+#include <stdio.h>
+#include <unistd.h>
+#include <assert.h>
+#include <setjmp.h>
+#include <check.h>
+#include <vapi/vapi.hpp>
+#include <vapi/vpe.api.vapi.hpp>
+#include <vapi/interface.api.vapi.hpp>
+#include <vapi/stats.api.vapi.hpp>
+#include <fake.api.vapi.hpp>
+
+DEFINE_VAPI_MSG_IDS_VPE_API_JSON;
+DEFINE_VAPI_MSG_IDS_INTERFACE_API_JSON;
+DEFINE_VAPI_MSG_IDS_STATS_API_JSON;
+DEFINE_VAPI_MSG_IDS_FAKE_API_JSON;
+
+static char *app_name = nullptr;
+static char *api_prefix = nullptr;
+static const int max_outstanding_requests = 32;
+static const int response_queue_size = 32;
+
+using namespace vapi;
+
+void verify_show_version_reply (const Show_version_reply &r)
+{
+ auto &p = r.get_payload ();
+ printf ("show_version_reply: program: `%s', version: `%s', build directory: "
+ "`%s', build date: `%s'\n",
+ p.program, p.version, p.build_directory, p.build_date);
+ ck_assert_str_eq ("vpe", (char *)p.program);
+}
+
+Connection con;
+
+void setup (void)
+{
+ vapi_error_e rv = con.connect (
+ app_name, api_prefix, max_outstanding_requests, response_queue_size);
+ ck_assert_int_eq (VAPI_OK, rv);
+}
+
+void teardown (void)
+{
+ con.disconnect ();
+}
+
+START_TEST (test_show_version_1)
+{
+ printf ("--- Show version by reading response associated to request ---\n");
+ Show_version sv (con);
+ vapi_error_e rv = sv.execute ();
+ ck_assert_int_eq (VAPI_OK, rv);
+ rv = con.wait_for_response (sv);
+ ck_assert_int_eq (VAPI_OK, rv);
+ auto &r = sv.get_response ();
+ verify_show_version_reply (r);
+}
+
+END_TEST;
+
+struct Show_version_cb
+{
+ Show_version_cb () : called{0} {};
+ int called;
+ vapi_error_e operator() (Show_version &sv)
+ {
+ auto &r = sv.get_response ();
+ verify_show_version_reply (r);
+ ++called;
+ return VAPI_OK;
+ }
+};
+
+START_TEST (test_show_version_2)
+{
+ printf ("--- Show version by getting a callback ---\n");
+ Show_version_cb cb;
+ Show_version sv (con, std::ref (cb));
+ vapi_error_e rv = sv.execute ();
+ ck_assert_int_eq (VAPI_OK, rv);
+ con.dispatch (sv);
+ ck_assert_int_eq (1, cb.called);
+}
+
+END_TEST;
+
+START_TEST (test_loopbacks_1)
+{
+ printf ("--- Create/delete loopbacks by waiting for response ---\n");
+ const auto num_ifs = 5;
+ u8 mac_addresses[num_ifs][6];
+ memset (&mac_addresses, 0, sizeof (mac_addresses));
+ u32 sw_if_indexes[num_ifs];
+ memset (&sw_if_indexes, 0xff, sizeof (sw_if_indexes));
+ for (int i = 0; i < num_ifs; ++i)
+ {
+ memcpy (&mac_addresses[i], "\1\2\3\4\5\6", 6);
+ mac_addresses[i][5] = i;
+ }
+ for (int i = 0; i < num_ifs; ++i)
+ {
+ Create_loopback cl (con);
+ auto &p = cl.get_request ().get_payload ();
+ memcpy (p.mac_address, mac_addresses[i], sizeof (p.mac_address));
+ auto e = cl.execute ();
+ ck_assert_int_eq (VAPI_OK, e);
+ vapi_error_e rv = con.wait_for_response (cl);
+ ck_assert_int_eq (VAPI_OK, rv);
+ auto &rp = cl.get_response ().get_payload ();
+ ck_assert_int_eq (0, rp.retval);
+ sw_if_indexes[i] = rp.sw_if_index;
+ }
+ for (int i = 0; i < num_ifs; ++i)
+ {
+ printf ("Created loopback with MAC %02x:%02x:%02x:%02x:%02x:%02x --> "
+ "sw_if_index %u\n",
+ mac_addresses[i][0], mac_addresses[i][1], mac_addresses[i][2],
+ mac_addresses[i][3], mac_addresses[i][4], mac_addresses[i][5],
+ sw_if_indexes[i]);
+ }
+
+ { // new context
+ bool seen[num_ifs] = {0};
+ Sw_interface_dump d (con);
+ auto &p = d.get_request ().get_payload ();
+ p.name_filter_valid = 0;
+ memset (p.name_filter, 0, sizeof (p.name_filter));
+ auto rv = d.execute ();
+ ck_assert_int_eq (VAPI_OK, rv);
+ rv = con.wait_for_response (d);
+ ck_assert_int_eq (VAPI_OK, rv);
+ auto &rs = d.get_result_set ();
+ for (auto &r : rs)
+ {
+ auto &p = r.get_payload ();
+ for (int i = 0; i < num_ifs; ++i)
+ {
+ if (sw_if_indexes[i] == p.sw_if_index)
+ {
+ ck_assert_int_eq (0, seen[i]);
+ seen[i] = true;
+ }
+ }
+ }
+ for (int i = 0; i < num_ifs; ++i)
+ {
+ ck_assert_int_eq (1, seen[i]);
+ }
+ }
+
+ for (int i = 0; i < num_ifs; ++i)
+ {
+ Delete_loopback dl (con);
+ dl.get_request ().get_payload ().sw_if_index = sw_if_indexes[i];
+ auto rv = dl.execute ();
+ ck_assert_int_eq (VAPI_OK, rv);
+ rv = con.wait_for_response (dl);
+ ck_assert_int_eq (VAPI_OK, rv);
+ auto &response = dl.get_response ();
+ auto rp = response.get_payload ();
+ ck_assert_int_eq (0, rp.retval);
+ printf ("Deleted loopback with sw_if_index %u\n", sw_if_indexes[i]);
+ }
+
+ { // new context
+ Sw_interface_dump d (con);
+ auto &p = d.get_request ().get_payload ();
+ p.name_filter_valid = 0;
+ memset (p.name_filter, 0, sizeof (p.name_filter));
+ auto rv = d.execute ();
+ ck_assert_int_eq (VAPI_OK, rv);
+ rv = con.wait_for_response (d);
+ ck_assert_int_eq (VAPI_OK, rv);
+ auto &rs = d.get_result_set ();
+ for (auto &r : rs)
+ {
+ auto &p = r.get_payload ();
+ for (int i = 0; i < num_ifs; ++i)
+ {
+ ck_assert_int_ne (sw_if_indexes[i], p.sw_if_index);
+ }
+ }
+ }
+}
+
+END_TEST;
+
+struct Create_loopback_cb
+{
+ Create_loopback_cb () : called{0}, sw_if_index{0} {};
+ int called;
+ u32 sw_if_index;
+ bool seen;
+ vapi_error_e operator() (Create_loopback &cl)
+ {
+ auto &r = cl.get_response ();
+ sw_if_index = r.get_payload ().sw_if_index;
+ ++called;
+ return VAPI_OK;
+ }
+};
+
+struct Delete_loopback_cb
+{
+ Delete_loopback_cb () : called{0}, sw_if_index{0} {};
+ int called;
+ u32 sw_if_index;
+ bool seen;
+ vapi_error_e operator() (Delete_loopback &dl)
+ {
+ auto &r = dl.get_response ();
+ ck_assert_int_eq (0, r.get_payload ().retval);
+ ++called;
+ return VAPI_OK;
+ }
+};
+
+template <int num_ifs> struct Sw_interface_dump_cb
+{
+ Sw_interface_dump_cb (std::array<Create_loopback_cb, num_ifs> &cbs)
+ : called{0}, cbs{cbs} {};
+ int called;
+ std::array<Create_loopback_cb, num_ifs> &cbs;
+ vapi_error_e operator() (Sw_interface_dump &d)
+ {
+ for (auto &y : cbs)
+ {
+ y.seen = false;
+ }
+ for (auto &x : d.get_result_set ())
+ {
+ auto &p = x.get_payload ();
+ for (auto &y : cbs)
+ {
+ if (p.sw_if_index == y.sw_if_index)
+ {
+ y.seen = true;
+ }
+ }
+ }
+ for (auto &y : cbs)
+ {
+ ck_assert_int_eq (true, y.seen);
+ }
+ ++called;
+ return VAPI_OK;
+ }
+};
+
+START_TEST (test_loopbacks_2)
+{
+ printf ("--- Create/delete loopbacks by getting a callback ---\n");
+ const auto num_ifs = 5;
+ u8 mac_addresses[num_ifs][6];
+ memset (&mac_addresses, 0, sizeof (mac_addresses));
+ for (int i = 0; i < num_ifs; ++i)
+ {
+ memcpy (&mac_addresses[i], "\1\2\3\4\5\6", 6);
+ mac_addresses[i][5] = i;
+ }
+ std::array<Create_loopback_cb, num_ifs> ccbs;
+ std::array<std::unique_ptr<Create_loopback>, num_ifs> clcs;
+ for (int i = 0; i < num_ifs; ++i)
+ {
+ Create_loopback *cl = new Create_loopback (con, std::ref (ccbs[i]));
+ clcs[i].reset (cl);
+ auto &p = cl->get_request ().get_payload ();
+ memcpy (p.mac_address, mac_addresses[i], sizeof (p.mac_address));
+ auto e = cl->execute ();
+ ck_assert_int_eq (VAPI_OK, e);
+ }
+ con.dispatch ();
+ for (int i = 0; i < num_ifs; ++i)
+ {
+ ck_assert_int_eq (1, ccbs[i].called);
+ printf ("Created loopback with MAC %02x:%02x:%02x:%02x:%02x:%02x --> "
+ "sw_if_index %u\n",
+ mac_addresses[i][0], mac_addresses[i][1], mac_addresses[i][2],
+ mac_addresses[i][3], mac_addresses[i][4], mac_addresses[i][5],
+ ccbs[i].sw_if_index);
+ }
+
+ Sw_interface_dump_cb<num_ifs> swdcb (ccbs);
+ Sw_interface_dump d (con, std::ref (swdcb));
+ auto &p = d.get_request ().get_payload ();
+ p.name_filter_valid = 0;
+ memset (p.name_filter, 0, sizeof (p.name_filter));
+ auto rv = d.execute ();
+ ck_assert_int_eq (VAPI_OK, rv);
+ rv = con.wait_for_response (d);
+ ck_assert_int_eq (VAPI_OK, rv);
+ ck_assert_int_ne (0, swdcb.called);
+ std::array<Delete_loopback_cb, num_ifs> dcbs;
+ std::array<std::unique_ptr<Delete_loopback>, num_ifs> dlcs;
+ for (int i = 0; i < num_ifs; ++i)
+ {
+ Delete_loopback *dl = new Delete_loopback (con, std::ref (dcbs[i]));
+ dlcs[i].reset (dl);
+ auto &p = dl->get_request ().get_payload ();
+ p.sw_if_index = ccbs[i].sw_if_index;
+ dcbs[i].sw_if_index = ccbs[i].sw_if_index;
+ auto e = dl->execute ();
+ ck_assert_int_eq (VAPI_OK, e);
+ }
+ con.dispatch ();
+ for (auto &x : dcbs)
+ {
+ ck_assert_int_eq (true, x.called);
+ printf ("Deleted loopback with sw_if_index %u\n", x.sw_if_index);
+ }
+
+ { // new context
+ Sw_interface_dump d (con);
+ auto &p = d.get_request ().get_payload ();
+ p.name_filter_valid = 0;
+ memset (p.name_filter, 0, sizeof (p.name_filter));
+ auto rv = d.execute ();
+ ck_assert_int_eq (VAPI_OK, rv);
+ rv = con.wait_for_response (d);
+ ck_assert_int_eq (VAPI_OK, rv);
+ auto &rs = d.get_result_set ();
+ for (auto &r : rs)
+ {
+ auto &p = r.get_payload ();
+ for (int i = 0; i < num_ifs; ++i)
+ {
+ ck_assert_int_ne (ccbs[i].sw_if_index, p.sw_if_index);
+ }
+ }
+ }
+}
+
+END_TEST;
+
+START_TEST (test_stats_1)
+{
+ printf ("--- Receive single stats by waiting for response ---\n");
+ Want_stats ws (con);
+ auto &payload = ws.get_request ().get_payload ();
+ payload.enable_disable = 1;
+ payload.pid = getpid ();
+ auto rv = ws.execute ();
+ ck_assert_int_eq (VAPI_OK, rv);
+ Event_registration<Vnet_interface_simple_counters> sc (con);
+ rv = con.wait_for_response (sc);
+ ck_assert_int_eq (VAPI_OK, rv);
+ auto &rs = sc.get_result_set ();
+ int count = 0;
+ for (auto &r : rs)
+ {
+ printf ("simple counters: first_sw_if_index=%u\n",
+ r.get_payload ().first_sw_if_index);
+ ++count;
+ }
+ ck_assert_int_ne (0, count);
+}
+
+END_TEST;
+
+struct Vnet_interface_simple_counters_cb
+{
+ Vnet_interface_simple_counters_cb () : called{0} {};
+ int called;
+ vapi_error_e
+ operator() (Event_registration<Vnet_interface_simple_counters> &e)
+ {
+ ++called;
+ auto &rs = e.get_result_set ();
+ int count = 0;
+ for (auto &r : rs)
+ {
+ printf ("simple counters: first_sw_if_index=%u\n",
+ r.get_payload ().first_sw_if_index);
+ ++count;
+ }
+ ck_assert_int_ne (0, count);
+ return VAPI_OK;
+ }
+};
+
+START_TEST (test_stats_2)
+{
+ printf ("--- Receive single stats by getting a callback ---\n");
+ Want_stats ws (con);
+ auto &payload = ws.get_request ().get_payload ();
+ payload.enable_disable = 1;
+ payload.pid = getpid ();
+ auto rv = ws.execute ();
+ ck_assert_int_eq (VAPI_OK, rv);
+ Vnet_interface_simple_counters_cb cb;
+ Event_registration<Vnet_interface_simple_counters> sc (con, std::ref (cb));
+ rv = con.wait_for_response (sc);
+ ck_assert_int_eq (VAPI_OK, rv);
+ ck_assert_int_ne (0, cb.called);
+}
+
+END_TEST;
+
+struct Vnet_interface_simple_counters_2_cb
+{
+ Vnet_interface_simple_counters_2_cb () : called{0}, total{0} {};
+ int called;
+ int total;
+ vapi_error_e
+ operator() (Event_registration<Vnet_interface_simple_counters> &e)
+ {
+ ++called;
+ auto &rs = e.get_result_set ();
+ int count = 0;
+ for (auto &r : rs)
+ {
+ printf ("simple counters: first_sw_if_index=%u\n",
+ r.get_payload ().first_sw_if_index);
+ ++count;
+ }
+ rs.free_all_responses ();
+ ck_assert_int_ne (0, count);
+ total += count;
+ return VAPI_OK;
+ }
+};
+
+START_TEST (test_stats_3)
+{
+ printf (
+ "--- Receive single stats by getting a callback - clear results ---\n");
+ Want_stats ws (con);
+ auto &payload = ws.get_request ().get_payload ();
+ payload.enable_disable = 1;
+ payload.pid = getpid ();
+ auto rv = ws.execute ();
+ ck_assert_int_eq (VAPI_OK, rv);
+ Vnet_interface_simple_counters_2_cb cb;
+ Event_registration<Vnet_interface_simple_counters> sc (con, std::ref (cb));
+ for (int i = 0; i < 5; ++i)
+ {
+ rv = con.wait_for_response (sc);
+ }
+ ck_assert_int_eq (VAPI_OK, rv);
+ ck_assert_int_eq (5, cb.called);
+ ck_assert_int_eq (5, cb.total);
+}
+
+END_TEST;
+
+START_TEST (test_stats_4)
+{
+ printf ("--- Receive multiple stats by waiting for response ---\n");
+ Want_stats ws (con);
+ auto &payload = ws.get_request ().get_payload ();
+ payload.enable_disable = 1;
+ payload.pid = getpid ();
+ auto rv = ws.execute ();
+ ck_assert_int_eq (VAPI_OK, rv);
+ Event_registration<Vnet_interface_simple_counters> sc (con);
+ Event_registration<Vnet_interface_combined_counters> cc (con);
+ rv = con.wait_for_response (sc);
+ ck_assert_int_eq (VAPI_OK, rv);
+ rv = con.wait_for_response (cc);
+ ck_assert_int_eq (VAPI_OK, rv);
+ int count = 0;
+ for (auto &r : sc.get_result_set ())
+ {
+ printf ("simple counters: first_sw_if_index=%u\n",
+ r.get_payload ().first_sw_if_index);
+ ++count;
+ }
+ ck_assert_int_ne (0, count);
+ count = 0;
+ for (auto &r : cc.get_result_set ())
+ {
+ printf ("combined counters: first_sw_if_index=%u\n",
+ r.get_payload ().first_sw_if_index);
+ ++count;
+ }
+ ck_assert_int_ne (0, count);
+}
+
+END_TEST;
+
+START_TEST (test_unsupported)
+{
+ printf ("--- Unsupported messages ---\n");
+ bool thrown = false;
+ try
+ {
+ Test_fake_msg fake (con);
+ }
+ catch (const Msg_not_available_exception &)
+ {
+ thrown = true;
+ printf ("Constructing unsupported msg not possible - test pass.\n");
+ }
+ ck_assert_int_eq (true, thrown);
+ thrown = false;
+ try
+ {
+ Test_fake_dump fake (con);
+ }
+ catch (const Msg_not_available_exception &)
+ {
+ thrown = true;
+ printf ("Constructing unsupported dump not possible - test pass.\n");
+ }
+ ck_assert_int_eq (true, thrown);
+ thrown = false;
+ try
+ {
+ Event_registration<Test_fake_details> fake (con);
+ }
+ catch (const Msg_not_available_exception &)
+ {
+ thrown = true;
+ printf ("Constructing unsupported event registration not possible - "
+ "test pass.\n");
+ }
+ ck_assert_int_eq (true, thrown);
+}
+
+END_TEST;
+
+Suite *test_suite (void)
+{
+ Suite *s = suite_create ("VAPI test");
+
+ TCase *tc_cpp_api = tcase_create ("C++ API");
+ tcase_set_timeout (tc_cpp_api, 25);
+ tcase_add_checked_fixture (tc_cpp_api, setup, teardown);
+ tcase_add_test (tc_cpp_api, test_show_version_1);
+ tcase_add_test (tc_cpp_api, test_show_version_2);
+ tcase_add_test (tc_cpp_api, test_loopbacks_1);
+ tcase_add_test (tc_cpp_api, test_loopbacks_2);
+ tcase_add_test (tc_cpp_api, test_stats_1);
+ tcase_add_test (tc_cpp_api, test_stats_2);
+ tcase_add_test (tc_cpp_api, test_stats_3);
+ tcase_add_test (tc_cpp_api, test_stats_4);
+ tcase_add_test (tc_cpp_api, test_unsupported);
+ suite_add_tcase (s, tc_cpp_api);
+
+ return s;
+}
+
+int main (int argc, char *argv[])
+{
+ if (3 != argc)
+ {
+ printf ("Invalid argc==`%d'\n", argc);
+ return EXIT_FAILURE;
+ }
+ app_name = argv[1];
+ api_prefix = argv[2];
+ printf ("App name: `%s', API prefix: `%s'\n", app_name, api_prefix);
+
+ int number_failed;
+ Suite *s;
+ SRunner *sr;
+
+ s = test_suite ();
+ sr = srunner_create (s);
+
+ srunner_run_all (sr, CK_NORMAL);
+ number_failed = srunner_ntests_failed (sr);
+ srunner_free (sr);
+ return (number_failed == 0) ? EXIT_SUCCESS : EXIT_FAILURE;
+}
+
+/*
+ * fd.io coding-style-patch-verification: ON
+ *
+ * Local Variables:
+ * eval: (c-set-style "gnu")
+ * End:
+ */
diff --git a/test/framework.py b/test/framework.py
new file mode 100644
index 00000000..600a0e54
--- /dev/null
+++ b/test/framework.py
@@ -0,0 +1,1017 @@
+#!/usr/bin/env python
+
+from __future__ import print_function
+import gc
+import sys
+import os
+import select
+import unittest
+import tempfile
+import time
+import resource
+import faulthandler
+from collections import deque
+from threading import Thread, Event
+from inspect import getdoc, isclass
+from traceback import format_exception
+from logging import FileHandler, DEBUG, Formatter
+from scapy.packet import Raw
+from hook import StepHook, PollHook
+from vpp_pg_interface import VppPGInterface
+from vpp_sub_interface import VppSubInterface
+from vpp_lo_interface import VppLoInterface
+from vpp_papi_provider import VppPapiProvider
+from log import *
+from vpp_object import VppObjectRegistry
+if os.name == 'posix' and sys.version_info[0] < 3:
+ # using subprocess32 is recommended by python official documentation
+ # @ https://docs.python.org/2/library/subprocess.html
+ import subprocess32 as subprocess
+else:
+ import subprocess
+
+"""
+ Test framework module.
+
+ The module provides a set of tools for constructing and running tests and
+ representing the results.
+"""
+
+
+class _PacketInfo(object):
+ """Private class to create packet info object.
+
+ Help process information about the next packet.
+ Set variables to default values.
+ """
+ #: Store the index of the packet.
+ index = -1
+ #: Store the index of the source packet generator interface of the packet.
+ src = -1
+ #: Store the index of the destination packet generator interface
+ #: of the packet.
+ dst = -1
+ #: Store expected ip version
+ ip = -1
+ #: Store expected upper protocol
+ proto = -1
+ #: Store the copy of the former packet.
+ data = None
+
+ def __eq__(self, other):
+ index = self.index == other.index
+ src = self.src == other.src
+ dst = self.dst == other.dst
+ data = self.data == other.data
+ return index and src and dst and data
+
+
+def pump_output(testclass):
+ """ pump output from vpp stdout/stderr to proper queues """
+ while not testclass.pump_thread_stop_flag.wait(0):
+ readable = select.select([testclass.vpp.stdout.fileno(),
+ testclass.vpp.stderr.fileno(),
+ testclass.pump_thread_wakeup_pipe[0]],
+ [], [])[0]
+ if testclass.vpp.stdout.fileno() in readable:
+ read = os.read(testclass.vpp.stdout.fileno(), 1024)
+ testclass.vpp_stdout_deque.append(read)
+ if testclass.vpp.stderr.fileno() in readable:
+ read = os.read(testclass.vpp.stderr.fileno(), 1024)
+ testclass.vpp_stderr_deque.append(read)
+ # ignoring the dummy pipe here intentionally - the flag will take care
+ # of properly terminating the loop
+
+
+def running_extended_tests():
+ try:
+ s = os.getenv("EXTENDED_TESTS")
+ return True if s.lower() in ("y", "yes", "1") else False
+ except:
+ return False
+ return False
+
+
+class KeepAliveReporter(object):
+ """
+ Singleton object which reports test start to parent process
+ """
+ _shared_state = {}
+
+ def __init__(self):
+ self.__dict__ = self._shared_state
+
+ @property
+ def pipe(self):
+ return self._pipe
+
+ @pipe.setter
+ def pipe(self, pipe):
+ if hasattr(self, '_pipe'):
+ raise Exception("Internal error - pipe should only be set once.")
+ self._pipe = pipe
+
+ def send_keep_alive(self, test):
+ """
+ Write current test tmpdir & desc to keep-alive pipe to signal liveness
+ """
+ if self.pipe is None:
+ # if not running forked..
+ return
+
+ if isclass(test):
+ desc = test.__name__
+ else:
+ desc = test.shortDescription()
+ if not desc:
+ desc = str(test)
+
+ self.pipe.send((desc, test.vpp_bin, test.tempdir, test.vpp.pid))
+
+
+class VppTestCase(unittest.TestCase):
+ """This subclass is a base class for VPP test cases that are implemented as
+ classes. It provides methods to create and run test case.
+ """
+
+ @property
+ def packet_infos(self):
+ """List of packet infos"""
+ return self._packet_infos
+
+ @classmethod
+ def get_packet_count_for_if_idx(cls, dst_if_index):
+ """Get the number of packet info for specified destination if index"""
+ if dst_if_index in cls._packet_count_for_dst_if_idx:
+ return cls._packet_count_for_dst_if_idx[dst_if_index]
+ else:
+ return 0
+
+ @classmethod
+ def instance(cls):
+ """Return the instance of this testcase"""
+ return cls.test_instance
+
+ @classmethod
+ def set_debug_flags(cls, d):
+ cls.debug_core = False
+ cls.debug_gdb = False
+ cls.debug_gdbserver = False
+ if d is None:
+ return
+ dl = d.lower()
+ if dl == "core":
+ cls.debug_core = True
+ elif dl == "gdb":
+ cls.debug_gdb = True
+ elif dl == "gdbserver":
+ cls.debug_gdbserver = True
+ else:
+ raise Exception("Unrecognized DEBUG option: '%s'" % d)
+
+ @classmethod
+ def setUpConstants(cls):
+ """ Set-up the test case class based on environment variables """
+ try:
+ s = os.getenv("STEP")
+ cls.step = True if s.lower() in ("y", "yes", "1") else False
+ except:
+ cls.step = False
+ try:
+ d = os.getenv("DEBUG")
+ except:
+ d = None
+ cls.set_debug_flags(d)
+ cls.vpp_bin = os.getenv('VPP_TEST_BIN', "vpp")
+ cls.plugin_path = os.getenv('VPP_TEST_PLUGIN_PATH')
+ cls.extern_plugin_path = os.getenv('EXTERN_PLUGINS')
+ plugin_path = None
+ if cls.plugin_path is not None:
+ if cls.extern_plugin_path is not None:
+ plugin_path = "%s:%s" % (
+ cls.plugin_path, cls.extern_plugin_path)
+ else:
+ plugin_path = cls.plugin_path
+ elif cls.extern_plugin_path is not None:
+ plugin_path = cls.extern_plugin_path
+ debug_cli = ""
+ if cls.step or cls.debug_gdb or cls.debug_gdbserver:
+ debug_cli = "cli-listen localhost:5002"
+ coredump_size = None
+ try:
+ size = os.getenv("COREDUMP_SIZE")
+ if size is not None:
+ coredump_size = "coredump-size %s" % size
+ except:
+ pass
+ if coredump_size is None:
+ coredump_size = "coredump-size unlimited"
+ cls.vpp_cmdline = [cls.vpp_bin, "unix",
+ "{", "nodaemon", debug_cli, "full-coredump",
+ coredump_size, "}", "api-trace", "{", "on", "}",
+ "api-segment", "{", "prefix", cls.shm_prefix, "}",
+ "plugins", "{", "plugin", "dpdk_plugin.so", "{",
+ "disable", "}", "}"]
+ if plugin_path is not None:
+ cls.vpp_cmdline.extend(["plugin_path", plugin_path])
+ cls.logger.info("vpp_cmdline: %s" % cls.vpp_cmdline)
+
+ @classmethod
+ def wait_for_enter(cls):
+ if cls.debug_gdbserver:
+ print(double_line_delim)
+ print("Spawned GDB server with PID: %d" % cls.vpp.pid)
+ elif cls.debug_gdb:
+ print(double_line_delim)
+ print("Spawned VPP with PID: %d" % cls.vpp.pid)
+ else:
+ cls.logger.debug("Spawned VPP with PID: %d" % cls.vpp.pid)
+ return
+ print(single_line_delim)
+ print("You can debug the VPP using e.g.:")
+ if cls.debug_gdbserver:
+ print("gdb " + cls.vpp_bin + " -ex 'target remote localhost:7777'")
+ print("Now is the time to attach a gdb by running the above "
+ "command, set up breakpoints etc. and then resume VPP from "
+ "within gdb by issuing the 'continue' command")
+ elif cls.debug_gdb:
+ print("gdb " + cls.vpp_bin + " -ex 'attach %s'" % cls.vpp.pid)
+ print("Now is the time to attach a gdb by running the above "
+ "command and set up breakpoints etc.")
+ print(single_line_delim)
+ raw_input("Press ENTER to continue running the testcase...")
+
+ @classmethod
+ def run_vpp(cls):
+ cmdline = cls.vpp_cmdline
+
+ if cls.debug_gdbserver:
+ gdbserver = '/usr/bin/gdbserver'
+ if not os.path.isfile(gdbserver) or \
+ not os.access(gdbserver, os.X_OK):
+ raise Exception("gdbserver binary '%s' does not exist or is "
+ "not executable" % gdbserver)
+
+ cmdline = [gdbserver, 'localhost:7777'] + cls.vpp_cmdline
+ cls.logger.info("Gdbserver cmdline is %s", " ".join(cmdline))
+
+ try:
+ cls.vpp = subprocess.Popen(cmdline,
+ stdout=subprocess.PIPE,
+ stderr=subprocess.PIPE,
+ bufsize=1)
+ except Exception as e:
+ cls.logger.critical("Couldn't start vpp: %s" % e)
+ raise
+
+ cls.wait_for_enter()
+
+ @classmethod
+ def setUpClass(cls):
+ """
+ Perform class setup before running the testcase
+ Remove shared memory files, start vpp and connect the vpp-api
+ """
+ gc.collect() # run garbage collection first
+ cls.logger = getLogger(cls.__name__)
+ cls.tempdir = tempfile.mkdtemp(
+ prefix='vpp-unittest-%s-' % cls.__name__)
+ cls.file_handler = FileHandler("%s/log.txt" % cls.tempdir)
+ cls.file_handler.setFormatter(
+ Formatter(fmt='%(asctime)s,%(msecs)03d %(message)s',
+ datefmt="%H:%M:%S"))
+ cls.file_handler.setLevel(DEBUG)
+ cls.logger.addHandler(cls.file_handler)
+ cls.shm_prefix = cls.tempdir.split("/")[-1]
+ os.chdir(cls.tempdir)
+ cls.logger.info("Temporary dir is %s, shm prefix is %s",
+ cls.tempdir, cls.shm_prefix)
+ cls.setUpConstants()
+ cls.reset_packet_infos()
+ cls._captures = []
+ cls._zombie_captures = []
+ cls.verbose = 0
+ cls.vpp_dead = False
+ cls.registry = VppObjectRegistry()
+ cls.vpp_startup_failed = False
+ cls.reporter = KeepAliveReporter()
+ # need to catch exceptions here because if we raise, then the cleanup
+ # doesn't get called and we might end with a zombie vpp
+ try:
+ cls.run_vpp()
+ cls.reporter.send_keep_alive(cls)
+ cls.vpp_stdout_deque = deque()
+ cls.vpp_stderr_deque = deque()
+ cls.pump_thread_stop_flag = Event()
+ cls.pump_thread_wakeup_pipe = os.pipe()
+ cls.pump_thread = Thread(target=pump_output, args=(cls,))
+ cls.pump_thread.daemon = True
+ cls.pump_thread.start()
+ cls.vapi = VppPapiProvider(cls.shm_prefix, cls.shm_prefix, cls)
+ if cls.step:
+ hook = StepHook(cls)
+ else:
+ hook = PollHook(cls)
+ cls.vapi.register_hook(hook)
+ cls.sleep(0.1, "after vpp startup, before initial poll")
+ try:
+ hook.poll_vpp()
+ except:
+ cls.vpp_startup_failed = True
+ cls.logger.critical(
+ "VPP died shortly after startup, check the"
+ " output to standard error for possible cause")
+ raise
+ try:
+ cls.vapi.connect()
+ except:
+ if cls.debug_gdbserver:
+ print(colorize("You're running VPP inside gdbserver but "
+ "VPP-API connection failed, did you forget "
+ "to 'continue' VPP from within gdb?", RED))
+ raise
+ except:
+ t, v, tb = sys.exc_info()
+ try:
+ cls.quit()
+ except:
+ pass
+ raise t, v, tb
+
+ @classmethod
+ def quit(cls):
+ """
+ Disconnect vpp-api, kill vpp and cleanup shared memory files
+ """
+ if (cls.debug_gdbserver or cls.debug_gdb) and hasattr(cls, 'vpp'):
+ cls.vpp.poll()
+ if cls.vpp.returncode is None:
+ print(double_line_delim)
+ print("VPP or GDB server is still running")
+ print(single_line_delim)
+ raw_input("When done debugging, press ENTER to kill the "
+ "process and finish running the testcase...")
+
+ os.write(cls.pump_thread_wakeup_pipe[1], 'ding dong wake up')
+ cls.pump_thread_stop_flag.set()
+ if hasattr(cls, 'pump_thread'):
+ cls.logger.debug("Waiting for pump thread to stop")
+ cls.pump_thread.join()
+ if hasattr(cls, 'vpp_stderr_reader_thread'):
+ cls.logger.debug("Waiting for stdderr pump to stop")
+ cls.vpp_stderr_reader_thread.join()
+
+ if hasattr(cls, 'vpp'):
+ if hasattr(cls, 'vapi'):
+ cls.vapi.disconnect()
+ del cls.vapi
+ cls.vpp.poll()
+ if cls.vpp.returncode is None:
+ cls.logger.debug("Sending TERM to vpp")
+ cls.vpp.terminate()
+ cls.logger.debug("Waiting for vpp to die")
+ cls.vpp.communicate()
+ del cls.vpp
+
+ if cls.vpp_startup_failed:
+ stdout_log = cls.logger.info
+ stderr_log = cls.logger.critical
+ else:
+ stdout_log = cls.logger.info
+ stderr_log = cls.logger.info
+
+ if hasattr(cls, 'vpp_stdout_deque'):
+ stdout_log(single_line_delim)
+ stdout_log('VPP output to stdout while running %s:', cls.__name__)
+ stdout_log(single_line_delim)
+ vpp_output = "".join(cls.vpp_stdout_deque)
+ with open(cls.tempdir + '/vpp_stdout.txt', 'w') as f:
+ f.write(vpp_output)
+ stdout_log('\n%s', vpp_output)
+ stdout_log(single_line_delim)
+
+ if hasattr(cls, 'vpp_stderr_deque'):
+ stderr_log(single_line_delim)
+ stderr_log('VPP output to stderr while running %s:', cls.__name__)
+ stderr_log(single_line_delim)
+ vpp_output = "".join(cls.vpp_stderr_deque)
+ with open(cls.tempdir + '/vpp_stderr.txt', 'w') as f:
+ f.write(vpp_output)
+ stderr_log('\n%s', vpp_output)
+ stderr_log(single_line_delim)
+
+ @classmethod
+ def tearDownClass(cls):
+ """ Perform final cleanup after running all tests in this test-case """
+ cls.quit()
+ cls.file_handler.close()
+
+ def tearDown(self):
+ """ Show various debug prints after each test """
+ self.logger.debug("--- tearDown() for %s.%s(%s) called ---" %
+ (self.__class__.__name__, self._testMethodName,
+ self._testMethodDoc))
+ if not self.vpp_dead:
+ self.logger.debug(self.vapi.cli("show trace"))
+ self.logger.info(self.vapi.ppcli("show interface"))
+ self.logger.info(self.vapi.ppcli("show hardware"))
+ self.logger.info(self.vapi.ppcli("show error"))
+ self.logger.info(self.vapi.ppcli("show run"))
+ self.registry.remove_vpp_config(self.logger)
+ # Save/Dump VPP api trace log
+ api_trace = "vpp_api_trace.%s.log" % self._testMethodName
+ tmp_api_trace = "/tmp/%s" % api_trace
+ vpp_api_trace_log = "%s/%s" % (self.tempdir, api_trace)
+ self.logger.info(self.vapi.ppcli("api trace save %s" % api_trace))
+ self.logger.info("Moving %s to %s\n" % (tmp_api_trace,
+ vpp_api_trace_log))
+ os.rename(tmp_api_trace, vpp_api_trace_log)
+ self.logger.info(self.vapi.ppcli("api trace dump %s" %
+ vpp_api_trace_log))
+ else:
+ self.registry.unregister_all(self.logger)
+
+ def setUp(self):
+ """ Clear trace before running each test"""
+ self.reporter.send_keep_alive(self)
+ self.logger.debug("--- setUp() for %s.%s(%s) called ---" %
+ (self.__class__.__name__, self._testMethodName,
+ self._testMethodDoc))
+ if self.vpp_dead:
+ raise Exception("VPP is dead when setting up the test")
+ self.sleep(.1, "during setUp")
+ self.vpp_stdout_deque.append(
+ "--- test setUp() for %s.%s(%s) starts here ---\n" %
+ (self.__class__.__name__, self._testMethodName,
+ self._testMethodDoc))
+ self.vpp_stderr_deque.append(
+ "--- test setUp() for %s.%s(%s) starts here ---\n" %
+ (self.__class__.__name__, self._testMethodName,
+ self._testMethodDoc))
+ self.vapi.cli("clear trace")
+ # store the test instance inside the test class - so that objects
+ # holding the class can access instance methods (like assertEqual)
+ type(self).test_instance = self
+
+ @classmethod
+ def pg_enable_capture(cls, interfaces):
+ """
+ Enable capture on packet-generator interfaces
+
+ :param interfaces: iterable interface indexes
+
+ """
+ for i in interfaces:
+ i.enable_capture()
+
+ @classmethod
+ def register_capture(cls, cap_name):
+ """ Register a capture in the testclass """
+ # add to the list of captures with current timestamp
+ cls._captures.append((time.time(), cap_name))
+ # filter out from zombies
+ cls._zombie_captures = [(stamp, name)
+ for (stamp, name) in cls._zombie_captures
+ if name != cap_name]
+
+ @classmethod
+ def pg_start(cls):
+ """ Remove any zombie captures and enable the packet generator """
+ # how long before capture is allowed to be deleted - otherwise vpp
+ # crashes - 100ms seems enough (this shouldn't be needed at all)
+ capture_ttl = 0.1
+ now = time.time()
+ for stamp, cap_name in cls._zombie_captures:
+ wait = stamp + capture_ttl - now
+ if wait > 0:
+ cls.sleep(wait, "before deleting capture %s" % cap_name)
+ now = time.time()
+ cls.logger.debug("Removing zombie capture %s" % cap_name)
+ cls.vapi.cli('packet-generator delete %s' % cap_name)
+
+ cls.vapi.cli("trace add pg-input 50") # 50 is maximum
+ cls.vapi.cli('packet-generator enable')
+ cls._zombie_captures = cls._captures
+ cls._captures = []
+
+ @classmethod
+ def create_pg_interfaces(cls, interfaces):
+ """
+ Create packet-generator interfaces.
+
+ :param interfaces: iterable indexes of the interfaces.
+ :returns: List of created interfaces.
+
+ """
+ result = []
+ for i in interfaces:
+ intf = VppPGInterface(cls, i)
+ setattr(cls, intf.name, intf)
+ result.append(intf)
+ cls.pg_interfaces = result
+ return result
+
+ @classmethod
+ def create_loopback_interfaces(cls, interfaces):
+ """
+ Create loopback interfaces.
+
+ :param interfaces: iterable indexes of the interfaces.
+ :returns: List of created interfaces.
+ """
+ result = []
+ for i in interfaces:
+ intf = VppLoInterface(cls, i)
+ setattr(cls, intf.name, intf)
+ result.append(intf)
+ cls.lo_interfaces = result
+ return result
+
+ @staticmethod
+ def extend_packet(packet, size):
+ """
+ Extend packet to given size by padding with spaces
+ NOTE: Currently works only when Raw layer is present.
+
+ :param packet: packet
+ :param size: target size
+
+ """
+ packet_len = len(packet) + 4
+ extend = size - packet_len
+ if extend > 0:
+ packet[Raw].load += ' ' * extend
+
+ @classmethod
+ def reset_packet_infos(cls):
+ """ Reset the list of packet info objects and packet counts to zero """
+ cls._packet_infos = {}
+ cls._packet_count_for_dst_if_idx = {}
+
+ @classmethod
+ def create_packet_info(cls, src_if, dst_if):
+ """
+ Create packet info object containing the source and destination indexes
+ and add it to the testcase's packet info list
+
+ :param VppInterface src_if: source interface
+ :param VppInterface dst_if: destination interface
+
+ :returns: _PacketInfo object
+
+ """
+ info = _PacketInfo()
+ info.index = len(cls._packet_infos)
+ info.src = src_if.sw_if_index
+ info.dst = dst_if.sw_if_index
+ if isinstance(dst_if, VppSubInterface):
+ dst_idx = dst_if.parent.sw_if_index
+ else:
+ dst_idx = dst_if.sw_if_index
+ if dst_idx in cls._packet_count_for_dst_if_idx:
+ cls._packet_count_for_dst_if_idx[dst_idx] += 1
+ else:
+ cls._packet_count_for_dst_if_idx[dst_idx] = 1
+ cls._packet_infos[info.index] = info
+ return info
+
+ @staticmethod
+ def info_to_payload(info):
+ """
+ Convert _PacketInfo object to packet payload
+
+ :param info: _PacketInfo object
+
+ :returns: string containing serialized data from packet info
+ """
+ return "%d %d %d %d %d" % (info.index, info.src, info.dst,
+ info.ip, info.proto)
+
+ @staticmethod
+ def payload_to_info(payload):
+ """
+ Convert packet payload to _PacketInfo object
+
+ :param payload: packet payload
+
+ :returns: _PacketInfo object containing de-serialized data from payload
+
+ """
+ numbers = payload.split()
+ info = _PacketInfo()
+ info.index = int(numbers[0])
+ info.src = int(numbers[1])
+ info.dst = int(numbers[2])
+ info.ip = int(numbers[3])
+ info.proto = int(numbers[4])
+ return info
+
+ def get_next_packet_info(self, info):
+ """
+ Iterate over the packet info list stored in the testcase
+ Start iteration with first element if info is None
+ Continue based on index in info if info is specified
+
+ :param info: info or None
+ :returns: next info in list or None if no more infos
+ """
+ if info is None:
+ next_index = 0
+ else:
+ next_index = info.index + 1
+ if next_index == len(self._packet_infos):
+ return None
+ else:
+ return self._packet_infos[next_index]
+
+ def get_next_packet_info_for_interface(self, src_index, info):
+ """
+ Search the packet info list for the next packet info with same source
+ interface index
+
+ :param src_index: source interface index to search for
+ :param info: packet info - where to start the search
+ :returns: packet info or None
+
+ """
+ while True:
+ info = self.get_next_packet_info(info)
+ if info is None:
+ return None
+ if info.src == src_index:
+ return info
+
+ def get_next_packet_info_for_interface2(self, src_index, dst_index, info):
+ """
+ Search the packet info list for the next packet info with same source
+ and destination interface indexes
+
+ :param src_index: source interface index to search for
+ :param dst_index: destination interface index to search for
+ :param info: packet info - where to start the search
+ :returns: packet info or None
+
+ """
+ while True:
+ info = self.get_next_packet_info_for_interface(src_index, info)
+ if info is None:
+ return None
+ 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)
+ 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)
+
+ @classmethod
+ def sleep(cls, timeout, remark=None):
+ if hasattr(cls, 'logger'):
+ cls.logger.debug("Starting sleep for %ss (%s)" % (timeout, remark))
+ before = time.time()
+ time.sleep(timeout)
+ after = time.time()
+ if after - before > 2 * timeout:
+ cls.logger.error("unexpected time.sleep() result - "
+ "slept for %ss instead of ~%ss!" % (
+ after - before, timeout))
+ if hasattr(cls, 'logger'):
+ cls.logger.debug(
+ "Finished sleep (%s) - slept %ss (wanted %ss)" % (
+ remark, after - before, timeout))
+
+
+class TestCasePrinter(object):
+ _shared_state = {}
+
+ def __init__(self):
+ self.__dict__ = self._shared_state
+ if not hasattr(self, "_test_case_set"):
+ self._test_case_set = set()
+
+ def print_test_case_heading_if_first_time(self, case):
+ if case.__class__ not in self._test_case_set:
+ print(double_line_delim)
+ print(colorize(getdoc(case.__class__).splitlines()[0], YELLOW))
+ print(double_line_delim)
+ self._test_case_set.add(case.__class__)
+
+
+class VppTestResult(unittest.TestResult):
+ """
+ @property result_string
+ String variable to store the test case result string.
+ @property errors
+ List variable containing 2-tuples of TestCase instances and strings
+ holding formatted tracebacks. Each tuple represents a test which
+ raised an unexpected exception.
+ @property failures
+ List variable containing 2-tuples of TestCase instances and strings
+ holding formatted tracebacks. Each tuple represents a test where
+ a failure was explicitly signalled using the TestCase.assert*()
+ methods.
+ """
+
+ def __init__(self, stream, descriptions, verbosity):
+ """
+ :param stream File descriptor to store where to report test results.
+ Set to the standard error stream by default.
+ :param descriptions Boolean variable to store information if to use
+ test case descriptions.
+ :param verbosity Integer variable to store required verbosity level.
+ """
+ unittest.TestResult.__init__(self, stream, descriptions, verbosity)
+ self.stream = stream
+ self.descriptions = descriptions
+ self.verbosity = verbosity
+ self.result_string = None
+ self.printer = TestCasePrinter()
+
+ def addSuccess(self, test):
+ """
+ Record a test succeeded result
+
+ :param test:
+
+ """
+ if hasattr(test, 'logger'):
+ test.logger.debug("--- addSuccess() %s.%s(%s) called"
+ % (test.__class__.__name__,
+ test._testMethodName,
+ test._testMethodDoc))
+ unittest.TestResult.addSuccess(self, test)
+ self.result_string = colorize("OK", GREEN)
+
+ def addSkip(self, test, reason):
+ """
+ Record a test skipped.
+
+ :param test:
+ :param reason:
+
+ """
+ if hasattr(test, 'logger'):
+ test.logger.debug("--- addSkip() %s.%s(%s) called, reason is %s"
+ % (test.__class__.__name__,
+ test._testMethodName,
+ test._testMethodDoc,
+ reason))
+ unittest.TestResult.addSkip(self, test, reason)
+ self.result_string = colorize("SKIP", YELLOW)
+
+ def symlink_failed(self, test):
+ logger = None
+ if hasattr(test, 'logger'):
+ logger = test.logger
+ if hasattr(test, 'tempdir'):
+ try:
+ failed_dir = os.getenv('VPP_TEST_FAILED_DIR')
+ link_path = '%s/%s-FAILED' % (failed_dir,
+ test.tempdir.split("/")[-1])
+ if logger:
+ logger.debug("creating a link to the failed test")
+ logger.debug("os.symlink(%s, %s)" %
+ (test.tempdir, link_path))
+ os.symlink(test.tempdir, link_path)
+ except Exception as e:
+ if logger:
+ logger.error(e)
+
+ def addFailure(self, test, err):
+ """
+ Record a test failed result
+
+ :param test:
+ :param err: error message
+
+ """
+ if hasattr(test, 'logger'):
+ test.logger.debug("--- addFailure() %s.%s(%s) called, err is %s"
+ % (test.__class__.__name__,
+ test._testMethodName,
+ test._testMethodDoc, err))
+ test.logger.debug("formatted exception is:\n%s" %
+ "".join(format_exception(*err)))
+ unittest.TestResult.addFailure(self, test, err)
+ if hasattr(test, 'tempdir'):
+ self.result_string = colorize("FAIL", RED) + \
+ ' [ temp dir used by test case: ' + test.tempdir + ' ]'
+ self.symlink_failed(test)
+ else:
+ self.result_string = colorize("FAIL", RED) + ' [no temp dir]'
+
+ def addError(self, test, err):
+ """
+ Record a test error result
+
+ :param test:
+ :param err: error message
+
+ """
+ if hasattr(test, 'logger'):
+ test.logger.debug("--- addError() %s.%s(%s) called, err is %s"
+ % (test.__class__.__name__,
+ test._testMethodName,
+ test._testMethodDoc, err))
+ test.logger.debug("formatted exception is:\n%s" %
+ "".join(format_exception(*err)))
+ unittest.TestResult.addError(self, test, err)
+ if hasattr(test, 'tempdir'):
+ self.result_string = colorize("ERROR", RED) + \
+ ' [ temp dir used by test case: ' + test.tempdir + ' ]'
+ self.symlink_failed(test)
+ else:
+ self.result_string = colorize("ERROR", RED) + ' [no temp dir]'
+
+ def getDescription(self, test):
+ """
+ Get test description
+
+ :param test:
+ :returns: test description
+
+ """
+ # TODO: if none print warning not raise exception
+ short_description = test.shortDescription()
+ if self.descriptions and short_description:
+ return short_description
+ else:
+ return str(test)
+
+ def startTest(self, test):
+ """
+ Start a test
+
+ :param test:
+
+ """
+ self.printer.print_test_case_heading_if_first_time(test)
+ unittest.TestResult.startTest(self, test)
+ if self.verbosity > 0:
+ self.stream.writeln(
+ "Starting " + self.getDescription(test) + " ...")
+ self.stream.writeln(single_line_delim)
+
+ def stopTest(self, test):
+ """
+ Stop a test
+
+ :param test:
+
+ """
+ unittest.TestResult.stopTest(self, test)
+ if self.verbosity > 0:
+ self.stream.writeln(single_line_delim)
+ self.stream.writeln("%-73s%s" % (self.getDescription(test),
+ self.result_string))
+ self.stream.writeln(single_line_delim)
+ else:
+ self.stream.writeln("%-73s%s" % (self.getDescription(test),
+ self.result_string))
+
+ def printErrors(self):
+ """
+ Print errors from running the test case
+ """
+ self.stream.writeln()
+ self.printErrorList('ERROR', self.errors)
+ self.printErrorList('FAIL', self.failures)
+
+ def printErrorList(self, flavour, errors):
+ """
+ Print error list to the output stream together with error type
+ and test case description.
+
+ :param flavour: error type
+ :param errors: iterable errors
+
+ """
+ for test, err in errors:
+ self.stream.writeln(double_line_delim)
+ self.stream.writeln("%s: %s" %
+ (flavour, self.getDescription(test)))
+ self.stream.writeln(single_line_delim)
+ self.stream.writeln("%s" % err)
+
+
+class VppTestRunner(unittest.TextTestRunner):
+ """
+ A basic test runner implementation which prints results to standard error.
+ """
+ @property
+ def resultclass(self):
+ """Class maintaining the results of the tests"""
+ return VppTestResult
+
+ def __init__(self, pipe=None, stream=sys.stderr, descriptions=True,
+ verbosity=1, failfast=False, buffer=False, resultclass=None):
+ # ignore stream setting here, use hard-coded stdout to be in sync
+ # with prints from VppTestCase methods ...
+ super(VppTestRunner, self).__init__(sys.stdout, descriptions,
+ verbosity, failfast, buffer,
+ resultclass)
+ reporter = KeepAliveReporter()
+ reporter.pipe = pipe
+
+ test_option = "TEST"
+
+ def parse_test_option(self):
+ try:
+ f = os.getenv(self.test_option)
+ except:
+ f = None
+ filter_file_name = None
+ filter_class_name = None
+ filter_func_name = None
+ if f:
+ if '.' in f:
+ parts = f.split('.')
+ if len(parts) > 3:
+ raise Exception("Unrecognized %s option: %s" %
+ (self.test_option, f))
+ if len(parts) > 2:
+ if parts[2] not in ('*', ''):
+ filter_func_name = parts[2]
+ if parts[1] not in ('*', ''):
+ filter_class_name = parts[1]
+ if parts[0] not in ('*', ''):
+ if parts[0].startswith('test_'):
+ filter_file_name = parts[0]
+ else:
+ filter_file_name = 'test_%s' % parts[0]
+ else:
+ if f.startswith('test_'):
+ filter_file_name = f
+ else:
+ filter_file_name = 'test_%s' % f
+ return filter_file_name, filter_class_name, filter_func_name
+
+ def filter_tests(self, tests, filter_file, filter_class, filter_func):
+ result = unittest.suite.TestSuite()
+ for t in tests:
+ if isinstance(t, unittest.suite.TestSuite):
+ # this is a bunch of tests, recursively filter...
+ x = self.filter_tests(t, filter_file, filter_class,
+ filter_func)
+ if x.countTestCases() > 0:
+ result.addTest(x)
+ elif isinstance(t, unittest.TestCase):
+ # this is a single test
+ parts = t.id().split('.')
+ # t.id() for common cases like this:
+ # test_classifier.TestClassifier.test_acl_ip
+ # apply filtering only if it is so
+ if len(parts) == 3:
+ if filter_file and filter_file != parts[0]:
+ continue
+ if filter_class and filter_class != parts[1]:
+ continue
+ if filter_func and filter_func != parts[2]:
+ continue
+ result.addTest(t)
+ else:
+ # unexpected object, don't touch it
+ result.addTest(t)
+ return result
+
+ def run(self, test):
+ """
+ Run the tests
+
+ :param test:
+
+ """
+ faulthandler.enable() # emit stack trace to stderr if killed by signal
+ print("Running tests using custom test runner") # debug message
+ filter_file, filter_class, filter_func = self.parse_test_option()
+ print("Active filters: file=%s, class=%s, function=%s" % (
+ filter_file, filter_class, filter_func))
+ filtered = self.filter_tests(test, filter_file, filter_class,
+ filter_func)
+ print("%s out of %s tests match specified filters" % (
+ filtered.countTestCases(), test.countTestCases()))
+ if not running_extended_tests():
+ print("Not running extended tests (some tests will be skipped)")
+ return super(VppTestRunner, self).run(filtered)
diff --git a/test/hook.py b/test/hook.py
new file mode 100644
index 00000000..f34e0c5b
--- /dev/null
+++ b/test/hook.py
@@ -0,0 +1,212 @@
+import signal
+import os
+import traceback
+from log import RED, single_line_delim, double_line_delim
+from debug import spawn_gdb, gdb_path
+
+
+class Hook(object):
+ """
+ Generic hooks before/after API/CLI calls
+ """
+
+ def __init__(self, logger):
+ self.logger = logger
+
+ def before_api(self, api_name, api_args):
+ """
+ Function called before API call
+ Emit a debug message describing the API name and arguments
+
+ @param api_name: name of the API
+ @param api_args: tuple containing the API arguments
+ """
+ self.logger.debug("API: %s (%s)" %
+ (api_name, api_args), extra={'color': RED})
+
+ def after_api(self, api_name, api_args):
+ """
+ Function called after API call
+
+ @param api_name: name of the API
+ @param api_args: tuple containing the API arguments
+ """
+ pass
+
+ def before_cli(self, cli):
+ """
+ Function called before CLI call
+ Emit a debug message describing the CLI
+
+ @param cli: CLI string
+ """
+ self.logger.debug("CLI: %s" % (cli), extra={'color': RED})
+
+ def after_cli(self, cli):
+ """
+ Function called after CLI call
+ """
+ pass
+
+
+class VppDiedError(Exception):
+ pass
+
+
+class PollHook(Hook):
+ """ Hook which checks if the vpp subprocess is alive """
+
+ def __init__(self, testcase):
+ self.testcase = testcase
+ self.logger = testcase.logger
+
+ def on_crash(self, core_path):
+ if self.testcase.debug_core:
+ if not spawn_gdb(self.testcase.vpp_bin, core_path):
+ self.logger.error(
+ "Debugger '%s' does not exist or is not an executable.." %
+ gdb_path)
+ else:
+ return
+ self.logger.critical("Core file present, debug with: gdb %s %s" %
+ (self.testcase.vpp_bin, core_path))
+
+ def poll_vpp(self):
+ """
+ Poll the vpp status and throw an exception if it's not running
+ :raises VppDiedError: exception if VPP is not running anymore
+ """
+ if self.testcase.vpp_dead:
+ # already dead, nothing to do
+ return
+
+ self.testcase.vpp.poll()
+ if self.testcase.vpp.returncode is not None:
+ 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, s)
+ self.logger.critical(msg)
+ core_path = self.testcase.tempdir + '/core'
+ if os.path.isfile(core_path):
+ self.on_crash(core_path)
+ self.testcase.vpp_dead = True
+ raise VppDiedError(msg)
+
+ def before_api(self, api_name, api_args):
+ """
+ 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).before_api(api_name, api_args)
+ self.poll_vpp()
+
+ def before_cli(self, 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).before_cli(cli)
+ self.poll_vpp()
+
+
+class StepHook(PollHook):
+ """ Hook which requires user to press ENTER before doing any API/CLI """
+
+ def __init__(self, testcase):
+ self.skip_stack = None
+ self.skip_num = None
+ self.skip_count = 0
+ super(StepHook, self).__init__(testcase)
+
+ def skip(self):
+ if self.skip_stack is None:
+ return False
+ stack = traceback.extract_stack()
+ counter = 0
+ skip = True
+ for e in stack:
+ if counter > self.skip_num:
+ break
+ if e[0] != self.skip_stack[counter][0]:
+ skip = False
+ if e[1] != self.skip_stack[counter][1]:
+ skip = False
+ counter += 1
+ if skip:
+ self.skip_count += 1
+ return True
+ else:
+ print("%d API/CLI calls skipped in specified stack "
+ "frame" % self.skip_count)
+ self.skip_count = 0
+ self.skip_stack = None
+ self.skip_num = None
+ return False
+
+ def user_input(self):
+ print('number\tfunction\tfile\tcode')
+ counter = 0
+ stack = traceback.extract_stack()
+ for e in stack:
+ print('%02d.\t%s\t%s:%d\t[%s]' % (counter, e[2], e[0], e[1], e[3]))
+ counter += 1
+ print(single_line_delim)
+ print("You can enter a number of stack frame chosen from above")
+ print("Calls in/below that stack frame will be not be stepped anymore")
+ print(single_line_delim)
+ while True:
+ choice = raw_input("Enter your choice, if any, and press ENTER to "
+ "continue running the testcase...")
+ if choice == "":
+ choice = None
+ try:
+ if choice is not None:
+ num = int(choice)
+ except:
+ print("Invalid input")
+ continue
+ if choice is not None and (num < 0 or num >= len(stack)):
+ print("Invalid choice")
+ continue
+ break
+ if choice is not None:
+ self.skip_stack = stack
+ self.skip_num = num
+
+ def before_cli(self, cli):
+ """ Wait for ENTER before executing CLI """
+ if self.skip():
+ print("Skip pause before executing CLI: %s" % cli)
+ else:
+ print(double_line_delim)
+ print("Test paused before executing CLI: %s" % cli)
+ print(single_line_delim)
+ self.user_input()
+ super(StepHook, self).before_cli(cli)
+
+ def before_api(self, api_name, api_args):
+ """ Wait for ENTER before executing API """
+ if self.skip():
+ print("Skip pause before executing API: %s (%s)"
+ % (api_name, api_args))
+ else:
+ print(double_line_delim)
+ print("Test paused before executing API: %s (%s)"
+ % (api_name, api_args))
+ print(single_line_delim)
+ self.user_input()
+ super(StepHook, self).before_api(api_name, api_args)
diff --git a/test/ipfix.py b/test/ipfix.py
new file mode 100644
index 00000000..deaff67b
--- /dev/null
+++ b/test/ipfix.py
@@ -0,0 +1,539 @@
+#!/usr/bin/env python
+# IPFIX support for Scapy (RFC7011)
+
+from scapy.all import *
+
+
+# IPFIX Information Elements http://www.iana.org/assignments/ipfix/ipfix.xhtml
+information_elements = {
+ 1: "octetDeltaCount",
+ 2: "packetDeltaCount",
+ 3: "deltaFlowCount",
+ 4: "protocolIdentifier",
+ 5: "ipClassOfService",
+ 6: "tcpControlBits",
+ 7: "sourceTransportPort",
+ 8: "sourceIPv4Address",
+ 9: "sourceIPv4PrefixLength",
+ 10: "ingressInterface",
+ 11: "destinationTransportPort",
+ 12: "destinationIPv4Address",
+ 13: "destinationIPv4PrefixLength",
+ 14: "egressInterface",
+ 15: "ipNextHopIPv4Address",
+ 16: "bgpSourceAsNumber",
+ 17: "bgpDestinationAsNumber",
+ 18: "bgpNextHopIPv4Address",
+ 19: "postMCastPacketDeltaCount",
+ 20: "postMCastOctetDeltaCount",
+ 21: "flowEndSysUpTime",
+ 22: "flowStartSysUpTime",
+ 23: "postOctetDeltaCount",
+ 24: "postPacketDeltaCount",
+ 25: "minimumIpTotalLength",
+ 26: "maximumIpTotalLength",
+ 27: "sourceIPv6Address",
+ 28: "destinationIPv6Address",
+ 29: "sourceIPv6PrefixLength",
+ 30: "destinationIPv6PrefixLength",
+ 31: "flowLabelIPv6",
+ 32: "icmpTypeCodeIPv4",
+ 33: "igmpType",
+ 34: "samplingInterval",
+ 35: "samplingAlgorithm",
+ 36: "flowActiveTimeout",
+ 37: "flowIdleTimeout",
+ 38: "engineType",
+ 39: "engineId",
+ 40: "exportedOctetTotalCount",
+ 41: "exportedMessageTotalCount",
+ 42: "exportedFlowRecordTotalCount",
+ 43: "ipv4RouterSc",
+ 44: "sourceIPv4Prefix",
+ 45: "destinationIPv4Prefix",
+ 46: "mplsTopLabelType",
+ 47: "mplsTopLabelIPv4Address",
+ 48: "samplerId",
+ 49: "samplerMode",
+ 50: "samplerRandomInterval",
+ 51: "classId",
+ 52: "minimumTTL",
+ 53: "maximumTTL",
+ 54: "fragmentIdentification",
+ 55: "postIpClassOfService",
+ 56: "sourceMacAddress",
+ 57: "postDestinationMacAddress",
+ 58: "vlanId",
+ 59: "postVlanId",
+ 60: "ipVersion",
+ 61: "flowDirection",
+ 62: "ipNextHopIPv6Address",
+ 63: "bgpNextHopIPv6Address",
+ 64: "ipv6ExtensionHeaders",
+ 70: "mplsTopLabelStackSection",
+ 71: "mplsLabelStackSection2",
+ 72: "mplsLabelStackSection3",
+ 73: "mplsLabelStackSection4",
+ 74: "mplsLabelStackSection5",
+ 75: "mplsLabelStackSection6",
+ 76: "mplsLabelStackSection7",
+ 77: "mplsLabelStackSection8",
+ 78: "mplsLabelStackSection9",
+ 79: "mplsLabelStackSection10",
+ 80: "destinationMacAddress",
+ 81: "postSourceMacAddress",
+ 82: "interfaceName",
+ 83: "interfaceDescription",
+ 84: "samplerName",
+ 85: "octetTotalCount",
+ 86: "packetTotalCount",
+ 87: "flagsAndSamplerId",
+ 88: "fragmentOffset",
+ 89: "forwardingStatus",
+ 90: "mplsVpnRouteDistinguisher",
+ 91: "mplsTopLabelPrefixLength",
+ 92: "srcTrafficIndex",
+ 93: "dstTrafficIndex",
+ 94: "applicationDescription",
+ 95: "applicationId",
+ 96: "applicationName",
+ 98: "postIpDiffServCodePoint",
+ 99: "multicastReplicationFactor",
+ 100: "className",
+ 101: "classificationEngineId",
+ 102: "layer2packetSectionOffset",
+ 103: "layer2packetSectionSize",
+ 104: "layer2packetSectionData",
+ 128: "bgpNextAdjacentAsNumber",
+ 129: "bgpPrevAdjacentAsNumber",
+ 130: "exporterIPv4Address",
+ 131: "exporterIPv6Address",
+ 132: "droppedOctetDeltaCount",
+ 133: "droppedPacketDeltaCount",
+ 134: "droppedOctetTotalCount",
+ 135: "droppedPacketTotalCount",
+ 136: "flowEndReason",
+ 137: "commonPropertiesId",
+ 138: "observationPointId",
+ 139: "icmpTypeCodeIPv6",
+ 140: "mplsTopLabelIPv6Address",
+ 141: "lineCardId",
+ 142: "portId",
+ 143: "meteringProcessId",
+ 144: "exportingProcessId",
+ 145: "templateId",
+ 146: "wlanChannelId",
+ 147: "wlanSSID",
+ 148: "flowId",
+ 149: "observationDomainId",
+ 150: "flowStartSeconds",
+ 151: "flowEndSeconds",
+ 152: "flowStartMilliseconds",
+ 153: "flowEndMilliseconds",
+ 154: "flowStartMicroseconds",
+ 155: "flowEndMicroseconds",
+ 156: "flowStartNanoseconds",
+ 157: "flowEndNanoseconds",
+ 158: "flowStartDeltaMicroseconds",
+ 159: "flowEndDeltaMicroseconds",
+ 160: "systemInitTimeMilliseconds",
+ 161: "flowDurationMilliseconds",
+ 162: "flowDurationMicroseconds",
+ 163: "observedFlowTotalCount",
+ 164: "ignoredPacketTotalCount",
+ 165: "ignoredOctetTotalCount",
+ 166: "notSentFlowTotalCount",
+ 167: "notSentPacketTotalCount",
+ 168: "notSentOctetTotalCount",
+ 169: "destinationIPv6Prefix",
+ 170: "sourceIPv6Prefix",
+ 171: "postOctetTotalCount",
+ 172: "postPacketTotalCount",
+ 173: "flowKeyIndicator",
+ 174: "postMCastPacketTotalCount",
+ 175: "postMCastOctetTotalCount",
+ 176: "icmpTypeIPv4",
+ 177: "icmpCodeIPv4",
+ 178: "icmpTypeIPv6",
+ 179: "icmpCodeIPv6",
+ 180: "udpSourcePort",
+ 181: "udpDestinationPort",
+ 182: "tcpSourcePort",
+ 183: "tcpDestinationPort",
+ 184: "tcpSequenceNumber",
+ 185: "tcpAcknowledgementNumber",
+ 186: "tcpWindowSize",
+ 187: "tcpUrgentPointer",
+ 188: "tcpHeaderLength",
+ 189: "ipHeaderLength",
+ 190: "totalLengthIPv4",
+ 191: "payloadLengthIPv6",
+ 192: "ipTTL",
+ 193: "nextHeaderIPv6",
+ 194: "mplsPayloadLength",
+ 195: "ipDiffServCodePoint",
+ 196: "ipPrecedence",
+ 197: "fragmentFlags",
+ 198: "octetDeltaSumOfSquares",
+ 199: "octetTotalSumOfSquares",
+ 200: "mplsTopLabelTTL",
+ 201: "mplsLabelStackLength",
+ 202: "mplsLabelStackDepth",
+ 203: "mplsTopLabelExp",
+ 204: "ipPayloadLength",
+ 205: "udpMessageLength",
+ 206: "isMulticast",
+ 207: "ipv4IHL",
+ 208: "ipv4Options",
+ 209: "tcpOptions",
+ 210: "paddingOctets",
+ 211: "collectorIPv4Address",
+ 212: "collectorIPv6Address",
+ 213: "exportInterface",
+ 214: "exportProtocolVersion",
+ 215: "exportTransportProtocol",
+ 216: "collectorTransportPort",
+ 217: "exporterTransportPort",
+ 218: "tcpSynTotalCount",
+ 219: "tcpFinTotalCount",
+ 220: "tcpRstTotalCount",
+ 221: "tcpPshTotalCount",
+ 222: "tcpAckTotalCount",
+ 223: "tcpUrgTotalCount",
+ 224: "ipTotalLength",
+ 225: "postNATSourceIPv4Address",
+ 226: "postNATDestinationIPv4Address",
+ 227: "postNAPTSourceTransportPort",
+ 228: "postNAPTDestinationTransportPort",
+ 229: "natOriginatingAddressRealm",
+ 230: "natEvent",
+ 231: "initiatorOctets",
+ 232: "responderOctets",
+ 233: "firewallEvent",
+ 234: "ingressVRFID",
+ 235: "egressVRFID",
+ 236: "VRFname",
+ 237: "postMplsTopLabelExp",
+ 238: "tcpWindowScale",
+ 239: "biflowDirection",
+ 240: "ethernetHeaderLength",
+ 241: "ethernetPayloadLength",
+ 242: "ethernetTotalLength",
+ 243: "dot1qVlanId",
+ 244: "dot1qPriority",
+ 245: "dot1qCustomerVlanId",
+ 246: "dot1qCustomerPriority",
+ 247: "metroEvcId",
+ 248: "metroEvcType",
+ 249: "pseudoWireId",
+ 250: "pseudoWireType",
+ 251: "pseudoWireControlWord",
+ 252: "ingressPhysicalInterface",
+ 253: "egressPhysicalInterface",
+ 254: "postDot1qVlanId",
+ 255: "postDot1qCustomerVlanId",
+ 256: "ethernetType",
+ 257: "postIpPrecedence",
+ 258: "collectionTimeMilliseconds",
+ 259: "exportSctpStreamId",
+ 260: "maxExportSeconds",
+ 261: "maxFlowEndSeconds",
+ 262: "messageMD5Checksum",
+ 263: "messageScope",
+ 264: "minExportSeconds",
+ 265: "minFlowStartSeconds",
+ 266: "opaqueOctets",
+ 267: "sessionScope",
+ 268: "maxFlowEndMicroseconds",
+ 269: "maxFlowEndMilliseconds",
+ 270: "maxFlowEndNanoseconds",
+ 271: "minFlowStartMicroseconds",
+ 272: "minFlowStartMilliseconds",
+ 273: "minFlowStartNanoseconds",
+ 274: "collectorCertificate",
+ 275: "exporterCertificate",
+ 276: "dataRecordsReliability",
+ 277: "observationPointType",
+ 278: "newConnectionDeltaCount",
+ 279: "connectionSumDurationSeconds",
+ 280: "connectionTransactionId",
+ 281: "postNATSourceIPv6Address",
+ 282: "postNATDestinationIPv6Address",
+ 283: "natPoolId",
+ 284: "natPoolName",
+ 285: "anonymizationFlags",
+ 286: "anonymizationTechnique",
+ 287: "informationElementIndex",
+ 288: "p2pTechnology",
+ 289: "tunnelTechnology",
+ 290: "encryptedTechnology",
+ 291: "basicList",
+ 292: "subTemplateList",
+ 293: "subTemplateMultiList",
+ 294: "bgpValidityState",
+ 295: "IPSecSPI",
+ 296: "greKey",
+ 297: "natType",
+ 298: "initiatorPackets",
+ 299: "responderPackets",
+ 300: "observationDomainName",
+ 301: "selectionSequenceId",
+ 302: "selectorId",
+ 303: "informationElementId",
+ 304: "selectorAlgorithm",
+ 305: "samplingPacketInterval",
+ 306: "samplingPacketSpace",
+ 307: "samplingTimeInterval",
+ 308: "samplingTimeSpace",
+ 309: "samplingSize",
+ 310: "samplingPopulation",
+ 311: "samplingProbability",
+ 312: "dataLinkFrameSize",
+ 313: "ipHeaderPacketSection",
+ 314: "ipPayloadPacketSection",
+ 315: "dataLinkFrameSection",
+ 316: "mplsLabelStackSection",
+ 317: "mplsPayloadPacketSection",
+ 318: "selectorIdTotalPktsObserved",
+ 319: "selectorIdTotalPktsSelected",
+ 320: "absoluteError",
+ 321: "relativeError",
+ 322: "observationTimeSeconds",
+ 323: "observationTimeMilliseconds",
+ 324: "observationTimeMicroseconds",
+ 325: "observationTimeNanoseconds",
+ 326: "digestHashValue",
+ 327: "hashIPPayloadOffset",
+ 328: "hashIPPayloadSize",
+ 329: "hashOutputRangeMin",
+ 330: "hashOutputRangeMax",
+ 331: "hashSelectedRangeMin",
+ 332: "hashSelectedRangeMax",
+ 333: "hashDigestOutput",
+ 334: "hashInitialiserValue",
+ 335: "selectorName",
+ 336: "upperCILimit",
+ 337: "lowerCILimit",
+ 338: "confidenceLevel",
+ 339: "informationElementDataType",
+ 340: "informationElementDescription",
+ 341: "informationElementName",
+ 342: "informationElementRangeBegin",
+ 343: "informationElementRangeEnd",
+ 344: "informationElementSemantics",
+ 345: "informationElementUnits",
+ 346: "privateEnterpriseNumber",
+ 347: "virtualStationInterfaceId",
+ 348: "virtualStationInterfaceName",
+ 349: "virtualStationUUID",
+ 350: "virtualStationName",
+ 351: "layer2SegmentId",
+ 352: "layer2OctetDeltaCount",
+ 353: "layer2OctetTotalCount",
+ 354: "ingressUnicastPacketTotalCount",
+ 355: "ingressMulticastPacketTotalCount",
+ 356: "ingressBroadcastPacketTotalCount",
+ 357: "egressUnicastPacketTotalCount",
+ 358: "egressBroadcastPacketTotalCount",
+ 359: "monitoringIntervalStartMilliSeconds",
+ 360: "monitoringIntervalEndMilliSeconds",
+ 361: "portRangeStart",
+ 362: "portRangeEnd",
+ 363: "portRangeStepSize",
+ 364: "portRangeNumPorts",
+ 365: "staMacAddress",
+ 366: "staIPv4Address",
+ 367: "wtpMacAddress",
+ 368: "ingressInterfaceType",
+ 369: "egressInterfaceType",
+ 370: "rtpSequenceNumber",
+ 371: "userName",
+ 372: "applicationCategoryName",
+ 373: "applicationSubCategoryName",
+ 374: "applicationGroupName",
+ 375: "originalFlowsPresent",
+ 376: "originalFlowsInitiated",
+ 377: "originalFlowsCompleted",
+ 378: "distinctCountOfSourceIPAddress",
+ 379: "distinctCountOfDestinationIPAddress",
+ 380: "distinctCountOfSourceIPv4Address",
+ 381: "distinctCountOfDestinationIPv4Address",
+ 382: "distinctCountOfSourceIPv6Address",
+ 383: "distinctCountOfDestinationIPv6Address",
+ 384: "valueDistributionMethod",
+ 385: "rfc3550JitterMilliseconds",
+ 386: "rfc3550JitterMicroseconds",
+ 387: "rfc3550JitterNanoseconds",
+ 388: "dot1qDEI",
+ 389: "dot1qCustomerDEI",
+ 390: "flowSelectorAlgorithm",
+ 391: "flowSelectedOctetDeltaCount",
+ 392: "flowSelectedPacketDeltaCount",
+ 393: "flowSelectedFlowDeltaCount",
+ 394: "selectorIDTotalFlowsObserved",
+ 395: "selectorIDTotalFlowsSelected",
+ 396: "samplingFlowInterval",
+ 397: "samplingFlowSpacing",
+ 398: "flowSamplingTimeInterval",
+ 399: "flowSamplingTimeSpacing",
+ 400: "hashFlowDomain",
+ 401: "transportOctetDeltaCount",
+ 402: "transportPacketDeltaCount",
+ 403: "originalExporterIPv4Address",
+ 404: "originalExporterIPv6Address",
+ 405: "originalObservationDomainId",
+ 406: "intermediateProcessId",
+ 407: "ignoredDataRecordTotalCount",
+ 408: "dataLinkFrameType",
+ 409: "sectionOffset",
+ 410: "sectionExportedOctets",
+ 411: "dot1qServiceInstanceTag",
+ 412: "dot1qServiceInstanceId",
+ 413: "dot1qServiceInstancePriority",
+ 414: "dot1qCustomerSourceMacAddress",
+ 415: "dot1qCustomerDestinationMacAddress",
+ 417: "postLayer2OctetDeltaCount",
+ 418: "postMCastLayer2OctetDeltaCount",
+ 420: "postLayer2OctetTotalCount",
+ 421: "postMCastLayer2OctetTotalCount",
+ 422: "minimumLayer2TotalLength",
+ 423: "maximumLayer2TotalLength",
+ 424: "droppedLayer2OctetDeltaCount",
+ 425: "droppedLayer2OctetTotalCount",
+ 426: "ignoredLayer2OctetTotalCount",
+ 427: "notSentLayer2OctetTotalCount",
+ 428: "layer2OctetDeltaSumOfSquares",
+ 429: "layer2OctetTotalSumOfSquares",
+ 430: "layer2FrameDeltaCount",
+ 431: "layer2FrameTotalCount",
+ 432: "pseudoWireDestinationIPv4Address",
+ 433: "ignoredLayer2FrameTotalCount",
+ 434: "mibObjectValueInteger",
+ 435: "mibObjectValueOctetString",
+ 436: "mibObjectValueOID",
+ 437: "mibObjectValueBits",
+ 438: "mibObjectValueIPAddress",
+ 439: "mibObjectValueCounter",
+ 440: "mibObjectValueGauge",
+ 441: "mibObjectValueTimeTicks",
+ 442: "mibObjectValueUnsigned",
+ 443: "mibObjectValueTable",
+ 444: "mibObjectValueRow",
+ 445: "mibObjectIdentifier",
+ 446: "mibSubIdentifier",
+ 447: "mibIndexIndicator",
+ 448: "mibCaptureTimeSemantics",
+ 449: "mibContextEngineID",
+ 450: "mibContextName",
+ 451: "mibObjectName",
+ 452: "mibObjectDescription",
+ 453: "mibObjectSyntax",
+ 454: "mibModuleName",
+ 455: "mobileIMSI",
+ 456: "mobileMSISDN",
+ 457: "httpStatusCode",
+ 458: "sourceTransportPortsLimit",
+ 459: "httpRequestMethod",
+ 460: "httpRequestHost",
+ 461: "httpRequestTarget",
+ 462: "httpMessageVersion"
+}
+
+
+class IPFIX(Packet):
+ name = "IPFIX"
+ fields_desc = [ShortField("version", 10),
+ ShortField("length", None),
+ IntField("exportTime", None),
+ IntField("sequenceNumber", 1),
+ IntField("observationDomainID", 1)]
+
+
+class FieldSpecifier(Packet):
+ name = "Field Specifier"
+ fields_desc = [ShortEnumField(
+ "informationElement", None, information_elements),
+ ShortField("fieldLength", None)]
+
+ def extract_padding(self, s):
+ return "", s
+
+
+class Template(Packet):
+ name = "Template"
+ fields_desc = [ShortField("templateID", 256),
+ FieldLenField("fieldCount", None, count_of="fields"),
+ PacketListField("templateFields", [], FieldSpecifier,
+ count_from=lambda p: p.fieldCount)]
+
+
+class Data(Packet):
+ name = "Data"
+ fields_desc = [
+ StrLenField("data", "", length_from=lambda p: p.underlayer.length - 4)]
+
+ def extract_padding(self, s):
+ return "", s
+
+
+class Set(Packet):
+ name = "Set"
+ fields_desc = [ShortField("setID", 256),
+ ShortField("length", None)]
+
+ def guess_payload_class(self, payload):
+ if self.setID == 2:
+ return Template
+ elif self.setID > 255:
+ return Data
+ else:
+ return Packet.guess_payload_class(self, payload)
+
+
+bind_layers(IPFIX, Set)
+bind_layers(UDP, IPFIX, dport=4739)
+
+
+class IPFIXDecoder(object):
+ """ IPFIX data set decoder """
+
+ def __init__(self):
+ self._templates = []
+
+ def add_template(self, template):
+ """
+ Add IPFIX tempalte
+
+ :param template: IPFIX template
+ """
+ templateID = template.templateID
+ fields = []
+ rec_len = 0
+ for field in template.templateFields:
+ fields.append(
+ {'name': field.informationElement, 'len': field.fieldLength})
+ rec_len += field.fieldLength
+ self._templates.append(
+ {'id': templateID, 'fields': fields, 'rec_len': rec_len})
+
+ def decode_data_set(self, data_set):
+ """
+ Decode IPFIX data
+
+ :param data_set: IPFIX data set
+ :returns: List of decoded data records.
+ """
+ data = []
+ for template in self._templates:
+ if template['id'] == data_set.setID:
+ offset = 0
+ d = data_set[Data].data
+ for i in range(len(d) / template['rec_len']):
+ record = {}
+ for field in template['fields']:
+ f = d[offset:offset + field['len']]
+ offset += field['len']
+ record.update({field['name']: f})
+ data.append(record)
+ break
+ return data
diff --git a/test/lisp.py b/test/lisp.py
new file mode 100644
index 00000000..865070df
--- /dev/null
+++ b/test/lisp.py
@@ -0,0 +1,326 @@
+from random import randint
+from socket import AF_INET, AF_INET6
+from scapy.all import *
+from scapy.packet import *
+from scapy.fields import *
+from lisp import *
+from framework import *
+from vpp_object import *
+
+
+class VppLispLocatorSet(VppObject):
+ """ Represents LISP locator set in VPP """
+
+ def __init__(self, test, ls_name):
+ self._test = test
+ self._ls_name = ls_name
+
+ @property
+ def test(self):
+ return self._test
+
+ @property
+ def ls_name(self):
+ return self._ls_name
+
+ def add_vpp_config(self):
+ self.test.vapi.lisp_locator_set(ls_name=self._ls_name)
+ self._test.registry.register(self, self.test.logger)
+
+ def get_lisp_locator_sets_dump_entry(self):
+ result = self.test.vapi.lisp_locator_set_dump()
+ for ls in result:
+ if ls.ls_name.strip('\x00') == self._ls_name:
+ return ls
+ return None
+
+ def query_vpp_config(self):
+ return self.get_lisp_locator_sets_dump_entry() is not None
+
+ def remove_vpp_config(self):
+ self.test.vapi.lisp_locator_set(ls_name=self._ls_name, is_add=0)
+
+ def object_id(self):
+ return 'lisp-locator-set-%s' % self._ls_name
+
+
+class VppLispLocator(VppObject):
+ """ Represents LISP locator in VPP """
+
+ def __init__(self, test, sw_if_index, ls_name, priority=1, weight=1):
+ self._test = test
+ self._sw_if_index = sw_if_index
+ self._ls_name = ls_name
+ self._priority = priority
+ self._weight = weight
+
+ @property
+ def test(self):
+ """ Test which created this locator """
+ return self._test
+
+ @property
+ def ls_name(self):
+ """ Locator set name """
+ return self._ls_name
+
+ @property
+ def sw_if_index(self):
+ return self._sw_if_index
+
+ @property
+ def priority(self):
+ return self.priority
+
+ @property
+ def weight(self):
+ return self._weight
+
+ def add_vpp_config(self):
+ self.test.vapi.lisp_locator(ls_name=self._ls_name,
+ sw_if_index=self._sw_if_index,
+ priority=self._priority,
+ weight=self._weight)
+ self._test.registry.register(self, self.test.logger)
+
+ def get_lisp_locator_dump_entry(self):
+ locators = self.test.vapi.lisp_locator_dump(
+ is_index_set=0, ls_name=self._ls_name)
+ for locator in locators:
+ if locator.sw_if_index == self._sw_if_index:
+ return locator
+ return None
+
+ def query_vpp_config(self):
+ locator = self.get_lisp_locator_dump_entry()
+ return locator is not None
+
+ def remove_vpp_config(self):
+ self.test.vapi.lisp_locator(
+ ls_name=self._ls_name, sw_if_index=self._sw_if_index,
+ priority=self._priority, weight=self._weight, is_add=0)
+ self._test.registry.register(self, self.test.logger)
+
+ def object_id(self):
+ return 'lisp-locator-%s-%d' % (self._ls_name, self._sw_if_index)
+
+
+class LispEIDType(object):
+ IP4 = 0
+ IP6 = 1
+ MAC = 2
+
+
+class LispKeyIdType(object):
+ NONE = 0
+ SHA1 = 1
+ SHA256 = 2
+
+
+class LispEID(object):
+ """ Lisp endpoint identifier """
+ def __init__(self, eid):
+ self.eid = eid
+
+ # find out whether EID is ip4 prefix, ip6 prefix or MAC
+ if self.eid.find("/") != -1:
+ if self.eid.find(":") == -1:
+ self.eid_type = LispEIDType.IP4
+ self.data_length = 4
+ else:
+ self.eid_type = LispEIDType.IP6
+ self.data_length = 16
+
+ self.eid_address = self.eid.split("/")[0]
+ self.prefix_length = int(self.eid.split("/")[1])
+ elif self.eid.count(":") == 5: # MAC address
+ self.eid_type = LispEIDType.MAC
+ self.eid_address = self.eid
+ self.prefix_length = 0
+ self.data_length = 6
+ else:
+ raise Exception('Unsupported EID format {}!'.format(eid))
+
+ def __str__(self):
+ if self.eid_type == LispEIDType.IP4:
+ return socket.inet_pton(socket.AF_INET, self.eid_address)
+ elif self.eid_type == LispEIDType.IP6:
+ return socket.inet_pton(socket.AF_INET6, self.eid_address)
+ elif self.eid_type == LispEIDType.MAC:
+ return Exception('Unimplemented')
+ raise Exception('Unknown EID type {}!'.format(self.eid_type))
+
+
+class VppLispMapping(VppObject):
+ """ Represents common features for remote and local LISP mapping in VPP """
+
+ def __init__(self, test, eid, vni=0, priority=1, weight=1):
+ self._eid = LispEID(eid)
+ self._test = test
+ self._priority = priority
+ self._weight = weight
+ self._vni = vni
+
+ @property
+ def test(self):
+ return self._test
+
+ @property
+ def vni(self):
+ return self._vni
+
+ @property
+ def eid(self):
+ return self._eid
+
+ @property
+ def priority(self):
+ return self._priority
+
+ @property
+ def weight(self):
+ return self._weight
+
+ def get_lisp_mapping_dump_entry(self):
+ return self.test.vapi.lisp_eid_table_dump(
+ eid_set=1, prefix_length=self._eid.prefix_length,
+ vni=self._vni, eid_type=self._eid.eid_type, eid=str(self._eid))
+
+ def query_vpp_config(self):
+ mapping = self.get_lisp_mapping_dump_entry()
+ return mapping
+
+
+class VppLocalMapping(VppLispMapping):
+ """ LISP Local mapping """
+ def __init__(self, test, eid, ls_name, vni=0, priority=1, weight=1,
+ key_id=LispKeyIdType.NONE, key=''):
+ super(VppLocalMapping, self).__init__(test, eid, vni, priority, weight)
+ self._ls_name = ls_name
+ self._key_id = key_id
+ self._key = key
+
+ @property
+ def ls_name(self):
+ return self._ls_name
+
+ @property
+ def key_id(self):
+ return self._key_id
+
+ @property
+ def key(self):
+ return self._key
+
+ def add_vpp_config(self):
+ self.test.vapi.lisp_local_mapping(
+ ls_name=self._ls_name, eid_type=self._eid.eid_type,
+ eid=str(self._eid), prefix_len=self._eid.prefix_length,
+ vni=self._vni, key_id=self._key_id, key=self._key)
+ self._test.registry.register(self, self.test.logger)
+
+ def remove_vpp_config(self):
+ self.test.vapi.lisp_local_mapping(
+ ls_name=self._ls_name, eid_type=self._eid.eid_type,
+ eid=str(self._eid), prefix_len=self._eid.prefix_length,
+ vni=self._vni, is_add=0)
+
+ def object_id(self):
+ return 'lisp-eid-local-mapping-%s[%d]' % (self._eid, self._vni)
+
+
+class VppRemoteMapping(VppLispMapping):
+
+ def __init__(self, test, eid, rlocs=None, vni=0, priority=1, weight=1):
+ super(VppRemoteMapping, self).__init__(test, eid, vni, priority,
+ weight)
+ self._rlocs = rlocs
+
+ @property
+ def rlocs(self):
+ return self._rlocs
+
+ def add_vpp_config(self):
+ self.test.vapi.lisp_remote_mapping(
+ rlocs=self._rlocs, eid_type=self._eid.eid_type,
+ eid=str(self._eid), eid_prefix_len=self._eid.prefix_length,
+ vni=self._vni, rlocs_num=len(self._rlocs))
+ self._test.registry.register(self, self.test.logger)
+
+ def remove_vpp_config(self):
+ self.test.vapi.lisp_remote_mapping(
+ eid_type=self._eid.eid_type, eid=str(self._eid),
+ eid_prefix_len=self._eid.prefix_length, vni=self._vni,
+ is_add=0, rlocs_num=0)
+
+ def object_id(self):
+ return 'lisp-eid-remote-mapping-%s[%d]' % (self._eid, self._vni)
+
+
+class VppLispAdjacency(VppObject):
+ """ Represents LISP adjacency in VPP """
+
+ def __init__(self, test, leid, reid, vni=0):
+ self._leid = LispEID(leid)
+ self._reid = LispEID(reid)
+ if self._leid.eid_type != self._reid.eid_type:
+ raise Exception('remote and local EID are different types!')
+ self._vni = vni
+ self._test = test
+
+ @property
+ def test(self):
+ return self._test
+
+ @property
+ def leid(self):
+ return self._leid
+
+ @property
+ def reid(self):
+ return self._reid
+
+ @property
+ def vni(self):
+ return self._vni
+
+ def add_vpp_config(self):
+ self.test.vapi.lisp_adjacency(
+ leid=str(self._leid),
+ reid=str(self._reid), eid_type=self._leid.eid_type,
+ leid_len=self._leid.prefix_length,
+ reid_len=self._reid.prefix_length, vni=self._vni)
+ self._test.registry.register(self, self.test.logger)
+
+ def eid_equal(self, eid, eid_type, eid_data, prefix_len):
+ if eid.eid_type != eid_type:
+ return False
+
+ if eid_type == LispEIDType.IP4 or eid_type == LispEIDType.IP6:
+ if eid.prefix_length != prefix_len:
+ return False
+
+ if str(eid) != eid_data[0:eid.data_length]:
+ return False
+
+ return True
+
+ def query_vpp_config(self):
+ res = self.test.vapi.lisp_adjacencies_get(vni=self._vni)
+ for adj in res.adjacencies:
+ if self.eid_equal(self._leid, adj.eid_type, adj.leid,
+ adj.leid_prefix_len) and \
+ self.eid_equal(self._reid, adj.eid_type, adj.reid,
+ adj.reid_prefix_len):
+ return True
+ return False
+
+ def remove_vpp_config(self):
+ self.test.vapi.lisp_adjacency(
+ leid=str(self._leid),
+ reid=str(self._reid), eid_type=self._leid.eid_type,
+ leid_len=self._leid.prefix_length,
+ reid_len=self._reid.prefix_length, vni=self._vni, is_add=0)
+
+ def object_id(self):
+ return 'lisp-adjacency-%s-%s[%d]' % (self._leid, self._reid, self._vni)
diff --git a/test/log.py b/test/log.py
new file mode 100644
index 00000000..1e541d38
--- /dev/null
+++ b/test/log.py
@@ -0,0 +1,73 @@
+#!/usr/bin/env python
+
+import sys
+import os
+import logging
+
+""" @var formatting delimiter consisting of '=' characters """
+double_line_delim = '=' * 78
+""" @var formatting delimiter consisting of '-' characters """
+single_line_delim = '-' * 78
+
+
+def colorize(msg, color):
+ return color + msg + COLOR_RESET
+
+
+class ColorFormatter(logging.Formatter):
+
+ def init(self, fmt=None, datefmt=None):
+ super(ColorFormatter, self).__init__(fmt, datefmt)
+
+ def format(self, record):
+ message = super(ColorFormatter, self).format(record)
+ if hasattr(record, 'color'):
+ message = colorize(message, record.color)
+ return message
+try:
+ verbose = int(os.getenv("V", 0))
+except:
+ verbose = 0
+
+# 40 = ERROR, 30 = WARNING, 20 = INFO, 10 = DEBUG, 0 = NOTSET (all messages)
+if verbose >= 2:
+ log_level = 10
+elif verbose == 1:
+ log_level = 20
+else:
+ log_level = 40
+
+handler = logging.StreamHandler(sys.stdout)
+handler.setFormatter(ColorFormatter(fmt='%(asctime)s,%(msecs)03d %(message)s',
+ datefmt="%H:%M:%S"))
+handler.setLevel(log_level)
+
+global_logger = logging.getLogger()
+global_logger.addHandler(handler)
+
+scapy_logger = logging.getLogger("scapy.runtime")
+scapy_logger.setLevel(logging.ERROR)
+
+
+def getLogger(name):
+ logger = logging.getLogger(name)
+ logger.setLevel(logging.DEBUG)
+ return logger
+
+# Static variables to store color formatting strings.
+#
+# These variables (RED, GREEN, YELLOW and LPURPLE) are used to configure
+# the color of the text to be printed in the terminal. Variable COLOR_RESET
+# is used to revert the text color to the default one.
+if hasattr(sys.stdout, 'isatty') and sys.stdout.isatty():
+ RED = '\033[91m'
+ GREEN = '\033[92m'
+ YELLOW = '\033[93m'
+ LPURPLE = '\033[94m'
+ COLOR_RESET = '\033[0m'
+else:
+ RED = ''
+ GREEN = ''
+ YELLOW = ''
+ LPURPLE = ''
+ COLOR_RESET = ''
diff --git a/test/patches/scapy-2.3.3/dhcp6-options.patch b/test/patches/scapy-2.3.3/dhcp6-options.patch
new file mode 100644
index 00000000..0e649398
--- /dev/null
+++ b/test/patches/scapy-2.3.3/dhcp6-options.patch
@@ -0,0 +1,58 @@
+diff --git a/scapy/layers/dhcp6.py b/scapy/layers/dhcp6.py
+index 4cb9291..a1adcfc 100644
+--- a/scapy/layers/dhcp6.py
++++ b/scapy/layers/dhcp6.py
+@@ -74,7 +74,9 @@ dhcp6opts = { 1: "CLIENTID",
+ 36: "OPTION_GEOCONF_CIVIC", #RFC-ietf-geopriv-dhcp-civil-09.txt
+ 37: "OPTION_REMOTE_ID", #RFC4649
+ 38: "OPTION_SUBSCRIBER_ID", #RFC4580
+- 39: "OPTION_CLIENT_FQDN" } #RFC4704
++ 39: "OPTION_CLIENT_FQDN", #RFC4704
++ 68: "OPTION_VSS", #RFC6607
++ 79: "OPTION_CLIENT_LINKLAYER_ADDR" } #RFC6939
+
+ dhcp6opts_by_code = { 1: "DHCP6OptClientId",
+ 2: "DHCP6OptServerId",
+@@ -116,12 +118,14 @@ dhcp6opts_by_code = { 1: "DHCP6OptClientId",
+ #40: "DHCP6OptPANAAgent", #RFC-ietf-dhc-paa-option-05.txt
+ #41: "DHCP6OptNewPOSIXTimeZone, #RFC4833
+ #42: "DHCP6OptNewTZDBTimeZone, #RFC4833
+- 43: "DHCP6OptRelayAgentERO" #RFC4994
++ 43: "DHCP6OptRelayAgentERO", #RFC4994
+ #44: "DHCP6OptLQQuery", #RFC5007
+ #45: "DHCP6OptLQClientData", #RFC5007
+ #46: "DHCP6OptLQClientTime", #RFC5007
+ #47: "DHCP6OptLQRelayData", #RFC5007
+ #48: "DHCP6OptLQClientLink", #RFC5007
++ 68: "DHCP6OptVSS", #RFC6607
++ 79: "DHCP6OptClientLinkLayerAddr", #RFC6939
+ }
+
+
+@@ -838,6 +842,26 @@ class DHCP6OptRelayAgentERO(_DHCP6OptGuessPayload): # RFC4994
+ _OptReqListField("reqopts", [23, 24],
+ length_from = lambda pkt: pkt.optlen) ]
+
++# "Client link-layer address type. The link-layer type MUST be a valid hardware
++# type assigned by the IANA, as described in [RFC0826]
++class DHCP6OptClientLinkLayerAddr(_DHCP6OptGuessPayload): #RFC6939
++ name = "DHCP6 Option - Client Link Layer address"
++ fields_desc = [ ShortEnumField("optcode", 79, dhcp6opts),
++ FieldLenField("optlen", None, length_of="clladdr",
++ adjust = lambda pkt,x: x+1),
++ ShortField("lltype", 1), # ethernet
++ _LLAddrField("clladdr", ETHER_ANY) ]
++
++# Virtual Subnet selection
++class DHCP6OptVSS(_DHCP6OptGuessPayload): #RFC6607
++ name = "DHCP6 Option - Virtual Subnet Selection"
++ fields_desc = [ ShortEnumField("optcode", 68, dhcp6opts),
++ FieldLenField("optlen", None, length_of="data",
++ adjust = lambda pkt,x: x+1),
++ ByteField("type", 255), # Default Global/default table
++ StrLenField("data", "",
++ length_from = lambda pkt: pkt.optlen) ]
++
+ #####################################################################
+ ### DHCPv6 messages ###
+ #####################################################################
diff --git a/test/patches/scapy-2.3.3/gre-layers.patch b/test/patches/scapy-2.3.3/gre-layers.patch
new file mode 100644
index 00000000..605a705b
--- /dev/null
+++ b/test/patches/scapy-2.3.3/gre-layers.patch
@@ -0,0 +1,25 @@
+diff --git a/scapy/layers/inet6.py b/scapy/layers/inet6.py
+index 03b80ec..a7e1e0f 100644
+--- a/scapy/layers/inet6.py
++++ b/scapy/layers/inet6.py
+@@ -3722,6 +3722,7 @@ conf.l2types.register(31, IPv6)
+
+ bind_layers(Ether, IPv6, type = 0x86dd )
+ bind_layers(CookedLinux, IPv6, proto = 0x86dd )
++bind_layers(GRE, IPv6, proto = 0x86dd )
+ bind_layers(IPerror6, TCPerror, nh = socket.IPPROTO_TCP )
+ bind_layers(IPerror6, UDPerror, nh = socket.IPPROTO_UDP )
+ bind_layers(IPv6, TCP, nh = socket.IPPROTO_TCP )
+diff --git a/scapy/layers/l2.py b/scapy/layers/l2.py
+index 4f491d2..661a5da 100644
+--- a/scapy/layers/l2.py
++++ b/scapy/layers/l2.py
+@@ -628,7 +628,7 @@ bind_layers( CookedLinux, EAPOL, proto=34958)
+ bind_layers( GRE, LLC, proto=122)
+ bind_layers( GRE, Dot1Q, proto=33024)
+ bind_layers( GRE, Dot1AD, type=0x88a8)
+-bind_layers( GRE, Ether, proto=1)
++bind_layers( GRE, Ether, proto=0x6558)
+ bind_layers( GRE, ARP, proto=2054)
+ bind_layers( GRE, EAPOL, proto=34958)
+ bind_layers( GRE, GRErouting, { "routing_present" : 1 } )
diff --git a/test/patches/scapy-2.3.3/inet6.py.patch b/test/patches/scapy-2.3.3/inet6.py.patch
new file mode 100644
index 00000000..f98e7091
--- /dev/null
+++ b/test/patches/scapy-2.3.3/inet6.py.patch
@@ -0,0 +1,185 @@
+diff --git a/scapy/layers/inet6.py b/scapy/layers/inet6.py
+--- a/scapy/layers/inet6.py 2017-06-01 14:04:18.160881034 +0200
++++ b/scapy/layers/inet6.py 2017-06-02 09:08:40.133800208 +0200
+@@ -369,6 +369,8 @@
+ return Raw
+ elif self.nh == 135 and len(p) > 3: # Mobile IPv6
+ return _mip6_mhtype2cls.get(ord(p[2]), MIP6MH_Generic)
++ elif self.nh == 43 and ord(p[2]) == 4: # Segment Routing header
++ return IPv6ExtHdrSegmentRouting
+ else:
+ return get_cls(ipv6nhcls.get(self.nh,"Raw"), "Raw")
+
+@@ -430,6 +432,14 @@
+ sd = strxor(sd, a)
+ sd = inet_ntop(socket.AF_INET6, sd)
+
++ if self.nh == 43 and isinstance(self.payload, IPv6ExtHdrSegmentRouting):
++ # With segment routing header (rh == 4), the destination is
++ # the first address of the IPv6 addresses list
++ try:
++ sd = self.addresses[0]
++ except IndexError:
++ sd = self.dst
++
+ if self.nh == 44 and isinstance(self.payload, IPv6ExtHdrFragment):
+ nh = self.payload.nh
+
+@@ -489,6 +499,8 @@
+ return self.payload.answers(other.payload.payload)
+ elif other.nh == 43 and isinstance(other.payload, IPv6ExtHdrRouting):
+ return self.payload.answers(other.payload.payload) # Buggy if self.payload is a IPv6ExtHdrRouting
++ elif other.nh == 43 and isinstance(other.payload, IPv6ExtHdrSegmentRouting):
++ return self.payload.answers(other.payload.payload) # Buggy if self.payload is a IPv6ExtHdrRouting
+ elif other.nh == 60 and isinstance(other.payload, IPv6ExtHdrDestOpt):
+ return self.payload.payload.answers(other.payload.payload)
+ elif self.nh == 60 and isinstance(self.payload, IPv6ExtHdrDestOpt): # BU in reply to BRR, for instance
+@@ -919,6 +931,148 @@
+ pkt = pkt[:3]+struct.pack("B", len(self.addresses))+pkt[4:]
+ return _IPv6ExtHdr.post_build(self, pkt, pay)
+
++######################### Segment Routing Header ############################
++
++# This implementation is based on draft 06, available at:
++# https://tools.ietf.org/html/draft-ietf-6man-segment-routing-header-06
++
++class IPv6ExtHdrSegmentRoutingTLV(Packet):
++ name = "IPv6 Option Header Segment Routing - Generic TLV"
++ fields_desc = [ ByteField("type", 0),
++ ByteField("len", 0),
++ ByteField("reserved", 0),
++ ByteField("flags", 0),
++ StrLenField("value", "", length_from=lambda pkt: pkt.len) ]
++
++ def extract_padding(self, p):
++ return "",p
++
++ registered_sr_tlv = {}
++ @classmethod
++ def register_variant(cls):
++ cls.registered_sr_tlv[cls.type.default] = cls
++
++ @classmethod
++ def dispatch_hook(cls, pkt=None, *args, **kargs):
++ if pkt:
++ tmp_type = ord(pkt[0])
++ return cls.registered_sr_tlv.get(tmp_type, cls)
++ return cls
++
++
++class IPv6ExtHdrSegmentRoutingTLVIngressNode(IPv6ExtHdrSegmentRoutingTLV):
++ name = "IPv6 Option Header Segment Routing - Ingress Node TLV"
++ fields_desc = [ ByteField("type", 1),
++ ByteField("len", 18),
++ ByteField("reserved", 0),
++ ByteField("flags", 0),
++ IP6Field("ingress_node", "::1") ]
++
++
++class IPv6ExtHdrSegmentRoutingTLVEgressNode(IPv6ExtHdrSegmentRoutingTLV):
++ name = "IPv6 Option Header Segment Routing - Egress Node TLV"
++ fields_desc = [ ByteField("type", 2),
++ ByteField("len", 18),
++ ByteField("reserved", 0),
++ ByteField("flags", 0),
++ IP6Field("egress_node", "::1") ]
++
++
++class IPv6ExtHdrSegmentRoutingTLVPadding(IPv6ExtHdrSegmentRoutingTLV):
++ name = "IPv6 Option Header Segment Routing - Padding TLV"
++ fields_desc = [ ByteField("type", 4),
++ FieldLenField("len", None, length_of="padding", fmt="B"),
++ StrLenField("padding", b"\x00", length_from=lambda pkt: pkt.len) ]
++
++
++class IPv6ExtHdrSegmentRouting(_IPv6ExtHdr):
++ # 0 1 2 3
++ # 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
++ #+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
++ #| Next Header | Hdr Ext Len | Routing Type | Segments Left |
++ #+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
++ #| Last Entry | Flags | Tag |
++ #+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
++ #| |
++ #| Segment List[0] (128 bits IPv6 address) |
++ #| |
++ #| |
++ #+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
++ #| |
++ #| |
++ # ...
++ #| |
++ #| |
++ #+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
++ #| |
++ #| Segment List[n] (128 bits IPv6 address) |
++ #| |
++ #| |
++ #+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
++ #// //
++ #// Optional Type Length Value objects (variable) //
++ #// //
++ #+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
++ #
++ # 0 1 2 3 4 5 6 7
++ # +-+-+-+-+-+-+-+-+
++ # |U|P|O|A|H| U |
++ # +-+-+-+-+-+-+-+-+
++
++ name = "IPv6 Segment Routing Extension Header"
++ fields_desc = [ ByteEnumField("nh", 59, ipv6nh),
++ ByteField("len", None),
++ ByteField("type", 4),
++ ByteField("segleft", None),
++ ByteField("lastentry", None),
++ BitField("unused1", 0, 1),
++ BitField("protected", 0, 1),
++ BitField("oam", 0, 1),
++ BitField("alert", 0, 1),
++ BitField("hmac", 0, 1),
++ BitField("unused2", 0, 3),
++ ShortField("tag", 0),
++ IP6ListField("addresses", ["::1"],
++ count_from=lambda pkt: pkt.lastentry+1),
++ PacketListField("tlv_objects", [], IPv6ExtHdrSegmentRoutingTLV,
++ length_from=lambda pkt: 8*pkt.len - 16*(pkt.lastentry+1)) ]
++
++ overload_fields = { IPv6: { "nh": 43 } }
++
++ def post_build(self, pkt, pay):
++
++ if self.len is None:
++
++ # The extension must be align on 8 bytes
++ tmp_mod = (len(pkt) - 8) % 8
++ if tmp_mod == 1:
++ warning("IPv6ExtHdrSegmentRouting(): can't pad 1 byte !")
++ elif tmp_mod >= 2:
++ #Add the padding extension
++ tmp_pad = b"\x00" * (tmp_mod-2)
++ tlv = IPv6ExtHdrSegmentRoutingTLVPadding(padding=tmp_pad)
++ pkt += str(tlv)
++
++ tmp_len = (len(pkt) - 8) / 8
++ pkt = pkt[:1] + struct.pack("B", tmp_len)+ pkt[2:]
++
++ if self.segleft is None:
++ tmp_len = len(self.addresses)
++ if tmp_len:
++ tmp_len -= 1
++ pkt = pkt[:3] + struct.pack("B", tmp_len) + pkt[4:]
++
++ if self.lastentry is None:
++ #km - changed to contain n-1
++ tmp_len = len(self.addresses)
++ if tmp_len:
++ tmp_len -= 1
++ #pkt = pkt[:4] + struct.pack("B", len(self.addresses)) + pkt[5:]
++ pkt = pkt[:4] + struct.pack("B", tmp_len) + pkt[5:]
++
++ return _IPv6ExtHdr.post_build(self, pkt, pay)
++
++
+ ########################### Fragmentation Header ############################
+
+ class IPv6ExtHdrFragment(_IPv6ExtHdr):
diff --git a/test/patches/scapy-2.3.3/mpls.py.patch b/test/patches/scapy-2.3.3/mpls.py.patch
new file mode 100644
index 00000000..f63a70a3
--- /dev/null
+++ b/test/patches/scapy-2.3.3/mpls.py.patch
@@ -0,0 +1,18 @@
+diff --git a/scapy/contrib/mpls.py b/scapy/contrib/mpls.py
+index 640a0c5..6af1d4a 100644
+--- a/scapy/contrib/mpls.py
++++ b/scapy/contrib/mpls.py
+@@ -18,6 +18,8 @@ class MPLS(Packet):
+
+ def guess_payload_class(self, payload):
+ if len(payload) >= 1:
++ if not self.s:
++ return MPLS
+ ip_version = (ord(payload[0]) >> 4) & 0xF
+ if ip_version == 4:
+ return IP
+@@ -27,3 +29,4 @@ class MPLS(Packet):
+
+ bind_layers(Ether, MPLS, type=0x8847)
+ bind_layers(GRE, MPLS, proto=0x8847)
++bind_layers(MPLS, MPLS, s=0)
diff --git a/test/run_tests.py b/test/run_tests.py
new file mode 100644
index 00000000..b07a923a
--- /dev/null
+++ b/test/run_tests.py
@@ -0,0 +1,125 @@
+#!/usr/bin/env python
+
+import sys
+import shutil
+import os
+import select
+import unittest
+import argparse
+from multiprocessing import Process, Pipe
+from framework import VppTestRunner
+from debug import spawn_gdb
+from log import global_logger
+from discover_tests import discover_tests
+
+
+def test_runner_wrapper(suite, keep_alive_pipe, result_pipe):
+ result = not VppTestRunner(
+ pipe=keep_alive_pipe,
+ verbosity=verbose,
+ failfast=failfast).run(suite).wasSuccessful()
+ result_pipe.send(result)
+ result_pipe.close()
+ keep_alive_pipe.close()
+
+
+class add_to_suite_callback:
+ def __init__(self, suite):
+ self.suite = suite
+
+ def __call__(self, file_name, cls, method):
+ suite.addTest(cls(method))
+
+
+def run_forked(suite):
+ keep_alive_parent_end, keep_alive_child_end = Pipe(duplex=False)
+ result_parent_end, result_child_end = Pipe(duplex=False)
+
+ child = Process(target=test_runner_wrapper,
+ args=(suite, keep_alive_child_end, result_child_end))
+ child.start()
+ last_test_temp_dir = None
+ last_test_vpp_binary = None
+ last_test = None
+ result = None
+ while result is None:
+ readable = select.select([keep_alive_parent_end.fileno(),
+ result_parent_end.fileno(),
+ ],
+ [], [], test_timeout)[0]
+ if result_parent_end.fileno() in readable:
+ result = result_parent_end.recv()
+ elif keep_alive_parent_end.fileno() in readable:
+ while keep_alive_parent_end.poll():
+ last_test, last_test_vpp_binary,\
+ last_test_temp_dir, vpp_pid = keep_alive_parent_end.recv()
+ else:
+ global_logger.critical("Timeout while waiting for child test "
+ "runner process (last test running was "
+ "`%s' in `%s')!" %
+ (last_test, last_test_temp_dir))
+ failed_dir = os.getenv('VPP_TEST_FAILED_DIR')
+ lttd = last_test_temp_dir.split("/")[-1]
+ link_path = '%s%s-FAILED' % (failed_dir, lttd)
+ global_logger.error("Creating a link to the failed " +
+ "test: %s -> %s" % (link_path, lttd))
+ os.symlink(last_test_temp_dir, link_path)
+ api_post_mortem_path = "/tmp/api_post_mortem.%d" % vpp_pid
+ if os.path.isfile(api_post_mortem_path):
+ global_logger.error("Copying api_post_mortem.%d to %s" %
+ (vpp_pid, last_test_temp_dir))
+ shutil.copy2(api_post_mortem_path, last_test_temp_dir)
+ if last_test_temp_dir and last_test_vpp_binary:
+ core_path = "%s/core" % last_test_temp_dir
+ if os.path.isfile(core_path):
+ global_logger.error("Core-file exists in test temporary "
+ "directory: %s!" % core_path)
+ if d and d.lower() == "core":
+ spawn_gdb(last_test_vpp_binary, core_path,
+ global_logger)
+ child.terminate()
+ result = -1
+ keep_alive_parent_end.close()
+ result_parent_end.close()
+ return result
+
+
+if __name__ == '__main__':
+
+ try:
+ verbose = int(os.getenv("V", 0))
+ except:
+ verbose = 0
+
+ default_test_timeout = 600 # 10 minutes
+ try:
+ test_timeout = int(os.getenv("TIMEOUT", default_test_timeout))
+ except:
+ test_timeout = default_test_timeout
+
+ try:
+ debug = os.getenv("DEBUG")
+ except:
+ debug = None
+
+ parser = argparse.ArgumentParser(description="VPP unit tests")
+ parser.add_argument("-f", "--failfast", action='count',
+ help="fast failure flag")
+ parser.add_argument("-d", "--dir", action='append', type=str,
+ help="directory containing test files "
+ "(may be specified multiple times)")
+ args = parser.parse_args()
+ failfast = True if args.failfast == 1 else False
+
+ suite = unittest.TestSuite()
+ cb = add_to_suite_callback(suite)
+ for d in args.dir:
+ global_logger.info("Adding tests from directory tree %s" % d)
+ discover_tests(d, cb)
+
+ if debug is None or debug.lower() not in ["gdb", "gdbserver"]:
+ sys.exit(run_forked(suite))
+
+ # don't fork if debugging..
+ sys.exit(not VppTestRunner(verbosity=verbose,
+ failfast=failfast).run(suite).wasSuccessful())
diff --git a/test/sanity_import_vpp_papi.py b/test/sanity_import_vpp_papi.py
new file mode 100644
index 00000000..535e00c9
--- /dev/null
+++ b/test/sanity_import_vpp_papi.py
@@ -0,0 +1,4 @@
+#!/usr/bin/env python
+
+""" sanity check script """
+import vpp_papi
diff --git a/test/sanity_run_vpp.py b/test/sanity_run_vpp.py
new file mode 100644
index 00000000..156608c1
--- /dev/null
+++ b/test/sanity_run_vpp.py
@@ -0,0 +1,32 @@
+#!/usr/bin/env python
+
+from __future__ import print_function
+from multiprocessing import Pipe
+from sys import exit
+from hook import VppDiedError
+from framework import VppTestCase, KeepAliveReporter
+
+
+class SanityTestCase(VppTestCase):
+ """ Dummy test case used to check if VPP is able to start """
+ pass
+
+if __name__ == '__main__':
+ rc = 0
+ tc = SanityTestCase
+ x, y = Pipe()
+ reporter = KeepAliveReporter()
+ reporter.pipe = y
+ try:
+ tc.setUpClass()
+ except VppDiedError:
+ rc = -1
+ else:
+ try:
+ tc.tearDownClass()
+ except:
+ pass
+ x.close()
+ y.close()
+
+ exit(rc)
diff --git a/test/scapy_handlers/__init__.py b/test/scapy_handlers/__init__.py
new file mode 100644
index 00000000..e69de29b
--- /dev/null
+++ b/test/scapy_handlers/__init__.py
diff --git a/test/scripts/compress_failed.sh b/test/scripts/compress_failed.sh
new file mode 100755
index 00000000..9559e2ac
--- /dev/null
+++ b/test/scripts/compress_failed.sh
@@ -0,0 +1,37 @@
+#!/bin/bash
+
+if [ "$(ls -A ${VPP_TEST_FAILED_DIR})" ]
+then
+ if [ "${COMPRESS_FAILED_TEST_LOGS}" == "yes" ]
+ then
+ echo -n "Compressing files in temporary directories from failed test runs... "
+ cd ${VPP_TEST_FAILED_DIR}
+ for d in *
+ do
+ cd ${d}
+ find . ! -path . -print0 | xargs -0 -n1 gzip
+ cd ${VPP_TEST_FAILED_DIR}
+ done
+ echo "done."
+ if [ -n "$WORKSPACE" ]
+ then
+ echo "Copying failed test logs into build log archive directory ($WORKSPACE/archives)... "
+ for failed_test in $(ls $VPP_TEST_FAILED_DIR)
+ do
+ mkdir -p $WORKSPACE/archives/$failed_test
+ cp -a $VPP_TEST_FAILED_DIR/$failed_test/* $WORKSPACE/archives/$failed_test
+ done
+ echo "done."
+ fi
+
+ else
+ echo "Not compressing files in temporary directories from failed test runs."
+ fi
+else
+ echo "No symlinks to failed tests' temporary directories found in ${VPP_TEST_FAILED_DIR}."
+fi
+
+# This script gets run only if there was a 'make test' failure,
+# so return failure error status so that the build results are
+# recorded correctly.
+exit 1
diff --git a/test/scripts/git_pull_or_clean.sh b/test/scripts/git_pull_or_clean.sh
new file mode 100755
index 00000000..b119a9cc
--- /dev/null
+++ b/test/scripts/git_pull_or_clean.sh
@@ -0,0 +1,8 @@
+#!/bin/sh
+CMD='git clean -dfX */'
+
+if git pull | grep -v 'Already up-to-date.'
+then
+ echo "Executing $CMD"
+ $CMD
+fi
diff --git a/test/scripts/run_in_venv_with_cleanup.sh b/test/scripts/run_in_venv_with_cleanup.sh
new file mode 100755
index 00000000..35b6737e
--- /dev/null
+++ b/test/scripts/run_in_venv_with_cleanup.sh
@@ -0,0 +1,39 @@
+#!/bin/bash
+
+rv=0
+
+atexit() {
+ group_id=`ps -p $$ -o pgid=`
+ my_id=$$
+ ids=`pgrep -g $group_id -d ' ' | sed "s/\b$my_id\b//g"`
+ echo "Killing possible remaining process IDs: $ids"
+ for id in $ids
+ do
+ if ps -p $id > /dev/null
+ then
+ kill -9 $id
+ fi
+ done
+ exit ${rv}
+}
+
+trap "atexit;" SIGINT SIGTERM
+
+FORCE_FOREGROUND=$1
+shift
+
+source $1
+shift
+
+if [[ "${FORCE_FOREGROUND}" == "1" ]]
+then
+ $*
+else
+ $* &
+ pid=$!
+ wait ${pid}
+fi
+
+rv=$?
+atexit
+exit ${rv}
diff --git a/test/scripts/setsid_wrapper.sh b/test/scripts/setsid_wrapper.sh
new file mode 100755
index 00000000..6d63426b
--- /dev/null
+++ b/test/scripts/setsid_wrapper.sh
@@ -0,0 +1,12 @@
+#!/bin/bash
+
+if [[ "$1" == "1" ]]
+then
+ setsid scripts/run_in_venv_with_cleanup.sh $*
+else
+ setsid scripts/run_in_venv_with_cleanup.sh $* &
+ pid=$!
+ trap "echo setsid_wrapper.sh: got signal, killing child pid ${pid}; kill ${pid}; sleep .1;" SIGINT SIGTERM
+ wait ${pid}
+ exit $?
+fi
diff --git a/test/scripts/socket_test.sh b/test/scripts/socket_test.sh
new file mode 100755
index 00000000..39454557
--- /dev/null
+++ b/test/scripts/socket_test.sh
@@ -0,0 +1,855 @@
+#! /bin/bash
+#
+# socket_test.sh -- script to run socket tests.
+#
+script_dir="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )"
+vpp_dir="$WS_ROOT/build-root/install-vpp-native/vpp/bin/"
+vpp_debug_dir="$WS_ROOT/build-root/install-vpp_debug-native/vpp/bin/"
+vpp_shm_dir="/dev/shm/"
+vpp_run_dir="/run/vpp"
+lib64_dir="$WS_ROOT/build-root/install-vpp-native/vpp/lib64/"
+lib64_debug_dir="$WS_ROOT/build-root/install-vpp_debug-native/vpp/lib64/"
+dpdk_devbind="/usr/share/dpdk/usertools/dpdk-devbind.py"
+docker_vpp_dir="/vpp/"
+docker_app_dir="/vpp/"
+docker_lib64_dir="/vpp-lib64/"
+docker_os="ubuntu"
+vcl_ldpreload_lib="libvcl_ldpreload.so.0.0.0"
+user_gid="$(id -g)"
+vpp_app="vpp"
+sock_srvr_app="sock_test_server"
+sock_clnt_app="sock_test_client"
+sock_srvr_addr="127.0.0.1"
+sock_srvr_port="22000"
+iperf_srvr_app="iperf3 -V4d -s"
+iperf_clnt_app="iperf3 -V4d -c \$srvr_addr"
+gdb_in_emacs="gdb_in_emacs"
+vppcom_conf="vppcom.conf"
+vppcom_conf_dir="$WS_ROOT/src/uri/"
+docker_vppcom_conf_dir="/etc/vpp/"
+xterm_geom="100x60"
+bash_header="#! /bin/bash"
+tmp_cmdfile_prefix="/tmp/socket_test_cmd"
+cmd1_file="${tmp_cmdfile_prefix}1.$$"
+cmd2_file="${tmp_cmdfile_prefix}2.$$"
+cmd3_file="${tmp_cmdfile_prefix}3.$$"
+vpp_eth_name="enp0s8"
+tmp_vpp_exec_file="/tmp/vpp_config.$$"
+tmp_gdb_cmdfile_prefix="/tmp/gdb_cmdfile"
+def_gdb_cmdfile_prefix="$WS_ROOT/extras/gdb/gdb_cmdfile"
+tmp_gdb_cmdfile_vpp="${tmp_gdb_cmdfile_prefix}_vpp.$$"
+tmp_gdb_cmdfile_client="${tmp_gdb_cmdfile_prefix}_vcl_client.$$"
+tmp_gdb_cmdfile_server="${tmp_gdb_cmdfile_prefix}_vcl_server.$$"
+get_docker_server_ip4addr='srvr_addr=$(docker network inspect bridge | grep IPv4Address | awk -e '\''{print $2}'\'' | sed -e '\''s,/16,,'\'' -e '\''s,",,g'\'' -e '\''s/,//'\'')'
+#' single quote to fix the confused emacs colorizer.
+trap_signals="SIGINT SIGTERM EXIT"
+
+# Set default values for imported environment variables if they don't exist.
+#
+VPP_GDB_CMDFILE="${VPP_GDB_CMDFILE:-${def_gdb_cmdfile_prefix}.vpp}"
+VPPCOM_CLIENT_GDB_CMDFILE="${VPPCOM_CLIENT_GDB_CMDFILE:-${def_gdb_cmdfile_prefix}.vppcom_client}"
+VPPCOM_SERVER_GDB_CMDFILE="${VPPCOM_SERVER_GDB_CMDFILE:-${def_gdb_cmdfile_prefix}.vppcom_server}"
+VCL_LDPRELOAD_LIB_DIR="${VCL_LDPRELOAD_LIB_DIR:-/usr/local/lib}"
+
+usage() {
+ cat <<EOF
+Usage: socket_test.sh OPTIONS TEST
+TESTS:
+ nk, native-kernel Run server & client on host using kernel.
+ nv, native-vcl Run vpp, server & client on host using VppComLib.
+ np, native-preload Run vpp, server & client on host using LD_PRELOAD.
+ dk, docker-kernel Run server & client in docker using kernel stack.
+ dv, docker-vcl Run vpp on host, server & client in docker using VppComLib.
+ dp, docker-preload Run vpp on host, server & client in docker using LD_PRELOAD.
+
+OPTIONS:
+ -h Print this usage text.
+ -l Leave ${tmp_cmdfile_prefix}* files after test run.
+ -b Run bash after application exit.
+ -d Run the vpp_debug version of all apps.
+ -c Set VPPCOM_CONF to use the vppcom_test.conf file.
+ -i Run iperf3 for client/server app in native tests.
+ -n Name of ethernet for VPP to use in multi-host cfg.
+ -6 Use ipv6 addressing.
+ -m c[lient] Run client in multi-host cfg (server on remote host)
+ s[erver] Run server in multi-host cfg (client on remote host)
+ -e a[ll] Run all in emacs+gdb.
+ c[lient] Run client in emacs+gdb.
+ s[erver] Run server in emacs+gdb.
+ v[pp] Run vpp in emacs+gdb.
+ -g a[ll] Run all in gdb.
+ c[lient] Run client in gdb.
+ s[erver] Run server in gdb.
+ v[pp] Run vpp in gdb.
+ -t Use tabs in one xterm if available (e.g. xfce4-terminal).
+
+OPTIONS passed to client/server:
+ -S <ip address> Server IP address.
+ -P <server port> Server Port number.
+ -E <data> Run Echo test.
+ -N <num-writes> Test Cfg: number of writes.
+ -R <rxbuf-size> Test Cfg: rx buffer size.
+ -T <txbuf-size> Test Cfg: tx buffer size.
+ -U Run Uni-directional test.
+ -B Run Bi-directional test.
+ -I <num-tst-socks> Send data over multiple test sockets in parallel.
+ -V Test Cfg: Verbose mode.
+ -X Exit client/server after running test.
+
+Environment variables:
+ VPPCOM_CONF Pathname of vppcom configuration file.
+ VPP_GDB_CMDFILE Pathname of gdb command file for vpp.
+ VPPCOM_CLIENT_GDB_CMDFILE Pathname of gdb command file for client.
+ VPPCOM_SERVER_GDB_CMDFILE Pathname of gdb command file for server.
+EOF
+ exit 1
+}
+
+declare -i emacs_vpp=0
+declare -i emacs_client=0
+declare -i emacs_server=0
+declare -i gdb_vpp=0
+declare -i gdb_client=0
+declare -i gdb_server=0
+declare -i perf_vpp=0
+declare -i perf_client=0
+declare -i perf_server=0
+declare -i leave_tmp_files=0
+declare -i bash_after_exit=0
+declare -i iperf3=0
+declare -i use_ipv6=0
+
+while getopts ":hitlbcd6n:m:e:g:p:E:I:N:P:R:S:T:UBVX" opt; do
+ case $opt in
+ h) usage ;;
+ l) leave_tmp_files=1
+ ;;
+ b) bash_after_exit=1
+ ;;
+ i) iperf3=1
+ ;;
+ 6) use_ipv6=1
+ ;;
+ t) xterm_geom="180x40"
+ use_tabs="true"
+ ;;
+ c) VPPCOM_CONF="${vppcom_conf_dir}vppcom_test.conf"
+ ;;
+ d) title_dbg="-DEBUG"
+ _debug="_debug"
+ vpp_dir=$vpp_debug_dir
+ lib64_dir=$lib64_debug_dir
+ ;;
+ e) if [ $OPTARG = "a" ] || [ $OPTARG = "all" ] ; then
+ emacs_client=1
+ emacs_server=1
+ emacs_vpp=1
+ elif [ $OPTARG = "c" ] || [ $OPTARG = "client" ] ; then
+ emacs_client=1
+ elif [ $OPTARG = "s" ] || [ $OPTARG = "server" ] ; then
+ emacs_server=1
+ elif [ $OPTARG = "v" ] || [ $OPTARG = "vpp" ] ; then
+ emacs_vpp=1
+ else
+ echo "ERROR: Option -e unknown argument \'$OPTARG\'" >&2
+ usage
+ fi
+ title_dbg="-DEBUG"
+ vpp_dir=$vpp_debug_dir
+ lib64_dir=$lib64_debug_dir
+ ;;
+ n) vpp_eth_name="$OPTARG"
+ ;;
+ m) if [ $OPTARG = "c" ] || [ $OPTARG = "client" ] ; then
+ multi_host="client"
+ elif [ $OPTARG = "s" ] || [ $OPTARG = "server" ] ; then
+ multi_host="server"
+ else
+ echo "ERROR: Option -e unknown argument \'$OPTARG\'" >&2
+ usage
+ fi
+ ;;
+ g) if [ $OPTARG = "a" ] || [ $OPTARG = "all" ] ; then
+ gdb_client=1
+ gdb_server=1
+ gdb_vpp=1
+ elif [ $OPTARG = "c" ] || [ $OPTARG = "client" ] ; then
+ gdb_client=1
+ elif [ $OPTARG = "s" ] || [ $OPTARG = "server" ] ; then
+ gdb_server=1
+ elif [ $OPTARG = "v" ] || [ $OPTARG = "vpp" ] ; then
+ gdb_vpp=1
+ else
+ echo "ERROR: Option -g unknown argument \'$OPTARG\'" >&2
+ usage
+ fi
+ title_dbg="-DEBUG"
+ vpp_dir=$vpp_debug_dir
+ lib64_dir=$lib64_debug_dir
+ ;;
+ p) if [ $OPTARG = "a" ] || [ $OPTARG = "all" ] ; then
+ perf_client=1
+ perf_server=1
+ perf_vpp=1
+ elif [ $OPTARG = "c" ] || [ $OPTARG = "client" ] ; then
+ perf_client=1
+ elif [ $OPTARG = "s" ] || [ $OPTARG = "server" ] ; then
+ perf_server=1
+ elif [ $OPTARG = "v" ] || [ $OPTARG = "vpp" ] ; then
+ perf_vpp=1
+ else
+ echo "ERROR: Option -p unknown argument \'$OPTARG\'" >&2
+ usage
+ fi
+ echo "WARNING: -p options TBD"
+ ;;
+ S) sock_srvr_addr="$OPTARG"
+ ;;
+ P) sock_srvr_port="$OPTARG"
+ ;;
+E|I|N|R|T) sock_clnt_options="$sock_clnt_options -$opt \"$OPTARG\""
+ ;;
+ U|B|V|X) sock_clnt_options="$sock_clnt_options -$opt"
+ ;;
+ \?)
+ echo "ERROR: Invalid option: -$OPTARG" >&2
+ usage
+ ;;
+ :)
+ echo "ERROR: Option -$OPTARG requires an argument." >&2
+ usage
+ ;;
+ esac
+done
+
+shift $(( $OPTIND-1 ))
+while ! [[ $run_test ]] && (( $# > 0 )) ; do
+ case $1 in
+ "nk" | "native-kernel")
+ run_test="native_kernel" ;;
+ "np" | "native-preload")
+ run_test="native_preload" ;;
+ "nv" | "native-vcl")
+ sock_srvr_app="vcl_test_server"
+ sock_clnt_app="vcl_test_client"
+ run_test="native_vcl" ;;
+ "dk" | "docker-kernel")
+ run_test="docker_kernel" ;;
+ "dp" | "docker-preload")
+ run_test="docker_preload" ;;
+ "dv" | "docker-vcl")
+ sock_srvr_app="vcl_test_server"
+ sock_clnt_app="vcl_test_client"
+ run_test="docker_vcl" ;;
+ *)
+ echo "ERROR: Unknown option '$1'!" >&2
+ usage ;;
+ esac
+ shift
+done
+
+if [ -z "$WS_ROOT" ] ; then
+ echo "ERROR: WS_ROOT environment variable not set!" >&2
+ echo " Please set WS_ROOT to VPP workspace root directory." >&2
+ exit 1
+fi
+
+if [[ "$(grep bin_PROGRAMS $WS_ROOT/src/uri.am)" = "" ]] ; then
+ $WS_ROOT/extras/vagrant/vcl_test.sh $WS_ROOT $USER
+ (cd $WS_ROOT; make build)
+fi
+
+if [ ! -d $vpp_dir ] ; then
+ echo "ERROR: Missing VPP$DEBUG bin directory!" >&2
+ echo " $vpp_dir" >&2
+ env_test_failed="true"
+fi
+
+if [[ $run_test =~ .*"_preload" ]] ; then
+ if [ ! -d $lib64_dir ] ; then
+ echo "ERROR: Missing VPP$DEBUG lib64 directory!" >&2
+ echo " $lib64_dir" >&2
+ elif [ ! -d $VCL_LDPRELOAD_LIB_DIR ] ; then
+ echo "ERROR: Missing VCL LD_PRELOAD Library directory!" >&2
+ echo " $VCL_LDPRELOAD_LIB_DIR" >&2
+ env_test_failed="true"
+ elif [ ! -f $VCL_LDPRELOAD_LIB_DIR/$vcl_ldpreload_lib ] ; then
+ echo "ERROR: Missing VCL LD_PRELOAD library!" >&2
+ echo " $VCL_LDPRELOAD_LIB_DIR/$vcl_ldpreload_lib" >&2
+ env_test_failed="true"
+ fi
+fi
+
+if [ ! -f $vpp_dir$vpp_app ] ; then
+ echo "ERROR: Missing VPP$DEBUG Application!" >&2
+ echo " $vpp_dir$vpp_app" >&2
+ env_test_failed="true"
+fi
+
+if [ ! -f $vpp_dir$sock_srvr_app ] && [ ! $iperf3 -eq 1 ] ; then
+ echo "ERROR: Missing$DEBUG Socket Server Application!" >&2
+ echo " $vpp_dir$sock_srvr_app" >&2
+ env_test_failed="true"
+fi
+
+if [ ! -f $vpp_dir$sock_clnt_app ] && [ ! $iperf3 -eq 1 ] ; then
+ echo "ERROR: Missing$DEBUG Socket Client Application!" >&2
+ echo " $vpp_dir$sock_clnt_app" >&2
+ env_test_failed="true"
+fi
+
+if [[ $run_test =~ "docker_".* ]] ; then
+ if [ $emacs_client -eq 1 ] || [ $emacs_server -eq 1 ] || [ $gdb_client -eq 1 ] || [ $gdb_server -eq 1 ] ; then
+
+ echo "WARNING: gdb is not currently supported in docker."
+ echo " Ignoring client/server gdb options."
+ emacs_client=0
+ emacs_server=0
+ gdb_client=0
+ gdb_server=0
+ fi
+fi
+
+if [[ $run_test =~ .*"_vcl" ]] && [ $iperf3 -eq 1 ] ; then
+ echo "ERROR: Invalid option 'i' for test $run_test!"
+ echo " iperf3 is not compiled with the VCL library."
+ env_test_failed="true"
+fi
+
+if [ -n "$mult_host"] && [ ! -f "$dpdk_devbind" ] ; then
+ echo "ERROR: Can't find dpdk-devbind.py!"
+ echo " Run \"cd \$WS_ROOT; make dpdk-install-dev\" to install it."
+ echo
+ env_test_failed="true"
+fi
+
+if [ -n "$env_test_failed" ] ; then
+ exit 1
+fi
+
+if [ -f "$VPPCOM_CONF" ] ; then
+ vppcom_conf="$(basename $VPPCOM_CONF)"
+ vppcom_conf_dir="$(dirname $VPPCOM_CONF)/"
+ api_prefix="$(egrep -s '^\s*api-prefix \w+' $VPPCOM_CONF | awk -e '{print $2}')"
+ if [ -n "$api_prefix" ] ; then
+ api_segment=" api-segment { gid $user_gid prefix $api_prefix }"
+ fi
+fi
+if [ -z "$api_segment" ] ; then
+ api_segment=" api-segment { gid $user_gid }"
+fi
+if [ -n "$multi_host" ] ; then
+ sudo modprobe uio_pci_generic
+ vpp_args="unix { interactive exec $tmp_vpp_exec_file}${api_segment}"
+else
+ vpp_args="unix { interactive }${api_segment}"
+fi
+
+if [ $iperf3 -eq 1 ] ; then
+ app_dir="$(dirname $(which iperf3))/"
+ srvr_app=$iperf_srvr_app
+ clnt_app=$iperf_clnt_app
+ if [[ $run_test =~ "docker_".* ]] ; then
+ unset -v app_dir
+ sock_srvr_port=5201
+ docker_app_dir="networkstatic/"
+ unset -v docker_os
+ fi
+else
+ app_dir="$vpp_dir"
+ srvr_app="$sock_srvr_app $sock_srvr_port"
+ clnt_app="$sock_clnt_app${sock_clnt_options} \$srvr_addr $sock_srvr_port"
+fi
+
+verify_no_vpp() {
+ local grep_for_vpp="ps -eaf|grep -v grep|grep \"bin/vpp\""
+
+ if [ -n "$api_prefix" ] ; then
+ grep_for_vpp="$grep_for_vpp|grep \"prefix $api_prefix\""
+ fi
+ local running_vpp="$(eval $grep_for_vpp)"
+ if [ -n "$running_vpp" ] ; then
+ echo "ERROR: Please kill the following vpp instance(s):"
+ echo
+ echo $running_vpp
+ echo
+ exit 1
+ fi
+ clean_devshm="$vpp_shm_dir*db $vpp_shm_dir*global_vm $vpp_shm_dir*vpe-api $vpp_shm_dir[0-9]*-[0-9]* $vpp_shm_dir*:segment[0-9]*"
+ sudo rm -f $clean_devshm
+ devshm_files="$(ls -l $clean_devshm 2>/dev/null | grep $(whoami))"
+ if [ "$devshm_files" != "" ] ; then
+ echo "ERROR: Please remove the following $vpp_shm_dir files:"
+ for file in "$devshm_files" ; do
+ echo " $file"
+ done
+ exit 1
+ fi
+ if [ ! -d "$vpp_run_dir" ] ; then
+ sudo mkdir $vpp_run_dir
+ sudo chown root:$USER $vpp_run_dir
+ fi
+ if [ -n "$multi_host" ] ; then
+ vpp_eth_pci_id="$(ls -ld /sys/class/net/$vpp_eth_name/device | awk '{print $11}' | cut -d/ -f4)"
+ if [ -z "$vpp_eth_pci_id" ] ; then
+ echo "ERROR: Missing ethernet interface $vpp_eth_name!"
+ usage
+ fi
+ printf -v bus "%x" "0x$(echo $vpp_eth_pci_id | cut -d: -f2)"
+ printf -v slot "%x" "0x$(echo $vpp_eth_pci_id | cut -d: -f3 | cut -d. -f1)"
+ printf -v func "%x" "0x$(echo $vpp_eth_pci_id | cut -d. -f2)"
+
+ vpp_eth_kernel_driver="$(basename $(ls -l /sys/bus/pci/devices/$vpp_eth_pci_id/driver | awk '{print $11}'))"
+ if [ -z "$vpp_eth_kernel_driver" ] ; then
+ echo "ERROR: Missing kernel driver for $vpp_eth_name!"
+ usage
+ fi
+ case $vpp_eth_kernel_driver in
+ e1000)
+ vpp_eth_ifname="GigabitEthernet$bus/$slot/$func" ;;
+ ixgbe)
+ vpp_eth_ifname="TenGigabitEthernet$bus/$slot/$func" ;;
+ i40e)
+ vpp_eth_ifname="FortyGigabitEthernet$bus/$slot/$func" ;;
+ *)
+ echo "ERROR: Unknown ethernet kernel driver $vpp_eth_kernel_driver!"
+ usage ;;
+ esac
+
+ vpp_eth_ip4_addr="$(ip -4 -br addr show $vpp_eth_name | awk '{print $3}')"
+ if [ -z "$vpp_eth_ip4_addr" ] ; then
+ if [ "$multi_host" = "server" ] ; then
+ vpp_eth_ip4_addr="10.10.10.10/24"
+ else
+ vpp_eth_ip4_addr="10.10.10.11/24"
+ fi
+ fi
+ if [ $use_ipv6 -eq 1 ] && [ -z "$vpp_eth_ip6_addr" ] ; then
+ echo "ERROR: No inet6 address configured for $vpp_eth_name!"
+ usage
+ fi
+ vpp_args="$vpp_args plugins { path ${lib64_dir}vpp_plugins } dpdk { dev $vpp_eth_pci_id }"
+
+ sudo ifconfig $vpp_eth_name down 2> /dev/null
+ echo "Configuring VPP to use $vpp_eth_name ($vpp_eth_pci_id), inet addr $vpp_eth_ip4_addr"
+
+ cat <<EOF >> $tmp_vpp_exec_file
+set int state $vpp_eth_ifname up
+set int ip addr $vpp_eth_ifname $vpp_eth_ip4_addr
+EOF
+
+ fi
+}
+
+verify_no_docker_containers() {
+ if (( $(which docker | wc -l) < 1 )) ; then
+ echo "ERROR: docker is not installed!"
+ echo "See https://docs.docker.com/engine/installation/linux/ubuntu/"
+ echo " or https://docs.docker.com/engine/installation/linux/centos/"
+ exit 1
+ fi
+ if (( $(docker ps | wc -l) > 1 )) ; then
+ echo "ERROR: Run the following to kill all docker containers:"
+ echo "docker kill \$(docker ps -q)"
+ echo
+ docker ps
+ exit 1
+ fi
+}
+
+set_pre_cmd() {
+ # arguments
+ # $1 : emacs flag
+ # $2 : gdb flag
+ # $3 : optional LD_PRELOAD library pathname
+ local -i emacs=$1
+ local -i gdb=$2
+
+ if [ $emacs -eq 1 ] ; then
+ write_gdb_cmdfile $tmp_gdb_cmdfile $gdb_cmdfile $emacs $3
+ pre_cmd="$gdb_in_emacs "
+ elif [ $gdb -eq 1 ] ; then
+ write_gdb_cmdfile $tmp_gdb_cmdfile $gdb_cmdfile $emacs $3
+ pre_cmd="gdb -x $tmp_gdb_cmdfile -i=mi --args "
+ elif [ -z $3 ] ; then
+ unset -v pre_cmd
+ else
+ docker_ld_preload="-e LD_PRELOAD=$3 "
+ pre_cmd="LD_PRELOAD=$3 "
+ fi
+}
+
+write_script_header() {
+ # arguments
+ # $1 : command script file
+ # $2 : gdb command file
+ # $3 : title
+ # $4 : optional command string (typically "sleep 2")
+ echo "$bash_header" > $1
+ echo -e "#\n# $1 generated on $(date)\n#" >> $1
+ if [ $leave_tmp_files -eq 0 ] ; then
+ if [ -n "$multi_host" ] ; then
+ echo "trap \"rm -f $1 $2 $tmp_vpp_exec_file; sudo $dpdk_devbind -b $vpp_eth_kernel_driver $vpp_eth_pci_id; sudo ifconfig $vpp_eth_name up\" $trap_signals" >> $1
+ else
+ echo "trap \"rm -f $1 $2 $tmp_vpp_exec_file\" $trap_signals" >> $1
+ fi
+ fi
+ echo "export VPPCOM_CONF=${vppcom_conf_dir}${vppcom_conf}" >> $1
+ if [ "$pre_cmd" = "$gdb_in_emacs " ] ; then
+ if [ -n "$multi_host" ] && [[ $3 =~ "VPP".* ]] ; then
+ cat <<EOF >> $1
+$gdb_in_emacs() {
+ sudo emacs --eval "(gdb \"gdb -x $2 -i=mi --args \$*\")" --eval "(setq frame-title-format \"$3\")"
+}
+EOF
+ else
+ cat <<EOF >> $1
+$gdb_in_emacs() {
+ emacs --eval "(gdb \"gdb -x $2 -i=mi --args \$*\")" --eval "(setq frame-title-format \"$3\")"
+}
+EOF
+ fi
+ fi
+ if [ -n "$4" ] ; then
+ echo "$4" >> $1
+ fi
+}
+
+write_script_footer() {
+ # arguments
+ # $1 : command script file
+ # $2 : perf flag indicating to run bash before exit
+ local -i perf=$2
+ if [ $bash_after_exit -eq 1 ] || [ $perf -eq 1 ] ; then
+ echo "bash" >> $1
+ fi
+}
+
+write_gdb_cmdfile() {
+ # arguments
+ # $1 : gdb command file
+ # $2 : User specified gdb cmdfile
+ # $3 : emacs flag
+ # $4 : optional LD_PRELOAD library pathname.
+ local -i emacs=$3
+
+ echo "# $1 generated on $(date)" > $1
+ echo "#" >> $1
+ echo "set confirm off" >> $1
+ if [ -n "$4" ] ; then
+ echo "set exec-wrapper env LD_PRELOAD=$4" >> $1
+ echo "start" >> $1
+ fi
+
+ if [ ! -f $2 ] ; then
+ echo -n "# " >> $1
+ fi
+ echo "source $2" >> $1
+ if [ $emacs -eq 0 ] ; then
+ echo "run" >> $1
+ fi
+}
+
+native_kernel() {
+ banner="Running NATIVE-KERNEL socket test"
+ if [ -z "$multi_host" ] || [ "$multi_host" = "server" ] ; then
+ title1="SERVER$title_dbg (Native-Kernel Socket Test)"
+ tmp_gdb_cmdfile=$tmp_gdb_cmdfile_server
+ gdb_cmdfile=$VPPCOM_SERVER_GDB_CMDFILE
+ set_pre_cmd $emacs_server $gdb_server
+ write_script_header $cmd1_file $tmp_gdb_cmdfile "$title1"
+ echo "${pre_cmd}${app_dir}${srvr_app}" >> $cmd1_file
+ write_script_footer $cmd1_file $perf_server
+ chmod +x $cmd1_file
+ fi
+
+ if [ -z "$multi_host" ] || [ "$multi_host" = "client" ] ; then
+ title2="CLIENT$title_dbg (Native-Kernel Socket Test)"
+ tmp_gdb_cmdfile=$tmp_gdb_cmdfile_client
+ gdb_cmdfile=$VPPCOM_CLIENT_GDB_CMDFILE
+ set_pre_cmd $emacs_client $gdb_client
+ write_script_header $cmd2_file $tmp_gdb_cmdfile "$title2" "sleep 2"
+ echo "srvr_addr=\"$sock_srvr_addr\"" >> $cmd2_file
+ echo "${pre_cmd}${app_dir}${clnt_app}" >> $cmd2_file
+ write_script_footer $cmd2_file $perf_client
+ chmod +x $cmd2_file
+
+ fi
+}
+
+native_preload() {
+ verify_no_vpp
+ banner="Running NATIVE-PRELOAD socket test"
+ ld_preload="$VCL_LDPRELOAD_LIB_DIR/$vcl_ldpreload_lib "
+
+ title1="VPP$title_dbg (Native-Preload Socket Test)"
+ tmp_gdb_cmdfile=$tmp_gdb_cmdfile_vpp
+ gdb_cmdfile=$VPP_GDB_CMDFILE
+ set_pre_cmd $emacs_vpp $gdb_vpp
+ write_script_header $cmd1_file $tmp_gdb_cmdfile "$title1"
+ if [ -n "$multi_host" ] && [ $emacs_vpp -eq 0 ] ; then
+ echo -n "sudo " >> $cmd1_file
+ fi
+ echo "${pre_cmd}$vpp_dir$vpp_app $vpp_args " >> $cmd1_file
+ write_script_footer $cmd1_file $perf_vpp
+ chmod +x $cmd1_file
+
+ if [ -z "$multi_host" ] || [ "$multi_host" = "server" ] ; then
+ title2="SERVER$title_dbg (Native-Preload Socket Test)"
+ tmp_gdb_cmdfile=$tmp_gdb_cmdfile_server
+ gdb_cmdfile=$VPPCOM_SERVER_GDB_CMDFILE
+ set_pre_cmd $emacs_server $gdb_server $ld_preload
+ write_script_header $cmd2_file $tmp_gdb_cmdfile "$title2" "sleep 2"
+ echo "export LD_LIBRARY_PATH=\"$lib64_dir:$VCL_LDPRELOAD_LIB_DIR:$LD_LIBRARY_PATH\"" >> $cmd2_file
+ echo "${pre_cmd}${app_dir}${srvr_app}" >> $cmd2_file
+ write_script_footer $cmd2_file $perf_server
+ chmod +x $cmd2_file
+ fi
+
+ if [ -z "$multi_host" ] || [ "$multi_host" = "client" ] ; then
+ title3="CLIENT$title_dbg (Native-Preload Socket Test)"
+ tmp_gdb_cmdfile=$tmp_gdb_cmdfile_client
+ gdb_cmdfile=$VPPCOM_CLIENT_GDB_CMDFILE
+ set_pre_cmd $emacs_client $gdb_client $ld_preload
+ write_script_header $cmd3_file $tmp_gdb_cmdfile "$title3" "sleep 3"
+ echo "export LD_LIBRARY_PATH=\"$lib64_dir:$VCL_LDPRELOAD_LIB_DIR:$LD_LIBRARY_PATH\"" >> $cmd3_file
+ echo "srvr_addr=\"$sock_srvr_addr\"" >> $cmd3_file
+ echo "${pre_cmd}${app_dir}${clnt_app}" >> $cmd3_file
+ write_script_footer $cmd3_file $perf_client
+ chmod +x $cmd3_file
+ fi
+}
+
+native_vcl() {
+ verify_no_vpp
+ banner="Running NATIVE-VCL socket test"
+
+ title1="VPP$title_dbg (Native-VCL Socket Test)"
+ tmp_gdb_cmdfile=$tmp_gdb_cmdfile_vpp
+ gdb_cmdfile=$VPP_GDB_CMDFILE
+ set_pre_cmd $emacs_vpp $gdb_vpp
+ write_script_header $cmd1_file $tmp_gdb_cmdfile "$title1"
+ if [ -n "$multi_host" ] && [ $emacs_vpp -eq 0 ] ; then
+ echo -n "sudo " >> $cmd1_file
+ fi
+ echo "${pre_cmd}$vpp_dir$vpp_app $vpp_args " >> $cmd1_file
+ write_script_footer $cmd1_file $perf_vpp
+ chmod +x $cmd1_file
+
+ if [ -z "$multi_host" ] || [ "$multi_host" = "server" ] ; then
+ title2="SERVER$title_dbg (Native-VCL Socket Test)"
+ tmp_gdb_cmdfile=$tmp_gdb_cmdfile_server
+ gdb_cmdfile=$VPPCOM_SERVER_GDB_CMDFILE
+ set_pre_cmd $emacs_server $gdb_server
+ if [ "$multi_host" = "server" ] ; then
+ delay="sleep 10"
+ else
+ delay="sleep 3"
+ fi
+ write_script_header $cmd2_file $tmp_gdb_cmdfile "$title2" "$delay"
+ echo "export LD_LIBRARY_PATH=\"$lib64_dir:$LD_LIBRARY_PATH\"" >> $cmd2_file
+ echo "${pre_cmd}${app_dir}${srvr_app}" >> $cmd2_file
+ write_script_footer $cmd2_file $perf_server
+ chmod +x $cmd2_file
+ fi
+
+ if [ -z "$multi_host" ] || [ "$multi_host" = "client" ] ; then
+ title3="CLIENT$title_dbg (Native-VCL Socket Test)"
+ tmp_gdb_cmdfile=$tmp_gdb_cmdfile_client
+ gdb_cmdfile=$VPPCOM_CLIENT_GDB_CMDFILE
+ set_pre_cmd $emacs_client $gdb_client
+ if [ "$multi_host" = "client" ] ; then
+ delay="sleep 10"
+ else
+ delay="sleep 3"
+ fi
+ write_script_header $cmd3_file $tmp_gdb_cmdfile "$title3" "$delay"
+ echo "export LD_LIBRARY_PATH=\"$lib64_dir:$LD_LIBRARY_PATH\"" >> $cmd3_file
+ echo "srvr_addr=\"$sock_srvr_addr\"" >> $cmd3_file
+ echo "${pre_cmd}${app_dir}${clnt_app}" >> $cmd3_file
+ write_script_footer $cmd3_file $perf_client
+ chmod +x $cmd3_file
+ fi
+}
+
+docker_kernel() {
+ verify_no_docker_containers
+ banner="Running DOCKER-KERNEL socket test"
+
+ if [ -z "$multi_host" ] || [ "$multi_host" = "server" ] ; then
+ title1="SERVER$title_dbg (Docker-Native Socket Test)"
+ tmp_gdb_cmdfile=$tmp_gdb_cmdfile_server
+ gdb_cmdfile=$VPPCOM_SERVER_GDB_CMDFILE
+ set_pre_cmd $emacs_server $gdb_server
+ write_script_header $cmd1_file $tmp_gdb_cmdfile "$title1"
+ echo "docker run -it --cpuset-cpus='4-7' --cpuset-cpus='4-7' -v $vpp_dir:$docker_vpp_dir -p $sock_srvr_port:$sock_srvr_port $docker_os ${docker_app_dir}${srvr_app}" >> $cmd1_file
+ write_script_footer $cmd1_file $perf_server
+ chmod +x $cmd1_file
+ fi
+
+ if [ -z "$multi_host" ] || [ "$multi_host" = "client" ] ; then
+ title2="CLIENT$title_dbg (Docker-Native Socket Test)"
+ tmp_gdb_cmdfile=$tmp_gdb_cmdfile_client
+ gdb_cmdfile=$VPPCOM_CLIENT_GDB_CMDFILE
+ set_pre_cmd $emacs_client $gdb_client
+ write_script_header $cmd2_file $tmp_gdb_cmdfile "$title2" "sleep 2"
+ echo "$get_docker_server_ip4addr" >> $cmd2_file
+ echo "docker run -it --cpuset-cpus='4-7' -v $vpp_dir:$docker_vpp_dir $docker_os ${docker_app_dir}${clnt_app}" >> $cmd2_file
+ write_script_footer $cmd2_file $perf_client
+ chmod +x $cmd2_file
+ fi
+}
+
+docker_preload() {
+ verify_no_vpp
+ verify_no_docker_containers
+ banner="Running DOCKER-PRELOAD socket test"
+ docker_ld_preload_dir="/vcl-ldpreload/"
+ ld_preload_dir="$VCL_LDPRELOAD_LIB_DIR"
+ ld_preload="$docker_ld_preload_dir$vcl_ldpreload_lib "
+ docker_ld_preload_lib="$docker_ld_preload_dir$vcl_ldpreload_lib "
+
+ title1="VPP$title_dbg (Docker-Preload Socket Test)"
+ tmp_gdb_cmdfile=$tmp_gdb_cmdfile_vpp
+ gdb_cmdfile=$VPP_GDB_CMDFILE
+ set_pre_cmd $emacs_vpp $gdb_vpp
+ write_script_header $cmd1_file $tmp_gdb_cmdfile "$title1"
+ if [ -n "$multi_host" ] ; then
+ echo -n "sudo " >> $cmd1_file
+ fi
+ echo "${pre_cmd}$vpp_dir$vpp_app $vpp_args" >> $cmd1_file
+ write_script_footer $cmd1_file $perf_vpp
+ chmod +x $cmd1_file
+
+ if [ -z "$multi_host" ] || [ "$multi_host" = "server" ] ; then
+ title2="SERVER$title_dbg (Docker-Preload Socket Test)"
+ tmp_gdb_cmdfile=$tmp_gdb_cmdfile_server
+ gdb_cmdfile=$VPPCOM_SERVER_GDB_CMDFILE
+ set_pre_cmd $emacs_server $gdb_server $docker_ld_preload_lib
+ write_script_header $cmd2_file $tmp_gdb_cmdfile "$title2" "sleep 2"
+ echo "docker run -it -v $vpp_shm_dir:$vpp_shm_dir -v $vpp_dir:$docker_vpp_dir -v $lib64_dir:$docker_lib64_dir -v $ld_preload_dir:$docker_ld_preload_dir -v $vppcom_conf_dir:$docker_vppcom_conf_dir -p $sock_srvr_port:$sock_srvr_port -e VPPCOM_CONF=${docker_vppcom_conf_dir}$vppcom_conf -e LD_LIBRARY_PATH=$docker_lib64_dir:$docker_ld_preload_dir ${docker_ld_preload}$docker_os ${docker_app_dir}${srvr_app}" >> $cmd2_file
+ write_script_footer $cmd2_file $perf_server
+ chmod +x $cmd2_file
+ fi
+
+ if [ -z "$multi_host" ] || [ "$multi_host" = "client" ] ; then
+ title3="CLIENT$title_dbg (Docker-Preload Socket Test)"
+ tmp_gdb_cmdfile=$tmp_gdb_cmdfile_client
+ gdb_cmdfile=$VPPCOM_CLIENT_GDB_CMDFILE
+ set_pre_cmd $emacs_client $gdb_client $docker_ld_preload_lib
+ write_script_header $cmd3_file $tmp_gdb_cmdfile "$title3" "sleep 3"
+ echo "$get_docker_server_ip4addr" >> $cmd3_file
+ echo "docker run -it --cpuset-cpus='4-7' -v $vpp_shm_dir:$vpp_shm_dir -v $vpp_dir:$docker_vpp_dir -v $lib64_dir:$docker_lib64_dir -v $ld_preload_dir:$docker_ld_preload_dir -v $vppcom_conf_dir:$docker_vppcom_conf_dir -e VPPCOM_CONF=${docker_vppcom_conf_dir}$vppcom_conf -e LD_LIBRARY_PATH=$docker_lib64_dir ${docker_ld_preload}$docker_os ${docker_app_dir}${clnt_app}" >> $cmd3_file
+ write_script_footer $cmd3_file $perf_client
+ chmod +x $cmd3_file
+ fi
+}
+
+docker_vcl() {
+ verify_no_vpp
+ verify_no_docker_containers
+ banner="Running DOCKER-VCL socket test"
+
+ title1="VPP$title_dbg (Docker-VCL Socket Test)"
+ tmp_gdb_cmdfile=$tmp_gdb_cmdfile_vpp
+ gdb_cmdfile=$VPP_GDB_CMDFILE
+ set_pre_cmd $emacs_vpp $gdb_vpp
+ write_script_header $cmd1_file $tmp_gdb_cmdfile "$title1"
+ if [ -n "$multi_host" ] ; then
+ echo -n "sudo " >> $cmd1_file
+ fi
+ echo "${pre_cmd}$vpp_dir$vpp_app $vpp_args" >> $cmd1_file
+ write_script_footer $cmd1_file $perf_vpp
+ chmod +x $cmd1_file
+
+ if [ -z "$multi_host" ] || [ "$multi_host" = "server" ] ; then
+ title2="SERVER$title_dbg (Docker-VCL Socket Test)"
+ tmp_gdb_cmdfile=$tmp_gdb_cmdfile_server
+ gdb_cmdfile=$VPPCOM_SERVER_GDB_CMDFILE
+ set_pre_cmd $emacs_server $gdb_server
+ write_script_header $cmd2_file $tmp_gdb_cmdfile "$title2" "sleep 2"
+ echo "docker run -it --cpuset-cpus='4-7' -v $vpp_shm_dir:$vpp_shm_dir -v $vpp_dir:$docker_vpp_dir -v $lib64_dir:$docker_lib64_dir -v $vppcom_conf_dir:$docker_vppcom_conf_dir -p $sock_srvr_port:$sock_srvr_port -e VPPCOM_CONF=${docker_vppcom_conf_dir}/$vppcom_conf -e LD_LIBRARY_PATH=$docker_lib64_dir $docker_os ${docker_app_dir}${srvr_app}" >> $cmd2_file
+ write_script_footer $cmd2_file $perf_server
+ chmod +x $cmd2_file
+ fi
+
+ if [ -z "$multi_host" ] || [ "$multi_host" = "client" ] ; then
+ title3="CLIENT$title_dbg (Docker-VCL Socket Test)"
+ tmp_gdb_cmdfile=$tmp_gdb_cmdfile_client
+ gdb_cmdfile=$VPPCOM_CLIENT_GDB_CMDFILE
+ set_pre_cmd $emacs_client $gdb_client
+ write_script_header $cmd3_file $tmp_gdb_cmdfile "$title3" "sleep 3"
+ echo "$get_docker_server_ip4addr" >> $cmd3_file
+ echo "docker run -it --cpuset-cpus='4-7' -v $vpp_shm_dir:$vpp_shm_dir -v $vpp_dir:$docker_vpp_dir -v $lib64_dir:$docker_lib64_dir -v $vppcom_conf_dir:$docker_vppcom_conf_dir -e VPPCOM_CONF=${docker_vppcom_conf_dir}/$vppcom_conf -e LD_LIBRARY_PATH=$docker_lib64_dir $docker_os ${docker_app_dir}${clnt_app}" >> $cmd3_file
+ write_script_footer $cmd3_file $perf_client
+ chmod +x $cmd3_file
+ fi
+}
+
+if [[ $run_test ]] ; then
+ eval $run_test
+else
+ echo "ERROR: Please specify a test to run!" >&2
+ usage;
+fi
+
+if (( $(which xfce4-terminal | wc -l) > 0 )) ; then
+ xterm_cmd="xfce4-terminal --geometry $xterm_geom"
+ if [[ $use_tabs ]] ; then
+ declare -a tab_cmd_files
+ declare -a tab_titles
+ declare -i i=0
+
+ if [ -x "$cmd1_file" ] ; then
+ tab_cmd_files[$i]="$cmd1_file"
+ tab_titles[$i]="$title1"
+ (( i++ ))
+ fi
+ if [ -x "$cmd2_file" ] ; then
+ tab_cmd_files[$i]="$cmd2_file"
+ tab_titles[$i]="$title2"
+ (( i++ ))
+ fi
+ if [ -x "$cmd3_file" ] ; then
+ tab_cmd_files[$i]="$cmd3_file"
+ tab_titles[$i]="$title3"
+ fi
+
+ if [ -n "${tab_cmd_files[2]}" ] ; then
+ $xterm_cmd --title "${tab_titles[0]}" --command "${tab_cmd_files[0]}" --tab --title "${tab_titles[1]}" --command "${tab_cmd_files[1]}" --tab --title "${tab_titles[2]}" --command "${tab_cmd_files[2]}"
+ elif [ -n "${tab_cmd_files[1]}" ] ; then
+ $xterm_cmd --title "${tab_titles[0]}" --command "${tab_cmd_files[0]}" --tab --title "${tab_titles[1]}" --command "${tab_cmd_files[1]}"
+
+ else
+ $xterm_cmd --title "${tab_titles[0]}" --command "${tab_cmd_files[0]}"
+ fi
+
+ else
+ if [ -x "$cmd1_file" ] ; then
+ ($xterm_cmd --title "$title1" --command "$cmd1_file" &)
+ fi
+ if [ -x "$cmd2_file" ] ; then
+ ($xterm_cmd --title "$title2" --command "$cmd2_file" &)
+ fi
+ if [ -x "$cmd3_file" ] ; then
+ ($xterm_cmd --title "$title3" --command "$cmd3_file" &)
+ fi
+ fi
+
+else
+ if [[ $use_tabs ]] ; then
+ echo "Sorry, plain ol' xterm doesn't support tabs."
+ fi
+ xterm_cmd="xterm -fs 10 -geometry $xterm_geom"
+ if [ -x "$cmd1_file" ] ; then
+ ($xterm_cmd -title "$title1" -e "$cmd1_file" &)
+ fi
+ if [ -x "$cmd2_file" ] ; then
+ ($xterm_cmd -title "$title2" -e "$cmd2_file" &)
+ fi
+ if [ -x "$cmd3_file" ] ; then
+ ($xterm_cmd -title "$title3" -e "$cmd3_file" &)
+ fi
+fi
+
+sleep 1
diff --git a/test/scripts/test-loop.sh b/test/scripts/test-loop.sh
new file mode 100755
index 00000000..51f5d5ce
--- /dev/null
+++ b/test/scripts/test-loop.sh
@@ -0,0 +1,123 @@
+#!/bin/bash
+
+function usage() {
+ echo "$0" 1>&2
+ echo "" 1>&2
+ echo "Usage: $0 [-p <pre-exec-cmd>] [-m <email>] -- <make test options|verify>" 1>&2
+ echo "" 1>&2
+ echo "Parameters:" 1>&2
+ echo " -p <pre-exec-cmd> - run a command before each test loop (e.g. 'git pull')" 1>&2
+ echo " -m <email> - if set, email is sent to this address on failure" 1>&2
+ echo "" 1>&2
+ echo "Examples:" 1>&2
+ echo " $0 -m <somebody@cisco.com> -- test-debug TEST=l2bd" 1>&2
+ echo " $0 -m <somebody@cisco.com> -- verify" 1>&2
+ exit 1;
+}
+
+PRE_EXEC_CMD=""
+EMAIL=""
+
+while getopts "p:m:h" o; do
+ case "${o}" in
+ p)
+ PRE_EXEC_CMD=${OPTARG}
+ ;;
+ m)
+ regex="^[a-z0-9!#\$%&'*+/=?^_\`{|}~-]+(\.[a-z0-9!#$%&'*+/=?^_\`{|}~-]+)*@([a-z0-9]([a-z0-9-]*[a-z0-9])?\.)+[a-z0-9]([a-z0-9-]*[a-z0-9])?\$"
+ m=${OPTARG}
+ if [[ ! $m =~ $regex ]]
+ then
+ echo "Invalid -m parameter value: \`$m'" >&2
+ usage
+ fi
+ EMAIL="$m"
+ ;;
+ h)
+ usage
+ ;;
+ ?)
+ usage
+ ;;
+esac
+ done
+shift $((OPTIND-1))
+
+if ! echo $* | grep test >/dev/null
+then
+ if ! echo $* | grep verify >/dev/null
+ then
+ echo "Error: command line doesn't look right - should contain \`test' or \`verify' token..." >&2
+ usage
+ fi
+fi
+
+function finish {
+ NOW=`date +%s`
+ RUNTIME=$((NOW - START))
+ AVG=$(echo "scale=2; $RUNTIME/$COUNT" | bc)
+ OUT="*********************************************************************"
+ OUT="$OUT\n* tail -n 30 $TMP:"
+ OUT="$OUT\n*********************************************************************"
+ OUT="$OUT\n`tail -n 30 $TMP`"
+ OUT="$OUT\n*********************************************************************"
+ OUT="$OUT\n* Total runtime: ${RUNTIME}s"
+ OUT="$OUT\n* Iterations: ${COUNT}"
+ OUT="$OUT\n* Average time: ${AVG}s"
+ OUT="$OUT\n* Log file: ${TMP}"
+ OUT="$OUT\n*********************************************************************"
+ echo -e "$OUT"
+ if [[ "$EMAIL" != "" && "$REASON" != "" ]]
+ then
+ SUBJECT="test loop finished ($REASON)"
+ echo -e "$OUT" | mail -s "$SUBJECT" $EMAIL
+ fi
+}
+
+trap "echo Caught signal, exiting...; REASON=\"received signal\"; finish; exit -1" SIGINT SIGTERM
+
+TMP=`mktemp`
+START=`date +%s`
+COUNT=0
+
+if ! test -f "$TMP"
+then
+ echo "Couldn't create temporary file!"
+ exit -1
+fi
+
+echo "Temporary file is $TMP"
+CMD="make $*"
+echo "Command line is \`$CMD'"
+
+REASON=""
+while true
+do
+ COUNT=$((COUNT+1))
+ BEFORE=`date +%s`
+ if [[ "$PRE_EXEC_CMD" != "" ]]
+ then
+ echo "Executing \`$PRE_EXEC_CMD' before test.."
+ if ! ($PRE_EXEC_CMD 2>&1 | tee $TMP)
+ then
+ echo "\`$PRE_EXEC_CMD' failed!" >&2
+ REASON="$PRE_EXEC_CMD failed"
+ break
+ fi
+ fi
+ echo -n "Running test iteration #$COUNT..."
+ if ! ($CMD >$TMP 2>&1)
+ then
+ AFTER=`date +%s`
+ RUNTIME=$((AFTER-BEFORE))
+ echo "FAILED! (after ${RUNTIME}s)"
+ REASON="test failed"
+ break
+ fi
+ AFTER=`date +%s`
+ RUNTIME=$((AFTER-BEFORE))
+ echo "PASSED (after ${RUNTIME}s)"
+done
+
+finish
+exit 1
diff --git a/test/template_bd.py b/test/template_bd.py
new file mode 100644
index 00000000..080b2e6b
--- /dev/null
+++ b/test/template_bd.py
@@ -0,0 +1,158 @@
+#!/usr/bin/env python
+
+from abc import abstractmethod, ABCMeta
+
+from scapy.layers.l2 import Ether, Raw
+from scapy.layers.inet import IP, UDP
+
+from util import ip4_range
+
+
+class BridgeDomain(object):
+ """ Bridge domain abstraction """
+ __metaclass__ = ABCMeta
+
+ @property
+ def frame_request(self):
+ """ Ethernet frame modeling a generic request """
+ return (Ether(src='00:00:00:00:00:01', dst='00:00:00:00:00:02') /
+ IP(src='1.2.3.4', dst='4.3.2.1') /
+ UDP(sport=10000, dport=20000) /
+ Raw('\xa5' * 100))
+
+ @property
+ def frame_reply(self):
+ """ Ethernet frame modeling a generic reply """
+ return (Ether(src='00:00:00:00:00:02', dst='00:00:00:00:00:01') /
+ IP(src='4.3.2.1', dst='1.2.3.4') /
+ UDP(sport=20000, dport=10000) /
+ Raw('\xa5' * 100))
+
+ @abstractmethod
+ def encap_mcast(self, pkt, src_ip, src_mac, vni):
+ """ Encapsulate mcast packet """
+ pass
+
+ @abstractmethod
+ def encapsulate(self, pkt, vni):
+ """ Encapsulate packet """
+ pass
+
+ @abstractmethod
+ def decapsulate(self, pkt):
+ """ Decapsulate packet """
+ pass
+
+ @abstractmethod
+ def check_encapsulation(self, pkt, vni, local_only=False):
+ """ Verify the encapsulation """
+ pass
+
+ def assert_eq_pkts(self, pkt1, pkt2):
+ """ Verify the Ether, IP, UDP, payload are equal in both
+ packets
+ """
+ self.assertEqual(pkt1[Ether].src, pkt2[Ether].src)
+ self.assertEqual(pkt1[Ether].dst, pkt2[Ether].dst)
+ self.assertEqual(pkt1[IP].src, pkt2[IP].src)
+ self.assertEqual(pkt1[IP].dst, pkt2[IP].dst)
+ self.assertEqual(pkt1[UDP].sport, pkt2[UDP].sport)
+ self.assertEqual(pkt1[UDP].dport, pkt2[UDP].dport)
+ self.assertEqual(pkt1[Raw], pkt2[Raw])
+
+ def test_decap(self):
+ """ Decapsulation test
+ Send encapsulated frames from pg0
+ Verify receipt of decapsulated frames on pg1
+ """
+
+ encapsulated_pkt = self.encapsulate(self.frame_request,
+ self.single_tunnel_bd)
+
+ self.pg0.add_stream([encapsulated_pkt, ])
+
+ self.pg1.enable_capture()
+
+ self.pg_start()
+
+ # Pick first received frame and check if it's the non-encapsulated
+ # frame
+ out = self.pg1.get_capture(1)
+ pkt = out[0]
+ self.assert_eq_pkts(pkt, self.frame_request)
+
+ def test_encap(self):
+ """ Encapsulation test
+ Send frames from pg1
+ Verify receipt of encapsulated frames on pg0
+ """
+ self.pg1.add_stream([self.frame_reply])
+
+ self.pg0.enable_capture()
+
+ self.pg_start()
+
+ # Pick first received frame and check if it's corectly encapsulated.
+ out = self.pg0.get_capture(1)
+ pkt = out[0]
+ self.check_encapsulation(pkt, self.single_tunnel_bd)
+
+ payload = self.decapsulate(pkt)
+ self.assert_eq_pkts(payload, self.frame_reply)
+
+ def test_ucast_flood(self):
+ """ Unicast flood test
+ Send frames from pg3
+ Verify receipt of encapsulated frames on pg0
+ """
+ self.pg3.add_stream([self.frame_reply])
+
+ self.pg0.enable_capture()
+
+ self.pg_start()
+
+ # Get packet from each tunnel and assert it's corectly encapsulated.
+ out = self.pg0.get_capture(self.n_ucast_tunnels)
+ for pkt in out:
+ self.check_encapsulation(pkt, self.ucast_flood_bd, True)
+ payload = self.decapsulate(pkt)
+ self.assert_eq_pkts(payload, self.frame_reply)
+
+ def test_mcast_flood(self):
+ """ Multicast flood test
+ Send frames from pg2
+ Verify receipt of encapsulated frames on pg0
+ """
+ self.pg2.add_stream([self.frame_reply])
+
+ self.pg0.enable_capture()
+
+ self.pg_start()
+
+ # Pick first received frame and check if it's corectly encapsulated.
+ out = self.pg0.get_capture(1)
+ pkt = out[0]
+ self.check_encapsulation(pkt, self.mcast_flood_bd,
+ local_only=False, mcast_pkt=True)
+
+ payload = self.decapsulate(pkt)
+ self.assert_eq_pkts(payload, self.frame_reply)
+
+ def test_mcast_rcv(self):
+ """ Multicast receive test
+ Send 20 encapsulated frames from pg0 only 10 match unicast tunnels
+ Verify receipt of 10 decap frames on pg2
+ """
+ mac = self.pg0.remote_mac
+ ip_range_start = 10
+ ip_range_end = 30
+ mcast_stream = [
+ self.encap_mcast(self.frame_request, ip, mac, self.mcast_flood_bd)
+ for ip in ip4_range(self.pg0.remote_ip4,
+ ip_range_start, ip_range_end)]
+ self.pg0.add_stream(mcast_stream)
+ self.pg2.enable_capture()
+ self.pg_start()
+ out = self.pg2.get_capture(10)
+ for pkt in out:
+ self.assert_eq_pkts(pkt, self.frame_request)
diff --git a/test/test_acl_plugin.py b/test/test_acl_plugin.py
new file mode 100644
index 00000000..cd375a2c
--- /dev/null
+++ b/test/test_acl_plugin.py
@@ -0,0 +1,1313 @@
+#!/usr/bin/env python
+"""ACL plugin Test Case HLD:
+"""
+
+import unittest
+import random
+
+from scapy.packet import Raw
+from scapy.layers.l2 import Ether
+from scapy.layers.inet import IP, TCP, UDP, ICMP
+from scapy.layers.inet6 import IPv6, ICMPv6EchoRequest
+from scapy.layers.inet6 import IPv6ExtHdrFragment
+from framework import VppTestCase, VppTestRunner
+from util import Host, ppp
+
+
+class TestACLplugin(VppTestCase):
+ """ ACL plugin Test Case """
+
+ # traffic types
+ IP = 0
+ ICMP = 1
+
+ # IP version
+ IPRANDOM = -1
+ IPV4 = 0
+ IPV6 = 1
+
+ # rule types
+ DENY = 0
+ PERMIT = 1
+
+ # supported protocols
+ proto = [[6, 17], [1, 58]]
+ proto_map = {1: 'ICMP', 58: 'ICMPv6EchoRequest', 6: 'TCP', 17: 'UDP'}
+ ICMPv4 = 0
+ ICMPv6 = 1
+ TCP = 0
+ UDP = 1
+ PROTO_ALL = 0
+
+ # port ranges
+ PORTS_ALL = -1
+ PORTS_RANGE = 0
+ PORTS_RANGE_2 = 1
+ udp_sport_from = 10
+ udp_sport_to = udp_sport_from + 5
+ udp_dport_from = 20000
+ udp_dport_to = udp_dport_from + 5000
+ tcp_sport_from = 30
+ tcp_sport_to = tcp_sport_from + 5
+ tcp_dport_from = 40000
+ tcp_dport_to = tcp_dport_from + 5000
+
+ udp_sport_from_2 = 90
+ udp_sport_to_2 = udp_sport_from_2 + 5
+ udp_dport_from_2 = 30000
+ udp_dport_to_2 = udp_dport_from_2 + 5000
+ tcp_sport_from_2 = 130
+ tcp_sport_to_2 = tcp_sport_from_2 + 5
+ tcp_dport_from_2 = 20000
+ tcp_dport_to_2 = tcp_dport_from_2 + 5000
+
+ icmp4_type = 8 # echo request
+ icmp4_code = 3
+ icmp6_type = 128 # echo request
+ icmp6_code = 3
+
+ icmp4_type_2 = 8
+ icmp4_code_from_2 = 5
+ icmp4_code_to_2 = 20
+ icmp6_type_2 = 128
+ icmp6_code_from_2 = 8
+ icmp6_code_to_2 = 42
+
+ # Test variables
+ bd_id = 1
+
+ @classmethod
+ def setUpClass(cls):
+ """
+ Perform standard class setup (defined by class method setUpClass in
+ class VppTestCase) before running the test case, set test case related
+ variables and configure VPP.
+ """
+ super(TestACLplugin, cls).setUpClass()
+
+ random.seed()
+
+ try:
+ # Create 2 pg interfaces
+ cls.create_pg_interfaces(range(2))
+
+ # Packet flows mapping pg0 -> pg1, pg2 etc.
+ cls.flows = dict()
+ cls.flows[cls.pg0] = [cls.pg1]
+
+ # Packet sizes
+ cls.pg_if_packet_sizes = [64, 512, 1518, 9018]
+
+ # Create BD with MAC learning and unknown unicast flooding disabled
+ # and put interfaces to this BD
+ cls.vapi.bridge_domain_add_del(bd_id=cls.bd_id, uu_flood=1,
+ learn=1)
+ for pg_if in cls.pg_interfaces:
+ cls.vapi.sw_interface_set_l2_bridge(pg_if.sw_if_index,
+ bd_id=cls.bd_id)
+
+ # Set up all interfaces
+ for i in cls.pg_interfaces:
+ i.admin_up()
+
+ # Mapping between packet-generator index and lists of test hosts
+ cls.hosts_by_pg_idx = dict()
+ for pg_if in cls.pg_interfaces:
+ cls.hosts_by_pg_idx[pg_if.sw_if_index] = []
+
+ # Create list of deleted hosts
+ cls.deleted_hosts_by_pg_idx = dict()
+ for pg_if in cls.pg_interfaces:
+ cls.deleted_hosts_by_pg_idx[pg_if.sw_if_index] = []
+
+ # warm-up the mac address tables
+ # self.warmup_test()
+
+ except Exception:
+ super(TestACLplugin, cls).tearDownClass()
+ raise
+
+ def setUp(self):
+ super(TestACLplugin, self).setUp()
+ self.reset_packet_infos()
+
+ def tearDown(self):
+ """
+ Show various debug prints after each test.
+ """
+ super(TestACLplugin, self).tearDown()
+ if not self.vpp_dead:
+ self.logger.info(self.vapi.ppcli("show l2fib verbose"))
+ self.logger.info(self.vapi.ppcli("show acl-plugin acl"))
+ self.logger.info(self.vapi.ppcli("show acl-plugin interface"))
+ self.logger.info(self.vapi.ppcli("show acl-plugin tables"))
+ self.logger.info(self.vapi.ppcli("show bridge-domain %s detail"
+ % self.bd_id))
+
+ def create_hosts(self, count, start=0):
+ """
+ Create required number of host MAC addresses and distribute them among
+ interfaces. Create host IPv4 address for every host MAC address.
+
+ :param int count: Number of hosts to create MAC/IPv4 addresses for.
+ :param int start: Number to start numbering from.
+ """
+ n_int = len(self.pg_interfaces)
+ macs_per_if = count / n_int
+ i = -1
+ for pg_if in self.pg_interfaces:
+ i += 1
+ start_nr = macs_per_if * i + start
+ end_nr = count + start if i == (n_int - 1) \
+ else macs_per_if * (i + 1) + start
+ hosts = self.hosts_by_pg_idx[pg_if.sw_if_index]
+ for j in range(start_nr, end_nr):
+ host = Host(
+ "00:00:00:ff:%02x:%02x" % (pg_if.sw_if_index, j),
+ "172.17.1%02x.%u" % (pg_if.sw_if_index, j),
+ "2017:dead:%02x::%u" % (pg_if.sw_if_index, j))
+ hosts.append(host)
+
+ def create_rule(self, ip=0, permit_deny=0, ports=PORTS_ALL, proto=-1,
+ s_prefix=0, s_ip='\x00\x00\x00\x00',
+ d_prefix=0, d_ip='\x00\x00\x00\x00'):
+ if proto == -1:
+ return
+ if ports == self.PORTS_ALL:
+ sport_from = 0
+ dport_from = 0
+ sport_to = 65535 if proto != 1 and proto != 58 else 255
+ dport_to = sport_to
+ elif ports == self.PORTS_RANGE:
+ if proto == 1:
+ sport_from = self.icmp4_type
+ sport_to = self.icmp4_type
+ dport_from = self.icmp4_code
+ dport_to = self.icmp4_code
+ elif proto == 58:
+ sport_from = self.icmp6_type
+ sport_to = self.icmp6_type
+ dport_from = self.icmp6_code
+ dport_to = self.icmp6_code
+ elif proto == self.proto[self.IP][self.TCP]:
+ sport_from = self.tcp_sport_from
+ sport_to = self.tcp_sport_to
+ dport_from = self.tcp_dport_from
+ dport_to = self.tcp_dport_to
+ elif proto == self.proto[self.IP][self.UDP]:
+ sport_from = self.udp_sport_from
+ sport_to = self.udp_sport_to
+ dport_from = self.udp_dport_from
+ dport_to = self.udp_dport_to
+ elif ports == self.PORTS_RANGE_2:
+ if proto == 1:
+ sport_from = self.icmp4_type_2
+ sport_to = self.icmp4_type_2
+ dport_from = self.icmp4_code_from_2
+ dport_to = self.icmp4_code_to_2
+ elif proto == 58:
+ sport_from = self.icmp6_type_2
+ sport_to = self.icmp6_type_2
+ dport_from = self.icmp6_code_from_2
+ dport_to = self.icmp6_code_to_2
+ elif proto == self.proto[self.IP][self.TCP]:
+ sport_from = self.tcp_sport_from_2
+ sport_to = self.tcp_sport_to_2
+ dport_from = self.tcp_dport_from_2
+ dport_to = self.tcp_dport_to_2
+ elif proto == self.proto[self.IP][self.UDP]:
+ sport_from = self.udp_sport_from_2
+ sport_to = self.udp_sport_to_2
+ dport_from = self.udp_dport_from_2
+ dport_to = self.udp_dport_to_2
+ else:
+ sport_from = ports
+ sport_to = ports
+ dport_from = ports
+ dport_to = ports
+
+ rule = ({'is_permit': permit_deny, 'is_ipv6': ip, 'proto': proto,
+ 'srcport_or_icmptype_first': sport_from,
+ 'srcport_or_icmptype_last': sport_to,
+ 'src_ip_prefix_len': s_prefix,
+ 'src_ip_addr': s_ip,
+ 'dstport_or_icmpcode_first': dport_from,
+ 'dstport_or_icmpcode_last': dport_to,
+ 'dst_ip_prefix_len': d_prefix,
+ 'dst_ip_addr': d_ip})
+ return rule
+
+ def apply_rules(self, rules, tag=''):
+ reply = self.vapi.acl_add_replace(acl_index=4294967295, r=rules,
+ tag=tag)
+ self.logger.info("Dumped ACL: " + str(
+ self.vapi.acl_dump(reply.acl_index)))
+ # Apply a ACL on the interface as inbound
+ for i in self.pg_interfaces:
+ self.vapi.acl_interface_set_acl_list(sw_if_index=i.sw_if_index,
+ n_input=1,
+ acls=[reply.acl_index])
+ return
+
+ def create_upper_layer(self, packet_index, proto, ports=0):
+ p = self.proto_map[proto]
+ if p == 'UDP':
+ if ports == 0:
+ return UDP(sport=random.randint(self.udp_sport_from,
+ self.udp_sport_to),
+ dport=random.randint(self.udp_dport_from,
+ self.udp_dport_to))
+ else:
+ return UDP(sport=ports, dport=ports)
+ elif p == 'TCP':
+ if ports == 0:
+ return TCP(sport=random.randint(self.tcp_sport_from,
+ self.tcp_sport_to),
+ dport=random.randint(self.tcp_dport_from,
+ self.tcp_dport_to))
+ else:
+ return TCP(sport=ports, dport=ports)
+ return ''
+
+ def create_stream(self, src_if, packet_sizes, traffic_type=0, ipv6=0,
+ proto=-1, ports=0, fragments=False, pkt_raw=True):
+ """
+ Create input packet stream for defined interface using hosts or
+ deleted_hosts list.
+
+ :param object src_if: Interface to create packet stream for.
+ :param list packet_sizes: List of required packet sizes.
+ :param traffic_type: 1: ICMP packet, 2: IPv6 with EH, 0: otherwise.
+ :return: Stream of packets.
+ """
+ pkts = []
+ if self.flows.__contains__(src_if):
+ src_hosts = self.hosts_by_pg_idx[src_if.sw_if_index]
+ for dst_if in self.flows[src_if]:
+ dst_hosts = self.hosts_by_pg_idx[dst_if.sw_if_index]
+ n_int = len(dst_hosts) * len(src_hosts)
+ for i in range(0, n_int):
+ dst_host = dst_hosts[i / len(src_hosts)]
+ src_host = src_hosts[i % len(src_hosts)]
+ pkt_info = self.create_packet_info(src_if, dst_if)
+ if ipv6 == 1:
+ pkt_info.ip = 1
+ elif ipv6 == 0:
+ pkt_info.ip = 0
+ else:
+ pkt_info.ip = random.choice([0, 1])
+ if proto == -1:
+ pkt_info.proto = random.choice(self.proto[self.IP])
+ else:
+ pkt_info.proto = proto
+ payload = self.info_to_payload(pkt_info)
+ p = Ether(dst=dst_host.mac, src=src_host.mac)
+ if pkt_info.ip:
+ p /= IPv6(dst=dst_host.ip6, src=src_host.ip6)
+ if fragments:
+ p /= IPv6ExtHdrFragment(offset=64, m=1)
+ else:
+ if fragments:
+ p /= IP(src=src_host.ip4, dst=dst_host.ip4,
+ flags=1, frag=64)
+ else:
+ p /= IP(src=src_host.ip4, dst=dst_host.ip4)
+ if traffic_type == self.ICMP:
+ if pkt_info.ip:
+ p /= ICMPv6EchoRequest(type=self.icmp6_type,
+ code=self.icmp6_code)
+ else:
+ p /= ICMP(type=self.icmp4_type,
+ code=self.icmp4_code)
+ else:
+ p /= self.create_upper_layer(i, pkt_info.proto, ports)
+ if pkt_raw:
+ p /= Raw(payload)
+ pkt_info.data = p.copy()
+ if pkt_raw:
+ size = random.choice(packet_sizes)
+ self.extend_packet(p, size)
+ pkts.append(p)
+ return pkts
+
+ def verify_capture(self, pg_if, capture, traffic_type=0, ip_type=0):
+ """
+ Verify captured input packet stream for defined interface.
+
+ :param object pg_if: Interface to verify captured packet stream for.
+ :param list capture: Captured packet stream.
+ :param traffic_type: 1: ICMP packet, 2: IPv6 with EH, 0: otherwise.
+ """
+ last_info = dict()
+ for i in self.pg_interfaces:
+ last_info[i.sw_if_index] = None
+ dst_sw_if_index = pg_if.sw_if_index
+ for packet in capture:
+ try:
+ # Raw data for ICMPv6 are stored in ICMPv6EchoRequest.data
+ if traffic_type == self.ICMP and ip_type == self.IPV6:
+ payload_info = self.payload_to_info(
+ packet[ICMPv6EchoRequest].data)
+ payload = packet[ICMPv6EchoRequest]
+ else:
+ payload_info = self.payload_to_info(str(packet[Raw]))
+ payload = packet[self.proto_map[payload_info.proto]]
+ except:
+ self.logger.error(ppp("Unexpected or invalid packet "
+ "(outside network):", packet))
+ raise
+
+ if ip_type != 0:
+ self.assertEqual(payload_info.ip, ip_type)
+ if traffic_type == self.ICMP:
+ try:
+ if payload_info.ip == 0:
+ self.assertEqual(payload.type, self.icmp4_type)
+ self.assertEqual(payload.code, self.icmp4_code)
+ else:
+ self.assertEqual(payload.type, self.icmp6_type)
+ self.assertEqual(payload.code, self.icmp6_code)
+ except:
+ self.logger.error(ppp("Unexpected or invalid packet "
+ "(outside network):", packet))
+ raise
+ else:
+ try:
+ ip_version = IPv6 if payload_info.ip == 1 else IP
+
+ ip = packet[ip_version]
+ packet_index = payload_info.index
+
+ self.assertEqual(payload_info.dst, dst_sw_if_index)
+ self.logger.debug("Got packet on port %s: src=%u (id=%u)" %
+ (pg_if.name, payload_info.src,
+ packet_index))
+ next_info = self.get_next_packet_info_for_interface2(
+ payload_info.src, dst_sw_if_index,
+ last_info[payload_info.src])
+ last_info[payload_info.src] = next_info
+ self.assertTrue(next_info is not None)
+ self.assertEqual(packet_index, next_info.index)
+ saved_packet = next_info.data
+ # Check standard fields
+ self.assertEqual(ip.src, saved_packet[ip_version].src)
+ self.assertEqual(ip.dst, saved_packet[ip_version].dst)
+ p = self.proto_map[payload_info.proto]
+ if p == 'TCP':
+ tcp = packet[TCP]
+ self.assertEqual(tcp.sport, saved_packet[
+ TCP].sport)
+ self.assertEqual(tcp.dport, saved_packet[
+ TCP].dport)
+ elif p == 'UDP':
+ udp = packet[UDP]
+ self.assertEqual(udp.sport, saved_packet[
+ UDP].sport)
+ self.assertEqual(udp.dport, saved_packet[
+ UDP].dport)
+ except:
+ self.logger.error(ppp("Unexpected or invalid packet:",
+ packet))
+ raise
+ for i in self.pg_interfaces:
+ remaining_packet = self.get_next_packet_info_for_interface2(
+ i, dst_sw_if_index, last_info[i.sw_if_index])
+ self.assertTrue(
+ remaining_packet is None,
+ "Port %u: Packet expected from source %u didn't arrive" %
+ (dst_sw_if_index, i.sw_if_index))
+
+ def run_traffic_no_check(self):
+ # Test
+ # Create incoming packet streams for packet-generator interfaces
+ for i in self.pg_interfaces:
+ if self.flows.__contains__(i):
+ pkts = self.create_stream(i, self.pg_if_packet_sizes)
+ if len(pkts) > 0:
+ i.add_stream(pkts)
+
+ # Enable packet capture and start packet sending
+ self.pg_enable_capture(self.pg_interfaces)
+ self.pg_start()
+
+ def run_verify_test(self, traffic_type=0, ip_type=0, proto=-1, ports=0,
+ frags=False, pkt_raw=True):
+ # Test
+ # Create incoming packet streams for packet-generator interfaces
+ pkts_cnt = 0
+ for i in self.pg_interfaces:
+ if self.flows.__contains__(i):
+ pkts = self.create_stream(i, self.pg_if_packet_sizes,
+ traffic_type, ip_type, proto, ports,
+ frags, pkt_raw)
+ if len(pkts) > 0:
+ i.add_stream(pkts)
+ pkts_cnt += len(pkts)
+
+ # Enable packet capture and start packet sendingself.IPV
+ self.pg_enable_capture(self.pg_interfaces)
+ self.pg_start()
+
+ # Verify
+ # Verify outgoing packet streams per packet-generator interface
+ for src_if in self.pg_interfaces:
+ if self.flows.__contains__(src_if):
+ for dst_if in self.flows[src_if]:
+ capture = dst_if.get_capture(pkts_cnt)
+ self.logger.info("Verifying capture on interface %s" %
+ dst_if.name)
+ self.verify_capture(dst_if, capture, traffic_type, ip_type)
+
+ def run_verify_negat_test(self, traffic_type=0, ip_type=0, proto=-1,
+ ports=0, frags=False):
+ # Test
+ self.reset_packet_infos()
+ for i in self.pg_interfaces:
+ if self.flows.__contains__(i):
+ pkts = self.create_stream(i, self.pg_if_packet_sizes,
+ traffic_type, ip_type, proto, ports,
+ frags)
+ if len(pkts) > 0:
+ i.add_stream(pkts)
+
+ # Enable packet capture and start packet sending
+ self.pg_enable_capture(self.pg_interfaces)
+ self.pg_start()
+
+ # Verify
+ # Verify outgoing packet streams per packet-generator interface
+ for src_if in self.pg_interfaces:
+ if self.flows.__contains__(src_if):
+ for dst_if in self.flows[src_if]:
+ self.logger.info("Verifying capture on interface %s" %
+ dst_if.name)
+ capture = dst_if.get_capture(0)
+ self.assertEqual(len(capture), 0)
+
+ def test_0000_warmup_test(self):
+ """ ACL plugin version check; learn MACs
+ """
+ self.create_hosts(16)
+ self.run_traffic_no_check()
+ reply = self.vapi.papi.acl_plugin_get_version()
+ self.assertEqual(reply.major, 1)
+ self.logger.info("Working with ACL plugin version: %d.%d" % (
+ reply.major, reply.minor))
+ # minor version changes are non breaking
+ # self.assertEqual(reply.minor, 0)
+
+ def test_0001_acl_create(self):
+ """ ACL create/delete test
+ """
+
+ self.logger.info("ACLP_TEST_START_0001")
+ # Add an ACL
+ r = [{'is_permit': 1, 'is_ipv6': 0, 'proto': 17,
+ 'srcport_or_icmptype_first': 1234,
+ 'srcport_or_icmptype_last': 1235,
+ 'src_ip_prefix_len': 0,
+ 'src_ip_addr': '\x00\x00\x00\x00',
+ 'dstport_or_icmpcode_first': 1234,
+ 'dstport_or_icmpcode_last': 1234,
+ 'dst_ip_addr': '\x00\x00\x00\x00',
+ 'dst_ip_prefix_len': 0}]
+ # Test 1: add a new ACL
+ reply = self.vapi.acl_add_replace(acl_index=4294967295, r=r,
+ tag="permit 1234")
+ self.assertEqual(reply.retval, 0)
+ # The very first ACL gets #0
+ self.assertEqual(reply.acl_index, 0)
+ first_acl = reply.acl_index
+ rr = self.vapi.acl_dump(reply.acl_index)
+ self.logger.info("Dumped ACL: " + str(rr))
+ self.assertEqual(len(rr), 1)
+ # We should have the same number of ACL entries as we had asked
+ self.assertEqual(len(rr[0].r), len(r))
+ # The rules should be the same. But because the submitted and returned
+ # are different types, we need to iterate over rules and keys to get
+ # to basic values.
+ for i_rule in range(0, len(r) - 1):
+ for rule_key in r[i_rule]:
+ self.assertEqual(rr[0].r[i_rule][rule_key],
+ r[i_rule][rule_key])
+
+ # Add a deny-1234 ACL
+ r_deny = [{'is_permit': 0, 'is_ipv6': 0, 'proto': 17,
+ 'srcport_or_icmptype_first': 1234,
+ 'srcport_or_icmptype_last': 1235,
+ 'src_ip_prefix_len': 0,
+ 'src_ip_addr': '\x00\x00\x00\x00',
+ 'dstport_or_icmpcode_first': 1234,
+ 'dstport_or_icmpcode_last': 1234,
+ 'dst_ip_addr': '\x00\x00\x00\x00',
+ 'dst_ip_prefix_len': 0},
+ {'is_permit': 1, 'is_ipv6': 0, 'proto': 17,
+ 'srcport_or_icmptype_first': 0,
+ 'srcport_or_icmptype_last': 0,
+ 'src_ip_prefix_len': 0,
+ 'src_ip_addr': '\x00\x00\x00\x00',
+ 'dstport_or_icmpcode_first': 0,
+ 'dstport_or_icmpcode_last': 0,
+ 'dst_ip_addr': '\x00\x00\x00\x00',
+ 'dst_ip_prefix_len': 0}]
+
+ reply = self.vapi.acl_add_replace(acl_index=4294967295, r=r_deny,
+ tag="deny 1234;permit all")
+ self.assertEqual(reply.retval, 0)
+ # The second ACL gets #1
+ self.assertEqual(reply.acl_index, 1)
+ second_acl = reply.acl_index
+
+ # Test 2: try to modify a nonexistent ACL
+ reply = self.vapi.acl_add_replace(acl_index=432, r=r,
+ tag="FFFF:FFFF", expected_retval=-1)
+ self.assertEqual(reply.retval, -1)
+ # The ACL number should pass through
+ self.assertEqual(reply.acl_index, 432)
+ # apply an ACL on an interface inbound, try to delete ACL, must fail
+ self.vapi.acl_interface_set_acl_list(sw_if_index=self.pg0.sw_if_index,
+ n_input=1,
+ acls=[first_acl])
+ reply = self.vapi.acl_del(acl_index=first_acl, expected_retval=-1)
+ # Unapply an ACL and then try to delete it - must be ok
+ self.vapi.acl_interface_set_acl_list(sw_if_index=self.pg0.sw_if_index,
+ n_input=0,
+ acls=[])
+ reply = self.vapi.acl_del(acl_index=first_acl, expected_retval=0)
+
+ # apply an ACL on an interface outbound, try to delete ACL, must fail
+ self.vapi.acl_interface_set_acl_list(sw_if_index=self.pg0.sw_if_index,
+ n_input=0,
+ acls=[second_acl])
+ reply = self.vapi.acl_del(acl_index=second_acl, expected_retval=-1)
+ # Unapply the ACL and then try to delete it - must be ok
+ self.vapi.acl_interface_set_acl_list(sw_if_index=self.pg0.sw_if_index,
+ n_input=0,
+ acls=[])
+ reply = self.vapi.acl_del(acl_index=second_acl, expected_retval=0)
+
+ # try to apply a nonexistent ACL - must fail
+ self.vapi.acl_interface_set_acl_list(sw_if_index=self.pg0.sw_if_index,
+ n_input=1,
+ acls=[first_acl],
+ expected_retval=-1)
+
+ self.logger.info("ACLP_TEST_FINISH_0001")
+
+ def test_0002_acl_permit_apply(self):
+ """ permit ACL apply test
+ """
+ self.logger.info("ACLP_TEST_START_0002")
+
+ rules = []
+ rules.append(self.create_rule(self.IPV4, self.PERMIT,
+ 0, self.proto[self.IP][self.UDP]))
+ rules.append(self.create_rule(self.IPV4, self.PERMIT,
+ 0, self.proto[self.IP][self.TCP]))
+
+ # Apply rules
+ self.apply_rules(rules, "permit per-flow")
+
+ # Traffic should still pass
+ self.run_verify_test(self.IP, self.IPV4, -1)
+ self.logger.info("ACLP_TEST_FINISH_0002")
+
+ def test_0003_acl_deny_apply(self):
+ """ deny ACL apply test
+ """
+ self.logger.info("ACLP_TEST_START_0003")
+ # Add a deny-flows ACL
+ rules = []
+ rules.append(self.create_rule(self.IPV4, self.DENY,
+ self.PORTS_ALL, self.proto[self.IP][self.UDP]))
+ # Permit ip any any in the end
+ rules.append(self.create_rule(self.IPV4, self.PERMIT,
+ self.PORTS_ALL, 0))
+
+ # Apply rules
+ self.apply_rules(rules, "deny per-flow;permit all")
+
+ # Traffic should not pass
+ self.run_verify_negat_test(self.IP, self.IPV4,
+ self.proto[self.IP][self.UDP])
+ self.logger.info("ACLP_TEST_FINISH_0003")
+ # self.assertEqual(1, 0)
+
+ def test_0004_vpp624_permit_icmpv4(self):
+ """ VPP_624 permit ICMPv4
+ """
+ self.logger.info("ACLP_TEST_START_0004")
+
+ # Add an ACL
+ rules = []
+ rules.append(self.create_rule(self.IPV4, self.PERMIT, self.PORTS_RANGE,
+ self.proto[self.ICMP][self.ICMPv4]))
+ # deny ip any any in the end
+ rules.append(self.create_rule(self.IPV4, self.DENY, self.PORTS_ALL, 0))
+
+ # Apply rules
+ self.apply_rules(rules, "permit icmpv4")
+
+ # Traffic should still pass
+ self.run_verify_test(self.ICMP, self.IPV4,
+ self.proto[self.ICMP][self.ICMPv4])
+
+ self.logger.info("ACLP_TEST_FINISH_0004")
+
+ def test_0005_vpp624_permit_icmpv6(self):
+ """ VPP_624 permit ICMPv6
+ """
+ self.logger.info("ACLP_TEST_START_0005")
+
+ # Add an ACL
+ rules = []
+ rules.append(self.create_rule(self.IPV6, self.PERMIT, self.PORTS_RANGE,
+ self.proto[self.ICMP][self.ICMPv6]))
+ # deny ip any any in the end
+ rules.append(self.create_rule(self.IPV6, self.DENY, self.PORTS_ALL, 0))
+
+ # Apply rules
+ self.apply_rules(rules, "permit icmpv6")
+
+ # Traffic should still pass
+ self.run_verify_test(self.ICMP, self.IPV6,
+ self.proto[self.ICMP][self.ICMPv6])
+
+ self.logger.info("ACLP_TEST_FINISH_0005")
+
+ def test_0006_vpp624_deny_icmpv4(self):
+ """ VPP_624 deny ICMPv4
+ """
+ self.logger.info("ACLP_TEST_START_0006")
+ # Add an ACL
+ rules = []
+ rules.append(self.create_rule(self.IPV4, self.DENY, self.PORTS_RANGE,
+ self.proto[self.ICMP][self.ICMPv4]))
+ # permit ip any any in the end
+ rules.append(self.create_rule(self.IPV4, self.PERMIT,
+ self.PORTS_ALL, 0))
+
+ # Apply rules
+ self.apply_rules(rules, "deny icmpv4")
+
+ # Traffic should not pass
+ self.run_verify_negat_test(self.ICMP, self.IPV4, 0)
+
+ self.logger.info("ACLP_TEST_FINISH_0006")
+
+ def test_0007_vpp624_deny_icmpv6(self):
+ """ VPP_624 deny ICMPv6
+ """
+ self.logger.info("ACLP_TEST_START_0007")
+ # Add an ACL
+ rules = []
+ rules.append(self.create_rule(self.IPV6, self.DENY, self.PORTS_RANGE,
+ self.proto[self.ICMP][self.ICMPv6]))
+ # deny ip any any in the end
+ rules.append(self.create_rule(self.IPV6, self.PERMIT,
+ self.PORTS_ALL, 0))
+
+ # Apply rules
+ self.apply_rules(rules, "deny icmpv6")
+
+ # Traffic should not pass
+ self.run_verify_negat_test(self.ICMP, self.IPV6, 0)
+
+ self.logger.info("ACLP_TEST_FINISH_0007")
+
+ def test_0008_tcp_permit_v4(self):
+ """ permit TCPv4
+ """
+ self.logger.info("ACLP_TEST_START_0008")
+
+ # Add an ACL
+ rules = []
+ rules.append(self.create_rule(self.IPV4, self.PERMIT, self.PORTS_RANGE,
+ self.proto[self.IP][self.TCP]))
+ # deny ip any any in the end
+ rules.append(self.create_rule(self.IPV4, self.DENY, self.PORTS_ALL, 0))
+
+ # Apply rules
+ self.apply_rules(rules, "permit ipv4 tcp")
+
+ # Traffic should still pass
+ self.run_verify_test(self.IP, self.IPV4, self.proto[self.IP][self.TCP])
+
+ self.logger.info("ACLP_TEST_FINISH_0008")
+
+ def test_0009_tcp_permit_v6(self):
+ """ permit TCPv6
+ """
+ self.logger.info("ACLP_TEST_START_0009")
+
+ # Add an ACL
+ rules = []
+ rules.append(self.create_rule(self.IPV6, self.PERMIT, self.PORTS_RANGE,
+ self.proto[self.IP][self.TCP]))
+ # deny ip any any in the end
+ rules.append(self.create_rule(self.IPV6, self.DENY, self.PORTS_ALL, 0))
+
+ # Apply rules
+ self.apply_rules(rules, "permit ip6 tcp")
+
+ # Traffic should still pass
+ self.run_verify_test(self.IP, self.IPV6, self.proto[self.IP][self.TCP])
+
+ self.logger.info("ACLP_TEST_FINISH_0008")
+
+ def test_0010_udp_permit_v4(self):
+ """ permit UDPv4
+ """
+ self.logger.info("ACLP_TEST_START_0010")
+
+ # Add an ACL
+ rules = []
+ rules.append(self.create_rule(self.IPV4, self.PERMIT, self.PORTS_RANGE,
+ self.proto[self.IP][self.UDP]))
+ # deny ip any any in the end
+ rules.append(self.create_rule(self.IPV4, self.DENY, self.PORTS_ALL, 0))
+
+ # Apply rules
+ self.apply_rules(rules, "permit ipv udp")
+
+ # Traffic should still pass
+ self.run_verify_test(self.IP, self.IPV4, self.proto[self.IP][self.UDP])
+
+ self.logger.info("ACLP_TEST_FINISH_0010")
+
+ def test_0011_udp_permit_v6(self):
+ """ permit UDPv6
+ """
+ self.logger.info("ACLP_TEST_START_0011")
+
+ # Add an ACL
+ rules = []
+ rules.append(self.create_rule(self.IPV6, self.PERMIT, self.PORTS_RANGE,
+ self.proto[self.IP][self.UDP]))
+ # deny ip any any in the end
+ rules.append(self.create_rule(self.IPV6, self.DENY, self.PORTS_ALL, 0))
+
+ # Apply rules
+ self.apply_rules(rules, "permit ip6 udp")
+
+ # Traffic should still pass
+ self.run_verify_test(self.IP, self.IPV6, self.proto[self.IP][self.UDP])
+
+ self.logger.info("ACLP_TEST_FINISH_0011")
+
+ def test_0012_tcp_deny(self):
+ """ deny TCPv4/v6
+ """
+ self.logger.info("ACLP_TEST_START_0012")
+
+ # Add an ACL
+ rules = []
+ rules.append(self.create_rule(self.IPV4, self.DENY, self.PORTS_RANGE,
+ self.proto[self.IP][self.TCP]))
+ rules.append(self.create_rule(self.IPV6, self.DENY, self.PORTS_RANGE,
+ self.proto[self.IP][self.TCP]))
+ # permit ip any any in the end
+ rules.append(self.create_rule(self.IPV4, self.PERMIT,
+ self.PORTS_ALL, 0))
+ rules.append(self.create_rule(self.IPV6, self.PERMIT,
+ self.PORTS_ALL, 0))
+
+ # Apply rules
+ self.apply_rules(rules, "deny ip4/ip6 tcp")
+
+ # Traffic should not pass
+ self.run_verify_negat_test(self.IP, self.IPRANDOM,
+ self.proto[self.IP][self.TCP])
+
+ self.logger.info("ACLP_TEST_FINISH_0012")
+
+ def test_0013_udp_deny(self):
+ """ deny UDPv4/v6
+ """
+ self.logger.info("ACLP_TEST_START_0013")
+
+ # Add an ACL
+ rules = []
+ rules.append(self.create_rule(self.IPV4, self.DENY, self.PORTS_RANGE,
+ self.proto[self.IP][self.UDP]))
+ rules.append(self.create_rule(self.IPV6, self.DENY, self.PORTS_RANGE,
+ self.proto[self.IP][self.UDP]))
+ # permit ip any any in the end
+ rules.append(self.create_rule(self.IPV4, self.PERMIT,
+ self.PORTS_ALL, 0))
+ rules.append(self.create_rule(self.IPV6, self.PERMIT,
+ self.PORTS_ALL, 0))
+
+ # Apply rules
+ self.apply_rules(rules, "deny ip4/ip6 udp")
+
+ # Traffic should not pass
+ self.run_verify_negat_test(self.IP, self.IPRANDOM,
+ self.proto[self.IP][self.UDP])
+
+ self.logger.info("ACLP_TEST_FINISH_0013")
+
+ def test_0014_acl_dump(self):
+ """ verify add/dump acls
+ """
+ self.logger.info("ACLP_TEST_START_0014")
+
+ r = [[self.IPV4, self.PERMIT, 1234, self.proto[self.IP][self.TCP]],
+ [self.IPV4, self.PERMIT, 2345, self.proto[self.IP][self.UDP]],
+ [self.IPV4, self.PERMIT, 0, self.proto[self.IP][self.TCP]],
+ [self.IPV4, self.PERMIT, 0, self.proto[self.IP][self.UDP]],
+ [self.IPV4, self.PERMIT, 5, self.proto[self.ICMP][self.ICMPv4]],
+ [self.IPV6, self.PERMIT, 4321, self.proto[self.IP][self.TCP]],
+ [self.IPV6, self.PERMIT, 5432, self.proto[self.IP][self.UDP]],
+ [self.IPV6, self.PERMIT, 0, self.proto[self.IP][self.TCP]],
+ [self.IPV6, self.PERMIT, 0, self.proto[self.IP][self.UDP]],
+ [self.IPV6, self.PERMIT, 6, self.proto[self.ICMP][self.ICMPv6]],
+ [self.IPV4, self.DENY, self.PORTS_ALL, 0],
+ [self.IPV4, self.DENY, 1234, self.proto[self.IP][self.TCP]],
+ [self.IPV4, self.DENY, 2345, self.proto[self.IP][self.UDP]],
+ [self.IPV4, self.DENY, 5, self.proto[self.ICMP][self.ICMPv4]],
+ [self.IPV6, self.DENY, 4321, self.proto[self.IP][self.TCP]],
+ [self.IPV6, self.DENY, 5432, self.proto[self.IP][self.UDP]],
+ [self.IPV6, self.DENY, 6, self.proto[self.ICMP][self.ICMPv6]],
+ [self.IPV6, self.DENY, self.PORTS_ALL, 0]
+ ]
+
+ # Add and verify new ACLs
+ rules = []
+ for i in range(len(r)):
+ rules.append(self.create_rule(r[i][0], r[i][1], r[i][2], r[i][3]))
+
+ reply = self.vapi.acl_add_replace(acl_index=4294967295, r=rules)
+ result = self.vapi.acl_dump(reply.acl_index)
+
+ i = 0
+ for drules in result:
+ for dr in drules.r:
+ self.assertEqual(dr.is_ipv6, r[i][0])
+ self.assertEqual(dr.is_permit, r[i][1])
+ self.assertEqual(dr.proto, r[i][3])
+
+ if r[i][2] > 0:
+ self.assertEqual(dr.srcport_or_icmptype_first, r[i][2])
+ else:
+ if r[i][2] < 0:
+ self.assertEqual(dr.srcport_or_icmptype_first, 0)
+ self.assertEqual(dr.srcport_or_icmptype_last, 65535)
+ else:
+ if dr.proto == self.proto[self.IP][self.TCP]:
+ self.assertGreater(dr.srcport_or_icmptype_first,
+ self.tcp_sport_from-1)
+ self.assertLess(dr.srcport_or_icmptype_first,
+ self.tcp_sport_to+1)
+ self.assertGreater(dr.dstport_or_icmpcode_last,
+ self.tcp_dport_from-1)
+ self.assertLess(dr.dstport_or_icmpcode_last,
+ self.tcp_dport_to+1)
+ elif dr.proto == self.proto[self.IP][self.UDP]:
+ self.assertGreater(dr.srcport_or_icmptype_first,
+ self.udp_sport_from-1)
+ self.assertLess(dr.srcport_or_icmptype_first,
+ self.udp_sport_to+1)
+ self.assertGreater(dr.dstport_or_icmpcode_last,
+ self.udp_dport_from-1)
+ self.assertLess(dr.dstport_or_icmpcode_last,
+ self.udp_dport_to+1)
+ i += 1
+
+ self.logger.info("ACLP_TEST_FINISH_0014")
+
+ def test_0015_tcp_permit_port_v4(self):
+ """ permit single TCPv4
+ """
+ self.logger.info("ACLP_TEST_START_0015")
+
+ port = random.randint(0, 65535)
+ # Add an ACL
+ rules = []
+ rules.append(self.create_rule(self.IPV4, self.PERMIT, port,
+ self.proto[self.IP][self.TCP]))
+ # deny ip any any in the end
+ rules.append(self.create_rule(self.IPV4, self.DENY, self.PORTS_ALL, 0))
+
+ # Apply rules
+ self.apply_rules(rules, "permit ip4 tcp "+str(port))
+
+ # Traffic should still pass
+ self.run_verify_test(self.IP, self.IPV4,
+ self.proto[self.IP][self.TCP], port)
+
+ self.logger.info("ACLP_TEST_FINISH_0015")
+
+ def test_0016_udp_permit_port_v4(self):
+ """ permit single UDPv4
+ """
+ self.logger.info("ACLP_TEST_START_0016")
+
+ port = random.randint(0, 65535)
+ # Add an ACL
+ rules = []
+ rules.append(self.create_rule(self.IPV4, self.PERMIT, port,
+ self.proto[self.IP][self.UDP]))
+ # deny ip any any in the end
+ rules.append(self.create_rule(self.IPV4, self.DENY, self.PORTS_ALL, 0))
+
+ # Apply rules
+ self.apply_rules(rules, "permit ip4 tcp "+str(port))
+
+ # Traffic should still pass
+ self.run_verify_test(self.IP, self.IPV4,
+ self.proto[self.IP][self.UDP], port)
+
+ self.logger.info("ACLP_TEST_FINISH_0016")
+
+ def test_0017_tcp_permit_port_v6(self):
+ """ permit single TCPv6
+ """
+ self.logger.info("ACLP_TEST_START_0017")
+
+ port = random.randint(0, 65535)
+ # Add an ACL
+ rules = []
+ rules.append(self.create_rule(self.IPV6, self.PERMIT, port,
+ self.proto[self.IP][self.TCP]))
+ # deny ip any any in the end
+ rules.append(self.create_rule(self.IPV6, self.DENY, self.PORTS_ALL, 0))
+
+ # Apply rules
+ self.apply_rules(rules, "permit ip4 tcp "+str(port))
+
+ # Traffic should still pass
+ self.run_verify_test(self.IP, self.IPV6,
+ self.proto[self.IP][self.TCP], port)
+
+ self.logger.info("ACLP_TEST_FINISH_0017")
+
+ def test_0018_udp_permit_port_v6(self):
+ """ permit single UPPv6
+ """
+ self.logger.info("ACLP_TEST_START_0018")
+
+ port = random.randint(0, 65535)
+ # Add an ACL
+ rules = []
+ rules.append(self.create_rule(self.IPV6, self.PERMIT, port,
+ self.proto[self.IP][self.UDP]))
+ # deny ip any any in the end
+ rules.append(self.create_rule(self.IPV6, self.DENY,
+ self.PORTS_ALL, 0))
+
+ # Apply rules
+ self.apply_rules(rules, "permit ip4 tcp "+str(port))
+
+ # Traffic should still pass
+ self.run_verify_test(self.IP, self.IPV6,
+ self.proto[self.IP][self.UDP], port)
+
+ self.logger.info("ACLP_TEST_FINISH_0018")
+
+ def test_0019_udp_deny_port(self):
+ """ deny single TCPv4/v6
+ """
+ self.logger.info("ACLP_TEST_START_0019")
+
+ port = random.randint(0, 65535)
+ # Add an ACL
+ rules = []
+ rules.append(self.create_rule(self.IPV4, self.DENY, port,
+ self.proto[self.IP][self.TCP]))
+ rules.append(self.create_rule(self.IPV6, self.DENY, port,
+ self.proto[self.IP][self.TCP]))
+ # Permit ip any any in the end
+ rules.append(self.create_rule(self.IPV4, self.PERMIT,
+ self.PORTS_ALL, 0))
+ rules.append(self.create_rule(self.IPV6, self.PERMIT,
+ self.PORTS_ALL, 0))
+
+ # Apply rules
+ self.apply_rules(rules, "deny ip4/ip6 udp "+str(port))
+
+ # Traffic should not pass
+ self.run_verify_negat_test(self.IP, self.IPRANDOM,
+ self.proto[self.IP][self.TCP], port)
+
+ self.logger.info("ACLP_TEST_FINISH_0019")
+
+ def test_0020_udp_deny_port(self):
+ """ deny single UDPv4/v6
+ """
+ self.logger.info("ACLP_TEST_START_0020")
+
+ port = random.randint(0, 65535)
+ # Add an ACL
+ rules = []
+ rules.append(self.create_rule(self.IPV4, self.DENY, port,
+ self.proto[self.IP][self.UDP]))
+ rules.append(self.create_rule(self.IPV6, self.DENY, port,
+ self.proto[self.IP][self.UDP]))
+ # Permit ip any any in the end
+ rules.append(self.create_rule(self.IPV4, self.PERMIT,
+ self.PORTS_ALL, 0))
+ rules.append(self.create_rule(self.IPV6, self.PERMIT,
+ self.PORTS_ALL, 0))
+
+ # Apply rules
+ self.apply_rules(rules, "deny ip4/ip6 udp "+str(port))
+
+ # Traffic should not pass
+ self.run_verify_negat_test(self.IP, self.IPRANDOM,
+ self.proto[self.IP][self.UDP], port)
+
+ self.logger.info("ACLP_TEST_FINISH_0020")
+
+ def test_0021_udp_deny_port_verify_fragment_deny(self):
+ """ deny single UDPv4/v6, permit ip any, verify non-initial fragment blocked
+ """
+ self.logger.info("ACLP_TEST_START_0021")
+
+ port = random.randint(0, 65535)
+ # Add an ACL
+ rules = []
+ rules.append(self.create_rule(self.IPV4, self.DENY, port,
+ self.proto[self.IP][self.UDP]))
+ rules.append(self.create_rule(self.IPV6, self.DENY, port,
+ self.proto[self.IP][self.UDP]))
+ # deny ip any any in the end
+ rules.append(self.create_rule(self.IPV4, self.PERMIT,
+ self.PORTS_ALL, 0))
+ rules.append(self.create_rule(self.IPV6, self.PERMIT,
+ self.PORTS_ALL, 0))
+
+ # Apply rules
+ self.apply_rules(rules, "deny ip4/ip6 udp "+str(port))
+
+ # Traffic should not pass
+ self.run_verify_negat_test(self.IP, self.IPRANDOM,
+ self.proto[self.IP][self.UDP], port, True)
+
+ self.logger.info("ACLP_TEST_FINISH_0021")
+
+ def test_0022_zero_length_udp_ipv4(self):
+ """ VPP-687 zero length udp ipv4 packet"""
+ self.logger.info("ACLP_TEST_START_0022")
+
+ port = random.randint(0, 65535)
+ # Add an ACL
+ rules = []
+ rules.append(self.create_rule(self.IPV4, self.PERMIT, port,
+ self.proto[self.IP][self.UDP]))
+ # deny ip any any in the end
+ rules.append(
+ self.create_rule(self.IPV4, self.DENY, self.PORTS_ALL, 0))
+
+ # Apply rules
+ self.apply_rules(rules, "permit empty udp ip4 " + str(port))
+
+ # Traffic should still pass
+ # Create incoming packet streams for packet-generator interfaces
+ pkts_cnt = 0
+ pkts = self.create_stream(self.pg0, self.pg_if_packet_sizes,
+ self.IP, self.IPV4,
+ self.proto[self.IP][self.UDP], port,
+ False, False)
+ if len(pkts) > 0:
+ self.pg0.add_stream(pkts)
+ pkts_cnt += len(pkts)
+
+ # Enable packet capture and start packet sendingself.IPV
+ self.pg_enable_capture(self.pg_interfaces)
+ self.pg_start()
+
+ self.pg1.get_capture(pkts_cnt)
+
+ self.logger.info("ACLP_TEST_FINISH_0022")
+
+ def test_0023_zero_length_udp_ipv6(self):
+ """ VPP-687 zero length udp ipv6 packet"""
+ self.logger.info("ACLP_TEST_START_0023")
+
+ port = random.randint(0, 65535)
+ # Add an ACL
+ rules = []
+ rules.append(self.create_rule(self.IPV6, self.PERMIT, port,
+ self.proto[self.IP][self.UDP]))
+ # deny ip any any in the end
+ rules.append(self.create_rule(self.IPV6, self.DENY, self.PORTS_ALL, 0))
+
+ # Apply rules
+ self.apply_rules(rules, "permit empty udp ip6 "+str(port))
+
+ # Traffic should still pass
+ # Create incoming packet streams for packet-generator interfaces
+ pkts_cnt = 0
+ pkts = self.create_stream(self.pg0, self.pg_if_packet_sizes,
+ self.IP, self.IPV6,
+ self.proto[self.IP][self.UDP], port,
+ False, False)
+ if len(pkts) > 0:
+ self.pg0.add_stream(pkts)
+ pkts_cnt += len(pkts)
+
+ # Enable packet capture and start packet sendingself.IPV
+ self.pg_enable_capture(self.pg_interfaces)
+ self.pg_start()
+
+ # Verify outgoing packet streams per packet-generator interface
+ self.pg1.get_capture(pkts_cnt)
+
+ self.logger.info("ACLP_TEST_FINISH_0023")
+
+ def test_0108_tcp_permit_v4(self):
+ """ permit TCPv4 + non-match range
+ """
+ self.logger.info("ACLP_TEST_START_0108")
+
+ # Add an ACL
+ rules = []
+ rules.append(self.create_rule(self.IPV4, self.DENY, self.PORTS_RANGE_2,
+ self.proto[self.IP][self.TCP]))
+ rules.append(self.create_rule(self.IPV4, self.PERMIT, self.PORTS_RANGE,
+ self.proto[self.IP][self.TCP]))
+ # deny ip any any in the end
+ rules.append(self.create_rule(self.IPV4, self.DENY, self.PORTS_ALL, 0))
+
+ # Apply rules
+ self.apply_rules(rules, "permit ipv4 tcp")
+
+ # Traffic should still pass
+ self.run_verify_test(self.IP, self.IPV4, self.proto[self.IP][self.TCP])
+
+ self.logger.info("ACLP_TEST_FINISH_0108")
+
+ def test_0109_tcp_permit_v6(self):
+ """ permit TCPv6 + non-match range
+ """
+ self.logger.info("ACLP_TEST_START_0109")
+
+ # Add an ACL
+ rules = []
+ rules.append(self.create_rule(self.IPV6, self.DENY, self.PORTS_RANGE_2,
+ self.proto[self.IP][self.TCP]))
+ rules.append(self.create_rule(self.IPV6, self.PERMIT, self.PORTS_RANGE,
+ self.proto[self.IP][self.TCP]))
+ # deny ip any any in the end
+ rules.append(self.create_rule(self.IPV6, self.DENY, self.PORTS_ALL, 0))
+
+ # Apply rules
+ self.apply_rules(rules, "permit ip6 tcp")
+
+ # Traffic should still pass
+ self.run_verify_test(self.IP, self.IPV6, self.proto[self.IP][self.TCP])
+
+ self.logger.info("ACLP_TEST_FINISH_0109")
+
+ def test_0110_udp_permit_v4(self):
+ """ permit UDPv4 + non-match range
+ """
+ self.logger.info("ACLP_TEST_START_0110")
+
+ # Add an ACL
+ rules = []
+ rules.append(self.create_rule(self.IPV4, self.DENY, self.PORTS_RANGE_2,
+ self.proto[self.IP][self.UDP]))
+ rules.append(self.create_rule(self.IPV4, self.PERMIT, self.PORTS_RANGE,
+ self.proto[self.IP][self.UDP]))
+ # deny ip any any in the end
+ rules.append(self.create_rule(self.IPV4, self.DENY, self.PORTS_ALL, 0))
+
+ # Apply rules
+ self.apply_rules(rules, "permit ipv4 udp")
+
+ # Traffic should still pass
+ self.run_verify_test(self.IP, self.IPV4, self.proto[self.IP][self.UDP])
+
+ self.logger.info("ACLP_TEST_FINISH_0110")
+
+ def test_0111_udp_permit_v6(self):
+ """ permit UDPv6 + non-match range
+ """
+ self.logger.info("ACLP_TEST_START_0111")
+
+ # Add an ACL
+ rules = []
+ rules.append(self.create_rule(self.IPV6, self.DENY, self.PORTS_RANGE_2,
+ self.proto[self.IP][self.UDP]))
+ rules.append(self.create_rule(self.IPV6, self.PERMIT, self.PORTS_RANGE,
+ self.proto[self.IP][self.UDP]))
+ # deny ip any any in the end
+ rules.append(self.create_rule(self.IPV6, self.DENY, self.PORTS_ALL, 0))
+
+ # Apply rules
+ self.apply_rules(rules, "permit ip6 udp")
+
+ # Traffic should still pass
+ self.run_verify_test(self.IP, self.IPV6, self.proto[self.IP][self.UDP])
+
+ self.logger.info("ACLP_TEST_FINISH_0111")
+
+ def test_0112_tcp_deny(self):
+ """ deny TCPv4/v6 + non-match range
+ """
+ self.logger.info("ACLP_TEST_START_0112")
+
+ # Add an ACL
+ rules = []
+ rules.append(self.create_rule(self.IPV4, self.PERMIT,
+ self.PORTS_RANGE_2,
+ self.proto[self.IP][self.TCP]))
+ rules.append(self.create_rule(self.IPV6, self.PERMIT,
+ self.PORTS_RANGE_2,
+ self.proto[self.IP][self.TCP]))
+ rules.append(self.create_rule(self.IPV4, self.DENY, self.PORTS_RANGE,
+ self.proto[self.IP][self.TCP]))
+ rules.append(self.create_rule(self.IPV6, self.DENY, self.PORTS_RANGE,
+ self.proto[self.IP][self.TCP]))
+ # permit ip any any in the end
+ rules.append(self.create_rule(self.IPV4, self.PERMIT,
+ self.PORTS_ALL, 0))
+ rules.append(self.create_rule(self.IPV6, self.PERMIT,
+ self.PORTS_ALL, 0))
+
+ # Apply rules
+ self.apply_rules(rules, "deny ip4/ip6 tcp")
+
+ # Traffic should not pass
+ self.run_verify_negat_test(self.IP, self.IPRANDOM,
+ self.proto[self.IP][self.TCP])
+
+ self.logger.info("ACLP_TEST_FINISH_0112")
+
+ def test_0113_udp_deny(self):
+ """ deny UDPv4/v6 + non-match range
+ """
+ self.logger.info("ACLP_TEST_START_0113")
+
+ # Add an ACL
+ rules = []
+ rules.append(self.create_rule(self.IPV4, self.PERMIT,
+ self.PORTS_RANGE_2,
+ self.proto[self.IP][self.UDP]))
+ rules.append(self.create_rule(self.IPV6, self.PERMIT,
+ self.PORTS_RANGE_2,
+ self.proto[self.IP][self.UDP]))
+ rules.append(self.create_rule(self.IPV4, self.DENY, self.PORTS_RANGE,
+ self.proto[self.IP][self.UDP]))
+ rules.append(self.create_rule(self.IPV6, self.DENY, self.PORTS_RANGE,
+ self.proto[self.IP][self.UDP]))
+ # permit ip any any in the end
+ rules.append(self.create_rule(self.IPV4, self.PERMIT,
+ self.PORTS_ALL, 0))
+ rules.append(self.create_rule(self.IPV6, self.PERMIT,
+ self.PORTS_ALL, 0))
+
+ # Apply rules
+ self.apply_rules(rules, "deny ip4/ip6 udp")
+
+ # Traffic should not pass
+ self.run_verify_negat_test(self.IP, self.IPRANDOM,
+ self.proto[self.IP][self.UDP])
+
+ self.logger.info("ACLP_TEST_FINISH_0113")
+
+
+if __name__ == '__main__':
+ unittest.main(testRunner=VppTestRunner)
diff --git a/test/test_acl_plugin_conns.py b/test/test_acl_plugin_conns.py
new file mode 100644
index 00000000..43e8b69f
--- /dev/null
+++ b/test/test_acl_plugin_conns.py
@@ -0,0 +1,403 @@
+#!/usr/bin/env python
+""" ACL plugin extended stateful tests """
+
+import unittest
+from framework import VppTestCase, VppTestRunner, running_extended_tests
+from scapy.layers.l2 import Ether
+from scapy.packet import Raw
+from scapy.layers.inet import IP, UDP, TCP
+from scapy.packet import Packet
+from socket import inet_pton, AF_INET, AF_INET6
+from scapy.layers.inet6 import IPv6, ICMPv6Unknown, ICMPv6EchoRequest
+from scapy.layers.inet6 import ICMPv6EchoReply, IPv6ExtHdrRouting
+from scapy.layers.inet6 import IPv6ExtHdrFragment
+from pprint import pprint
+from random import randint
+from util import L4_Conn
+
+
+def to_acl_rule(self, is_permit, wildcard_sport=False):
+ p = self
+ rule_family = AF_INET6 if p.haslayer(IPv6) else AF_INET
+ rule_prefix_len = 128 if p.haslayer(IPv6) else 32
+ rule_l3_layer = IPv6 if p.haslayer(IPv6) else IP
+ rule_l4_sport = p.sport
+ rule_l4_dport = p.dport
+ if p.haslayer(IPv6):
+ rule_l4_proto = p[IPv6].nh
+ else:
+ rule_l4_proto = p[IP].proto
+
+ if wildcard_sport:
+ rule_l4_sport_first = 0
+ rule_l4_sport_last = 65535
+ else:
+ rule_l4_sport_first = rule_l4_sport
+ rule_l4_sport_last = rule_l4_sport
+
+ new_rule = {
+ 'is_permit': is_permit,
+ 'is_ipv6': p.haslayer(IPv6),
+ 'src_ip_addr': inet_pton(rule_family,
+ p[rule_l3_layer].src),
+ 'src_ip_prefix_len': rule_prefix_len,
+ 'dst_ip_addr': inet_pton(rule_family,
+ p[rule_l3_layer].dst),
+ 'dst_ip_prefix_len': rule_prefix_len,
+ 'srcport_or_icmptype_first': rule_l4_sport_first,
+ 'srcport_or_icmptype_last': rule_l4_sport_last,
+ 'dstport_or_icmpcode_first': rule_l4_dport,
+ 'dstport_or_icmpcode_last': rule_l4_dport,
+ 'proto': rule_l4_proto,
+ }
+ return new_rule
+
+Packet.to_acl_rule = to_acl_rule
+
+
+class IterateWithSleep():
+ def __init__(self, testcase, n_iters, description, sleep_sec):
+ self.curr = 0
+ self.testcase = testcase
+ self.n_iters = n_iters
+ self.sleep_sec = sleep_sec
+ self.description = description
+
+ def __iter__(self):
+ for x in range(0, self.n_iters):
+ yield x
+ self.testcase.sleep(self.sleep_sec)
+
+
+class Conn(L4_Conn):
+ def apply_acls(self, reflect_side, acl_side):
+ pkts = []
+ pkts.append(self.pkt(0))
+ pkts.append(self.pkt(1))
+ pkt = pkts[reflect_side]
+
+ r = []
+ r.append(pkt.to_acl_rule(2, wildcard_sport=True))
+ r.append(self.wildcard_rule(0))
+ res = self.testcase.vapi.acl_add_replace(0xffffffff, r)
+ self.testcase.assert_equal(res.retval, 0, "error adding ACL")
+ reflect_acl_index = res.acl_index
+
+ r = []
+ r.append(self.wildcard_rule(0))
+ res = self.testcase.vapi.acl_add_replace(0xffffffff, r)
+ self.testcase.assert_equal(res.retval, 0, "error adding deny ACL")
+ deny_acl_index = res.acl_index
+
+ if reflect_side == acl_side:
+ self.testcase.vapi.acl_interface_set_acl_list(
+ self.ifs[acl_side].sw_if_index, 1,
+ [reflect_acl_index,
+ deny_acl_index])
+ self.testcase.vapi.acl_interface_set_acl_list(
+ self.ifs[1-acl_side].sw_if_index, 0, [])
+ else:
+ self.testcase.vapi.acl_interface_set_acl_list(
+ self.ifs[acl_side].sw_if_index, 1,
+ [deny_acl_index,
+ reflect_acl_index])
+ self.testcase.vapi.acl_interface_set_acl_list(
+ self.ifs[1-acl_side].sw_if_index, 0, [])
+
+ def wildcard_rule(self, is_permit):
+ any_addr = ["0.0.0.0", "::"]
+ rule_family = self.address_family
+ is_ip6 = 1 if rule_family == AF_INET6 else 0
+ new_rule = {
+ 'is_permit': is_permit,
+ 'is_ipv6': is_ip6,
+ 'src_ip_addr': inet_pton(rule_family, any_addr[is_ip6]),
+ 'src_ip_prefix_len': 0,
+ 'dst_ip_addr': inet_pton(rule_family, any_addr[is_ip6]),
+ 'dst_ip_prefix_len': 0,
+ 'srcport_or_icmptype_first': 0,
+ 'srcport_or_icmptype_last': 65535,
+ 'dstport_or_icmpcode_first': 0,
+ 'dstport_or_icmpcode_last': 65535,
+ 'proto': 0,
+ }
+ return new_rule
+
+
+@unittest.skipUnless(running_extended_tests(), "part of extended tests")
+class ACLPluginConnTestCase(VppTestCase):
+ """ ACL plugin connection-oriented extended testcases """
+
+ @classmethod
+ def setUpClass(self):
+ super(ACLPluginConnTestCase, self).setUpClass()
+ # create pg0 and pg1
+ self.create_pg_interfaces(range(2))
+ for i in self.pg_interfaces:
+ i.admin_up()
+ i.config_ip4()
+ i.config_ip6()
+ i.resolve_arp()
+ i.resolve_ndp()
+
+ def tearDown(self):
+ """Run standard test teardown and log various show commands
+ """
+ super(ACLPluginConnTestCase, self).tearDown()
+ if not self.vpp_dead:
+ self.logger.info(self.vapi.cli("show ip arp"))
+ self.logger.info(self.vapi.cli("show ip6 neighbors"))
+ self.logger.info(self.vapi.cli("show acl-plugin sessions"))
+ self.logger.info(self.vapi.cli("show acl-plugin acl"))
+ self.logger.info(self.vapi.cli("show acl-plugin interface"))
+ self.logger.info(self.vapi.cli("show acl-plugin tables"))
+
+ def run_basic_conn_test(self, af, acl_side):
+ """ Basic conn timeout test """
+ conn1 = Conn(self, self.pg0, self.pg1, af, UDP, 42001, 4242)
+ conn1.apply_acls(0, acl_side)
+ conn1.send_through(0)
+ # the return packets should pass
+ conn1.send_through(1)
+ # send some packets on conn1, ensure it doesn't go away
+ for i in IterateWithSleep(self, 20, "Keep conn active", 0.3):
+ conn1.send_through(1)
+ # allow the conn to time out
+ for i in IterateWithSleep(self, 30, "Wait for timeout", 0.1):
+ pass
+ # now try to send a packet on the reflected side
+ try:
+ p2 = conn1.send_through(1).command()
+ except:
+ # If we asserted while waiting, it's good.
+ # the conn should have timed out.
+ p2 = None
+ self.assert_equal(p2, None, "packet on long-idle conn")
+
+ def run_active_conn_test(self, af, acl_side):
+ """ Idle connection behind active connection test """
+ base = 10000 + 1000*acl_side
+ conn1 = Conn(self, self.pg0, self.pg1, af, UDP, base + 1, 2323)
+ conn2 = Conn(self, self.pg0, self.pg1, af, UDP, base + 2, 2323)
+ conn3 = Conn(self, self.pg0, self.pg1, af, UDP, base + 3, 2323)
+ conn1.apply_acls(0, acl_side)
+ conn1.send(0)
+ conn1.recv(1)
+ # create and check that the conn2/3 work
+ self.sleep(0.1)
+ conn2.send_pingpong(0)
+ self.sleep(0.1)
+ conn3.send_pingpong(0)
+ # send some packets on conn1, keep conn2/3 idle
+ for i in IterateWithSleep(self, 20, "Keep conn active", 0.2):
+ conn1.send_through(1)
+ try:
+ p2 = conn2.send_through(1).command()
+ except:
+ # If we asserted while waiting, it's good.
+ # the conn should have timed out.
+ p2 = None
+ # We should have not received the packet on a long-idle
+ # connection, because it should have timed out
+ # If it didn't - it is a problem
+ self.assert_equal(p2, None, "packet on long-idle conn")
+
+ def run_clear_conn_test(self, af, acl_side):
+ """ Clear the connections via CLI """
+ conn1 = Conn(self, self.pg0, self.pg1, af, UDP, 42001, 4242)
+ conn1.apply_acls(0, acl_side)
+ conn1.send_through(0)
+ # the return packets should pass
+ conn1.send_through(1)
+ # send some packets on conn1, ensure it doesn't go away
+ for i in IterateWithSleep(self, 20, "Keep conn active", 0.3):
+ conn1.send_through(1)
+ # clear all connections
+ self.vapi.ppcli("clear acl-plugin sessions")
+ # now try to send a packet on the reflected side
+ try:
+ p2 = conn1.send_through(1).command()
+ except:
+ # If we asserted while waiting, it's good.
+ # the conn should have timed out.
+ p2 = None
+ self.assert_equal(p2, None, "packet on supposedly deleted conn")
+
+ def run_tcp_transient_setup_conn_test(self, af, acl_side):
+ conn1 = Conn(self, self.pg0, self.pg1, af, TCP, 53001, 5151)
+ conn1.apply_acls(0, acl_side)
+ conn1.send_through(0, 'S')
+ # the return packets should pass
+ conn1.send_through(1, 'SA')
+ # allow the conn to time out
+ for i in IterateWithSleep(self, 30, "Wait for timeout", 0.1):
+ pass
+ # ensure conn times out
+ try:
+ p2 = conn1.send_through(1).command()
+ except:
+ # If we asserted while waiting, it's good.
+ # the conn should have timed out.
+ p2 = None
+ self.assert_equal(p2, None, "packet on supposedly deleted conn")
+
+ def run_tcp_established_conn_test(self, af, acl_side):
+ conn1 = Conn(self, self.pg0, self.pg1, af, TCP, 53002, 5052)
+ conn1.apply_acls(0, acl_side)
+ conn1.send_through(0, 'S')
+ # the return packets should pass
+ conn1.send_through(1, 'SA')
+ # complete the threeway handshake
+ # (NB: sequence numbers not tracked, so not set!)
+ conn1.send_through(0, 'A')
+ # allow the conn to time out if it's in embryonic timer
+ for i in IterateWithSleep(self, 30, "Wait for transient timeout", 0.1):
+ pass
+ # Try to send the packet from the "forbidden" side - it must pass
+ conn1.send_through(1, 'A')
+ # ensure conn times out for real
+ for i in IterateWithSleep(self, 130, "Wait for timeout", 0.1):
+ pass
+ try:
+ p2 = conn1.send_through(1).command()
+ except:
+ # If we asserted while waiting, it's good.
+ # the conn should have timed out.
+ p2 = None
+ self.assert_equal(p2, None, "packet on supposedly deleted conn")
+
+ def run_tcp_transient_teardown_conn_test(self, af, acl_side):
+ conn1 = Conn(self, self.pg0, self.pg1, af, TCP, 53002, 5052)
+ conn1.apply_acls(0, acl_side)
+ conn1.send_through(0, 'S')
+ # the return packets should pass
+ conn1.send_through(1, 'SA')
+ # complete the threeway handshake
+ # (NB: sequence numbers not tracked, so not set!)
+ conn1.send_through(0, 'A')
+ # allow the conn to time out if it's in embryonic timer
+ for i in IterateWithSleep(self, 30, "Wait for transient timeout", 0.1):
+ pass
+ # Try to send the packet from the "forbidden" side - it must pass
+ conn1.send_through(1, 'A')
+ # Send the FIN to bounce the session out of established
+ conn1.send_through(1, 'FA')
+ # If conn landed on transient timer it will time out here
+ for i in IterateWithSleep(self, 30, "Wait for transient timeout", 0.1):
+ pass
+ # Now it should have timed out already
+ try:
+ p2 = conn1.send_through(1).command()
+ except:
+ # If we asserted while waiting, it's good.
+ # the conn should have timed out.
+ p2 = None
+ self.assert_equal(p2, None, "packet on supposedly deleted conn")
+
+ def test_0000_conn_prepare_test(self):
+ """ Prepare the settings """
+ self.vapi.ppcli("set acl-plugin session timeout udp idle 1")
+
+ def test_0001_basic_conn_test(self):
+ """ IPv4: Basic conn timeout test reflect on ingress """
+ self.run_basic_conn_test(AF_INET, 0)
+
+ def test_0002_basic_conn_test(self):
+ """ IPv4: Basic conn timeout test reflect on egress """
+ self.run_basic_conn_test(AF_INET, 1)
+
+ def test_0005_clear_conn_test(self):
+ """ IPv4: reflect egress, clear conn """
+ self.run_clear_conn_test(AF_INET, 1)
+
+ def test_0006_clear_conn_test(self):
+ """ IPv4: reflect ingress, clear conn """
+ self.run_clear_conn_test(AF_INET, 0)
+
+ def test_0011_active_conn_test(self):
+ """ IPv4: Idle conn behind active conn, reflect on ingress """
+ self.run_active_conn_test(AF_INET, 0)
+
+ def test_0012_active_conn_test(self):
+ """ IPv4: Idle conn behind active conn, reflect on egress """
+ self.run_active_conn_test(AF_INET, 1)
+
+ def test_1001_basic_conn_test(self):
+ """ IPv6: Basic conn timeout test reflect on ingress """
+ self.run_basic_conn_test(AF_INET6, 0)
+
+ def test_1002_basic_conn_test(self):
+ """ IPv6: Basic conn timeout test reflect on egress """
+ self.run_basic_conn_test(AF_INET6, 1)
+
+ def test_1005_clear_conn_test(self):
+ """ IPv6: reflect egress, clear conn """
+ self.run_clear_conn_test(AF_INET6, 1)
+
+ def test_1006_clear_conn_test(self):
+ """ IPv6: reflect ingress, clear conn """
+ self.run_clear_conn_test(AF_INET6, 0)
+
+ def test_1011_active_conn_test(self):
+ """ IPv6: Idle conn behind active conn, reflect on ingress """
+ self.run_active_conn_test(AF_INET6, 0)
+
+ def test_1012_active_conn_test(self):
+ """ IPv6: Idle conn behind active conn, reflect on egress """
+ self.run_active_conn_test(AF_INET6, 1)
+
+ def test_2000_prepare_for_tcp_test(self):
+ """ Prepare for TCP session tests """
+ # ensure the session hangs on if it gets treated as UDP
+ self.vapi.ppcli("set acl-plugin session timeout udp idle 200")
+ # let the TCP connection time out at 5 seconds
+ self.vapi.ppcli("set acl-plugin session timeout tcp idle 10")
+ self.vapi.ppcli("set acl-plugin session timeout tcp transient 1")
+
+ def test_2001_tcp_transient_conn_test(self):
+ """ IPv4: transient TCP session (incomplete 3WHS), ref. on ingress """
+ self.run_tcp_transient_setup_conn_test(AF_INET, 0)
+
+ def test_2002_tcp_transient_conn_test(self):
+ """ IPv4: transient TCP session (incomplete 3WHS), ref. on egress """
+ self.run_tcp_transient_setup_conn_test(AF_INET, 1)
+
+ def test_2003_tcp_transient_conn_test(self):
+ """ IPv4: established TCP session (complete 3WHS), ref. on ingress """
+ self.run_tcp_established_conn_test(AF_INET, 0)
+
+ def test_2004_tcp_transient_conn_test(self):
+ """ IPv4: established TCP session (complete 3WHS), ref. on egress """
+ self.run_tcp_established_conn_test(AF_INET, 1)
+
+ def test_2005_tcp_transient_teardown_conn_test(self):
+ """ IPv4: transient TCP session (3WHS,ACK,FINACK), ref. on ingress """
+ self.run_tcp_transient_teardown_conn_test(AF_INET, 0)
+
+ def test_2006_tcp_transient_teardown_conn_test(self):
+ """ IPv4: transient TCP session (3WHS,ACK,FINACK), ref. on egress """
+ self.run_tcp_transient_teardown_conn_test(AF_INET, 1)
+
+ def test_3001_tcp_transient_conn_test(self):
+ """ IPv6: transient TCP session (incomplete 3WHS), ref. on ingress """
+ self.run_tcp_transient_setup_conn_test(AF_INET6, 0)
+
+ def test_3002_tcp_transient_conn_test(self):
+ """ IPv6: transient TCP session (incomplete 3WHS), ref. on egress """
+ self.run_tcp_transient_setup_conn_test(AF_INET6, 1)
+
+ def test_3003_tcp_transient_conn_test(self):
+ """ IPv6: established TCP session (complete 3WHS), ref. on ingress """
+ self.run_tcp_established_conn_test(AF_INET6, 0)
+
+ def test_3004_tcp_transient_conn_test(self):
+ """ IPv6: established TCP session (complete 3WHS), ref. on egress """
+ self.run_tcp_established_conn_test(AF_INET6, 1)
+
+ def test_3005_tcp_transient_teardown_conn_test(self):
+ """ IPv6: transient TCP session (3WHS,ACK,FINACK), ref. on ingress """
+ self.run_tcp_transient_teardown_conn_test(AF_INET6, 0)
+
+ def test_3006_tcp_transient_teardown_conn_test(self):
+ """ IPv6: transient TCP session (3WHS,ACK,FINACK), ref. on egress """
+ self.run_tcp_transient_teardown_conn_test(AF_INET6, 1)
diff --git a/test/test_acl_plugin_l2l3.py b/test/test_acl_plugin_l2l3.py
new file mode 100644
index 00000000..04f91cfc
--- /dev/null
+++ b/test/test_acl_plugin_l2l3.py
@@ -0,0 +1,686 @@
+#!/usr/bin/env python
+"""ACL IRB Test Case HLD:
+
+**config**
+ - L2 MAC learning enabled in l2bd
+ - 2 routed interfaces untagged, bvi (Bridge Virtual Interface)
+ - 2 bridged interfaces in l2bd with bvi
+
+**test**
+ - sending ip4 eth pkts between routed interfaces
+ - 2 routed interfaces
+ - 2 bridged interfaces
+
+ - 64B, 512B, 1518B, 9200B (ether_size)
+
+ - burst of pkts per interface
+ - 257pkts per burst
+ - routed pkts hitting different FIB entries
+ - bridged pkts hitting different MAC entries
+
+**verify**
+ - all packets received correctly
+
+"""
+
+import unittest
+from socket import inet_pton, AF_INET, AF_INET6
+from random import choice
+from pprint import pprint
+
+from scapy.packet import Raw
+from scapy.layers.l2 import Ether
+from scapy.layers.inet import IP, UDP, ICMP, TCP
+from scapy.layers.inet6 import IPv6, ICMPv6Unknown, ICMPv6EchoRequest
+from scapy.layers.inet6 import ICMPv6EchoReply, IPv6ExtHdrRouting
+from scapy.layers.inet6 import IPv6ExtHdrFragment
+
+from framework import VppTestCase, VppTestRunner
+import time
+
+
+class TestIpIrb(VppTestCase):
+ """IRB Test Case"""
+
+ @classmethod
+ def setUpClass(cls):
+ """
+ #. Create BD with MAC learning enabled and put interfaces to this BD.
+ #. Configure IPv4 addresses on loopback interface and routed interface.
+ #. Configure MAC address binding to IPv4 neighbors on loop0.
+ #. Configure MAC address on pg2.
+ #. Loopback BVI interface has remote hosts, one half of hosts are
+ behind pg0 second behind pg1.
+ """
+ super(TestIpIrb, cls).setUpClass()
+
+ cls.pg_if_packet_sizes = [64, 512, 1518, 9018] # packet sizes
+ cls.bd_id = 10
+ cls.remote_hosts_count = 250
+
+ # create 3 pg interfaces, 1 loopback interface
+ cls.create_pg_interfaces(range(3))
+ cls.create_loopback_interfaces(range(1))
+
+ cls.interfaces = list(cls.pg_interfaces)
+ cls.interfaces.extend(cls.lo_interfaces)
+
+ for i in cls.interfaces:
+ i.admin_up()
+
+ # Create BD with MAC learning enabled and put interfaces to this BD
+ cls.vapi.sw_interface_set_l2_bridge(
+ cls.loop0.sw_if_index, bd_id=cls.bd_id, bvi=1)
+ cls.vapi.sw_interface_set_l2_bridge(
+ cls.pg0.sw_if_index, bd_id=cls.bd_id)
+ cls.vapi.sw_interface_set_l2_bridge(
+ cls.pg1.sw_if_index, bd_id=cls.bd_id)
+
+ # Configure IPv4 addresses on loopback interface and routed interface
+ cls.loop0.config_ip4()
+ cls.loop0.config_ip6()
+ cls.pg2.config_ip4()
+ cls.pg2.config_ip6()
+
+ # Configure MAC address binding to IPv4 neighbors on loop0
+ cls.loop0.generate_remote_hosts(cls.remote_hosts_count)
+ cls.loop0.configure_ipv4_neighbors()
+ cls.loop0.configure_ipv6_neighbors()
+ # configure MAC address on pg2
+ cls.pg2.resolve_arp()
+ cls.pg2.resolve_ndp()
+
+ cls.WITHOUT_EH = False
+ cls.WITH_EH = True
+
+ # Loopback BVI interface has remote hosts, one half of hosts are behind
+ # pg0 second behind pg1
+ half = cls.remote_hosts_count // 2
+ cls.pg0.remote_hosts = cls.loop0.remote_hosts[:half]
+ cls.pg1.remote_hosts = cls.loop0.remote_hosts[half:]
+
+ def tearDown(self):
+ """Run standard test teardown and log ``show l2patch``,
+ ``show l2fib verbose``,``show bridge-domain <bd_id> detail``,
+ ``show ip arp``.
+ """
+ super(TestIpIrb, self).tearDown()
+ if not self.vpp_dead:
+ self.logger.info(self.vapi.cli("show l2patch"))
+ self.logger.info(self.vapi.cli("show classify tables"))
+ self.logger.info(self.vapi.cli("show vlib graph"))
+ self.logger.info(self.vapi.cli("show l2fib verbose"))
+ self.logger.info(self.vapi.cli("show bridge-domain %s detail" %
+ self.bd_id))
+ self.logger.info(self.vapi.cli("show ip arp"))
+ self.logger.info(self.vapi.cli("show ip6 neighbors"))
+ self.logger.info(self.vapi.cli("show acl-plugin sessions"))
+ self.logger.info(self.vapi.cli("show acl-plugin acl"))
+ self.logger.info(self.vapi.cli("show acl-plugin interface"))
+ self.logger.info(self.vapi.cli("show acl-plugin tables"))
+
+ def create_stream(self, src_ip_if, dst_ip_if, reverse, packet_sizes,
+ is_ip6, expect_blocked, expect_established,
+ add_extension_header):
+ pkts = []
+ rules = []
+ permit_rules = []
+ permit_and_reflect_rules = []
+ total_packet_count = 8
+ for i in range(0, total_packet_count):
+ modulo = (i//2) % 2
+ can_reflect_this_packet = (modulo == 0)
+ is_permit = i % 2
+ remote_dst_index = i % len(dst_ip_if.remote_hosts)
+ remote_dst_host = dst_ip_if.remote_hosts[remote_dst_index]
+ if is_permit == 1:
+ info = self.create_packet_info(src_ip_if, dst_ip_if)
+ payload = self.info_to_payload(info)
+ else:
+ to_be_blocked = False
+ if (expect_blocked and not expect_established):
+ to_be_blocked = True
+ if (not can_reflect_this_packet):
+ to_be_blocked = True
+ if to_be_blocked:
+ payload = "to be blocked"
+ else:
+ info = self.create_packet_info(src_ip_if, dst_ip_if)
+ payload = self.info_to_payload(info)
+ if reverse:
+ dst_mac = 'de:ad:00:00:00:00'
+ src_mac = remote_dst_host._mac
+ dst_ip6 = src_ip_if.remote_ip6
+ src_ip6 = remote_dst_host.ip6
+ dst_ip4 = src_ip_if.remote_ip4
+ src_ip4 = remote_dst_host.ip4
+ dst_l4 = 1234 + i
+ src_l4 = 4321 + i
+ else:
+ dst_mac = src_ip_if.local_mac
+ src_mac = src_ip_if.remote_mac
+ src_ip6 = src_ip_if.remote_ip6
+ dst_ip6 = remote_dst_host.ip6
+ src_ip4 = src_ip_if.remote_ip4
+ dst_ip4 = remote_dst_host.ip4
+ src_l4 = 1234 + i
+ dst_l4 = 4321 + i
+
+ # default ULP should be something we do not use in tests
+ ulp_l4 = TCP(sport=src_l4, dport=dst_l4)
+ # potentially a chain of protocols leading to ULP
+ ulp = ulp_l4
+
+ if can_reflect_this_packet:
+ if is_ip6:
+ ulp_l4 = UDP(sport=src_l4, dport=dst_l4)
+ if add_extension_header:
+ # prepend some extension headers
+ ulp = (IPv6ExtHdrRouting() / IPv6ExtHdrRouting() /
+ IPv6ExtHdrFragment(offset=0, m=1) / ulp_l4)
+ # uncomment below to test invalid ones
+ # ulp = IPv6ExtHdrRouting(len = 200) / ulp_l4
+ else:
+ ulp = ulp_l4
+ p = (Ether(dst=dst_mac, src=src_mac) /
+ IPv6(src=src_ip6, dst=dst_ip6) /
+ ulp /
+ Raw(payload))
+ else:
+ ulp_l4 = UDP(sport=src_l4, dport=dst_l4)
+ # IPv4 does not allow extension headers,
+ # but we rather make it a first fragment
+ flags = 1 if add_extension_header else 0
+ ulp = ulp_l4
+ p = (Ether(dst=dst_mac, src=src_mac) /
+ IP(src=src_ip4, dst=dst_ip4, frag=0, flags=flags) /
+ ulp /
+ Raw(payload))
+ elif modulo == 1:
+ if is_ip6:
+ ulp_l4 = ICMPv6Unknown(type=128 + (i % 2), code=i % 2)
+ ulp = ulp_l4
+ p = (Ether(dst=dst_mac, src=src_mac) /
+ IPv6(src=src_ip6, dst=dst_ip6) /
+ ulp /
+ Raw(payload))
+ else:
+ ulp_l4 = ICMP(type=8 + (i % 2), code=i % 2)
+ ulp = ulp_l4
+ p = (Ether(dst=dst_mac, src=src_mac) /
+ IP(src=src_ip4, dst=dst_ip4) /
+ ulp /
+ Raw(payload))
+
+ if i % 2 == 1:
+ info.data = p.copy()
+ size = packet_sizes[(i // 2) % len(packet_sizes)]
+ self.extend_packet(p, size)
+ pkts.append(p)
+
+ rule_family = AF_INET6 if p.haslayer(IPv6) else AF_INET
+ rule_prefix_len = 128 if p.haslayer(IPv6) else 32
+ rule_l3_layer = IPv6 if p.haslayer(IPv6) else IP
+
+ if p.haslayer(UDP):
+ rule_l4_sport = p[UDP].sport
+ rule_l4_dport = p[UDP].dport
+ else:
+ if p.haslayer(ICMP):
+ rule_l4_sport = p[ICMP].type
+ rule_l4_dport = p[ICMP].code
+ else:
+ rule_l4_sport = p[ICMPv6Unknown].type
+ rule_l4_dport = p[ICMPv6Unknown].code
+ if p.haslayer(IPv6):
+ rule_l4_proto = ulp_l4.overload_fields[IPv6]['nh']
+ else:
+ rule_l4_proto = p[IP].proto
+
+ new_rule = {
+ 'is_permit': is_permit,
+ 'is_ipv6': p.haslayer(IPv6),
+ 'src_ip_addr': inet_pton(rule_family,
+ p[rule_l3_layer].src),
+ 'src_ip_prefix_len': rule_prefix_len,
+ 'dst_ip_addr': inet_pton(rule_family,
+ p[rule_l3_layer].dst),
+ 'dst_ip_prefix_len': rule_prefix_len,
+ 'srcport_or_icmptype_first': rule_l4_sport,
+ 'srcport_or_icmptype_last': rule_l4_sport,
+ 'dstport_or_icmpcode_first': rule_l4_dport,
+ 'dstport_or_icmpcode_last': rule_l4_dport,
+ 'proto': rule_l4_proto,
+ }
+ rules.append(new_rule)
+ new_rule_permit = new_rule.copy()
+ new_rule_permit['is_permit'] = 1
+ permit_rules.append(new_rule_permit)
+
+ new_rule_permit_and_reflect = new_rule.copy()
+ if can_reflect_this_packet:
+ new_rule_permit_and_reflect['is_permit'] = 2
+ else:
+ new_rule_permit_and_reflect['is_permit'] = is_permit
+ permit_and_reflect_rules.append(new_rule_permit_and_reflect)
+
+ return {'stream': pkts,
+ 'rules': rules,
+ 'permit_rules': permit_rules,
+ 'permit_and_reflect_rules': permit_and_reflect_rules}
+
+ def verify_capture(self, dst_ip_if, src_ip_if, capture, reverse):
+ last_info = dict()
+ for i in self.interfaces:
+ last_info[i.sw_if_index] = None
+
+ dst_ip_sw_if_index = dst_ip_if.sw_if_index
+ return
+
+ for packet in capture:
+ l3 = IP if packet.haslayer(IP) else IPv6
+ ip = packet[l3]
+ if packet.haslayer(UDP):
+ l4 = UDP
+ else:
+ if packet.haslayer(ICMP):
+ l4 = ICMP
+ else:
+ l4 = ICMPv6Unknown
+
+ # Scapy IPv6 stuff is too smart for its own good.
+ # So we do this and coerce the ICMP into unknown type
+ if packet.haslayer(UDP):
+ data = str(packet[UDP][Raw])
+ else:
+ if l3 == IP:
+ data = str(ICMP(str(packet[l3].payload))[Raw])
+ else:
+ data = str(ICMPv6Unknown(str(packet[l3].payload)).msgbody)
+ udp_or_icmp = packet[l3].payload
+ payload_info = self.payload_to_info(data)
+ packet_index = payload_info.index
+
+ self.assertEqual(payload_info.dst, dst_ip_sw_if_index)
+
+ next_info = self.get_next_packet_info_for_interface2(
+ payload_info.src, dst_ip_sw_if_index,
+ last_info[payload_info.src])
+ last_info[payload_info.src] = next_info
+ self.assertTrue(next_info is not None)
+ self.assertEqual(packet_index, next_info.index)
+ saved_packet = next_info.data
+ self.assertTrue(next_info is not None)
+
+ # MAC: src, dst
+ if not reverse:
+ self.assertEqual(packet.src, dst_ip_if.local_mac)
+ host = dst_ip_if.host_by_mac(packet.dst)
+
+ # IP: src, dst
+ # self.assertEqual(ip.src, src_ip_if.remote_ip4)
+ if saved_packet is not None:
+ self.assertEqual(ip.src, saved_packet[l3].src)
+ self.assertEqual(ip.dst, saved_packet[l3].dst)
+ if l4 == UDP:
+ self.assertEqual(udp_or_icmp.sport, saved_packet[l4].sport)
+ self.assertEqual(udp_or_icmp.dport, saved_packet[l4].dport)
+ else:
+ print("Saved packet is none")
+ # self.assertEqual(ip.dst, host.ip4)
+
+ # UDP:
+
+ def create_acls_for_a_stream(self, stream_dict,
+ test_l2_action, is_reflect):
+ r = stream_dict['rules']
+ r_permit = stream_dict['permit_rules']
+ r_permit_reflect = stream_dict['permit_and_reflect_rules']
+ r_action = r_permit_reflect if is_reflect else r
+ reply = self.vapi.acl_add_replace(acl_index=4294967295, r=r_action,
+ tag="act. acl")
+ action_acl_index = reply.acl_index
+ reply = self.vapi.acl_add_replace(acl_index=4294967295, r=r_permit,
+ tag="perm. acl")
+ permit_acl_index = reply.acl_index
+ return {'L2': action_acl_index if test_l2_action else permit_acl_index,
+ 'L3': permit_acl_index if test_l2_action else action_acl_index,
+ 'permit': permit_acl_index, 'action': action_acl_index}
+
+ def apply_acl_ip46_x_to_y(self, bridged_to_routed, test_l2_deny,
+ is_ip6, is_reflect, add_eh):
+ """ Apply the ACLs
+ """
+ self.reset_packet_infos()
+ stream_dict = self.create_stream(
+ self.pg2, self.loop0,
+ bridged_to_routed,
+ self.pg_if_packet_sizes, is_ip6,
+ not is_reflect, False, add_eh)
+ stream = stream_dict['stream']
+ acl_idx = self.create_acls_for_a_stream(stream_dict, test_l2_deny,
+ is_reflect)
+ n_input_l3 = 0 if bridged_to_routed else 1
+ n_input_l2 = 1 if bridged_to_routed else 0
+ self.vapi.acl_interface_set_acl_list(sw_if_index=self.pg2.sw_if_index,
+ n_input=n_input_l3,
+ acls=[acl_idx['L3']])
+ self.vapi.acl_interface_set_acl_list(sw_if_index=self.pg0.sw_if_index,
+ n_input=n_input_l2,
+ acls=[acl_idx['L2']])
+ self.vapi.acl_interface_set_acl_list(sw_if_index=self.pg1.sw_if_index,
+ n_input=n_input_l2,
+ acls=[acl_idx['L2']])
+
+ def apply_acl_ip46_both_directions_reflect(self,
+ primary_is_bridged_to_routed,
+ reflect_on_l2, is_ip6, add_eh):
+ primary_is_routed_to_bridged = not primary_is_bridged_to_routed
+ self.reset_packet_infos()
+ stream_dict_fwd = self.create_stream(self.pg2, self.loop0,
+ primary_is_bridged_to_routed,
+ self.pg_if_packet_sizes, is_ip6,
+ False, False, add_eh)
+ acl_idx_fwd = self.create_acls_for_a_stream(stream_dict_fwd,
+ reflect_on_l2, True)
+
+ stream_dict_rev = self.create_stream(self.pg2, self.loop0,
+ not primary_is_bridged_to_routed,
+ self.pg_if_packet_sizes, is_ip6,
+ True, True, add_eh)
+ # We want the primary action to be "deny" rather than reflect
+ acl_idx_rev = self.create_acls_for_a_stream(stream_dict_rev,
+ reflect_on_l2, False)
+
+ if primary_is_bridged_to_routed:
+ inbound_l2_acl = acl_idx_fwd['L2']
+ else:
+ inbound_l2_acl = acl_idx_rev['L2']
+
+ if primary_is_routed_to_bridged:
+ outbound_l2_acl = acl_idx_fwd['L2']
+ else:
+ outbound_l2_acl = acl_idx_rev['L2']
+
+ if primary_is_routed_to_bridged:
+ inbound_l3_acl = acl_idx_fwd['L3']
+ else:
+ inbound_l3_acl = acl_idx_rev['L3']
+
+ if primary_is_bridged_to_routed:
+ outbound_l3_acl = acl_idx_fwd['L3']
+ else:
+ outbound_l3_acl = acl_idx_rev['L3']
+
+ self.vapi.acl_interface_set_acl_list(sw_if_index=self.pg2.sw_if_index,
+ n_input=1,
+ acls=[inbound_l3_acl,
+ outbound_l3_acl])
+ self.vapi.acl_interface_set_acl_list(sw_if_index=self.pg0.sw_if_index,
+ n_input=1,
+ acls=[inbound_l2_acl,
+ outbound_l2_acl])
+ self.vapi.acl_interface_set_acl_list(sw_if_index=self.pg1.sw_if_index,
+ n_input=1,
+ acls=[inbound_l2_acl,
+ outbound_l2_acl])
+
+ def apply_acl_ip46_routed_to_bridged(self, test_l2_deny, is_ip6,
+ is_reflect, add_eh):
+ self.apply_acl_ip46_x_to_y(False, test_l2_deny, is_ip6,
+ is_reflect, add_eh)
+
+ def apply_acl_ip46_bridged_to_routed(self, test_l2_deny, is_ip6,
+ is_reflect, add_eh):
+ self.apply_acl_ip46_x_to_y(True, test_l2_deny, is_ip6,
+ is_reflect, add_eh)
+
+ def run_traffic_ip46_x_to_y(self, bridged_to_routed,
+ test_l2_deny, is_ip6,
+ is_reflect, is_established, add_eh):
+ self.reset_packet_infos()
+ stream_dict = self.create_stream(self.pg2, self.loop0,
+ bridged_to_routed,
+ self.pg_if_packet_sizes, is_ip6,
+ not is_reflect, is_established,
+ add_eh)
+ stream = stream_dict['stream']
+
+ tx_if = self.pg0 if bridged_to_routed else self.pg2
+ rx_if = self.pg2 if bridged_to_routed else self.pg0
+
+ tx_if.add_stream(stream)
+ self.pg_enable_capture(self.pg_interfaces)
+ self.pg_start()
+ packet_count = self.get_packet_count_for_if_idx(self.loop0.sw_if_index)
+ rcvd1 = rx_if.get_capture(packet_count)
+ self.verify_capture(self.loop0, self.pg2, rcvd1, bridged_to_routed)
+
+ def run_traffic_ip46_routed_to_bridged(self, test_l2_deny, is_ip6,
+ is_reflect, is_established, add_eh):
+ self.run_traffic_ip46_x_to_y(False, test_l2_deny, is_ip6,
+ is_reflect, is_established, add_eh)
+
+ def run_traffic_ip46_bridged_to_routed(self, test_l2_deny, is_ip6,
+ is_reflect, is_established, add_eh):
+ self.run_traffic_ip46_x_to_y(True, test_l2_deny, is_ip6,
+ is_reflect, is_established, add_eh)
+
+ def run_test_ip46_routed_to_bridged(self, test_l2_deny,
+ is_ip6, is_reflect, add_eh):
+ self.apply_acl_ip46_routed_to_bridged(test_l2_deny,
+ is_ip6, is_reflect, add_eh)
+ self.run_traffic_ip46_routed_to_bridged(test_l2_deny, is_ip6,
+ is_reflect, False, add_eh)
+
+ def run_test_ip46_bridged_to_routed(self, test_l2_deny,
+ is_ip6, is_reflect, add_eh):
+ self.apply_acl_ip46_bridged_to_routed(test_l2_deny,
+ is_ip6, is_reflect, add_eh)
+ self.run_traffic_ip46_bridged_to_routed(test_l2_deny, is_ip6,
+ is_reflect, False, add_eh)
+
+ def run_test_ip46_routed_to_bridged_and_back(self, test_l2_action,
+ is_ip6, add_eh):
+ self.apply_acl_ip46_both_directions_reflect(False, test_l2_action,
+ is_ip6, add_eh)
+ self.run_traffic_ip46_routed_to_bridged(test_l2_action, is_ip6,
+ True, False, add_eh)
+ self.run_traffic_ip46_bridged_to_routed(test_l2_action, is_ip6,
+ False, True, add_eh)
+
+ def run_test_ip46_bridged_to_routed_and_back(self, test_l2_action,
+ is_ip6, add_eh):
+ self.apply_acl_ip46_both_directions_reflect(True, test_l2_action,
+ is_ip6, add_eh)
+ self.run_traffic_ip46_bridged_to_routed(test_l2_action, is_ip6,
+ True, False, add_eh)
+ self.run_traffic_ip46_routed_to_bridged(test_l2_action, is_ip6,
+ False, True, add_eh)
+
+ def test_0000_ip6_irb_1(self):
+ """ ACL plugin prepare"""
+ if not self.vpp_dead:
+ cmd = "set acl-plugin session timeout udp idle 2000"
+ self.logger.info(self.vapi.ppcli(cmd))
+ # uncomment to not skip past the routing header
+ # and watch the EH tests fail
+ # self.logger.info(self.vapi.ppcli(
+ # "set acl-plugin skip-ipv6-extension-header 43 0"))
+ # uncomment to test the session limit (stateful tests will fail)
+ # self.logger.info(self.vapi.ppcli(
+ # "set acl-plugin session table max-entries 1"))
+ # new datapath is the default, but just in case
+ # self.logger.info(self.vapi.ppcli(
+ # "set acl-plugin l2-datapath new"))
+ # If you want to see some tests fail, uncomment the next line
+ # self.logger.info(self.vapi.ppcli(
+ # "set acl-plugin l2-datapath old"))
+
+ def test_0001_ip6_irb_1(self):
+ """ ACL IPv6 routed -> bridged, L2 ACL deny"""
+ self.run_test_ip46_routed_to_bridged(True, True, False,
+ self.WITHOUT_EH)
+
+ def test_0002_ip6_irb_1(self):
+ """ ACL IPv6 routed -> bridged, L3 ACL deny"""
+ self.run_test_ip46_routed_to_bridged(False, True, False,
+ self.WITHOUT_EH)
+
+ def test_0003_ip4_irb_1(self):
+ """ ACL IPv4 routed -> bridged, L2 ACL deny"""
+ self.run_test_ip46_routed_to_bridged(True, False, False,
+ self.WITHOUT_EH)
+
+ def test_0004_ip4_irb_1(self):
+ """ ACL IPv4 routed -> bridged, L3 ACL deny"""
+ self.run_test_ip46_routed_to_bridged(False, False, False,
+ self.WITHOUT_EH)
+
+ def test_0005_ip6_irb_1(self):
+ """ ACL IPv6 bridged -> routed, L2 ACL deny """
+ self.run_test_ip46_bridged_to_routed(True, True, False,
+ self.WITHOUT_EH)
+
+ def test_0006_ip6_irb_1(self):
+ """ ACL IPv6 bridged -> routed, L3 ACL deny """
+ self.run_test_ip46_bridged_to_routed(False, True, False,
+ self.WITHOUT_EH)
+
+ def test_0007_ip6_irb_1(self):
+ """ ACL IPv4 bridged -> routed, L2 ACL deny """
+ self.run_test_ip46_bridged_to_routed(True, False, False,
+ self.WITHOUT_EH)
+
+ def test_0008_ip6_irb_1(self):
+ """ ACL IPv4 bridged -> routed, L3 ACL deny """
+ self.run_test_ip46_bridged_to_routed(False, False, False,
+ self.WITHOUT_EH)
+
+ # Stateful ACL tests
+ def test_0101_ip6_irb_1(self):
+ """ ACL IPv6 routed -> bridged, L2 ACL permit+reflect"""
+ self.run_test_ip46_routed_to_bridged_and_back(True, True,
+ self.WITHOUT_EH)
+
+ def test_0102_ip6_irb_1(self):
+ """ ACL IPv6 bridged -> routed, L2 ACL permit+reflect"""
+ self.run_test_ip46_bridged_to_routed_and_back(True, True,
+ self.WITHOUT_EH)
+
+ def test_0103_ip6_irb_1(self):
+ """ ACL IPv4 routed -> bridged, L2 ACL permit+reflect"""
+ self.run_test_ip46_routed_to_bridged_and_back(True, False,
+ self.WITHOUT_EH)
+
+ def test_0104_ip6_irb_1(self):
+ """ ACL IPv4 bridged -> routed, L2 ACL permit+reflect"""
+ self.run_test_ip46_bridged_to_routed_and_back(True, False,
+ self.WITHOUT_EH)
+
+ def test_0111_ip6_irb_1(self):
+ """ ACL IPv6 routed -> bridged, L3 ACL permit+reflect"""
+ self.run_test_ip46_routed_to_bridged_and_back(False, True,
+ self.WITHOUT_EH)
+
+ def test_0112_ip6_irb_1(self):
+ """ ACL IPv6 bridged -> routed, L3 ACL permit+reflect"""
+ self.run_test_ip46_bridged_to_routed_and_back(False, True,
+ self.WITHOUT_EH)
+
+ def test_0113_ip6_irb_1(self):
+ """ ACL IPv4 routed -> bridged, L3 ACL permit+reflect"""
+ self.run_test_ip46_routed_to_bridged_and_back(False, False,
+ self.WITHOUT_EH)
+
+ def test_0114_ip6_irb_1(self):
+ """ ACL IPv4 bridged -> routed, L3 ACL permit+reflect"""
+ self.run_test_ip46_bridged_to_routed_and_back(False, False,
+ self.WITHOUT_EH)
+
+ # A block of tests with extension headers
+
+ def test_1001_ip6_irb_1(self):
+ """ ACL IPv6+EH routed -> bridged, L2 ACL deny"""
+ self.run_test_ip46_routed_to_bridged(True, True, False,
+ self.WITH_EH)
+
+ def test_1002_ip6_irb_1(self):
+ """ ACL IPv6+EH routed -> bridged, L3 ACL deny"""
+ self.run_test_ip46_routed_to_bridged(False, True, False,
+ self.WITH_EH)
+
+ def test_1005_ip6_irb_1(self):
+ """ ACL IPv6+EH bridged -> routed, L2 ACL deny """
+ self.run_test_ip46_bridged_to_routed(True, True, False,
+ self.WITH_EH)
+
+ def test_1006_ip6_irb_1(self):
+ """ ACL IPv6+EH bridged -> routed, L3 ACL deny """
+ self.run_test_ip46_bridged_to_routed(False, True, False,
+ self.WITH_EH)
+
+ def test_1101_ip6_irb_1(self):
+ """ ACL IPv6+EH routed -> bridged, L2 ACL permit+reflect"""
+ self.run_test_ip46_routed_to_bridged_and_back(True, True,
+ self.WITH_EH)
+
+ def test_1102_ip6_irb_1(self):
+ """ ACL IPv6+EH bridged -> routed, L2 ACL permit+reflect"""
+ self.run_test_ip46_bridged_to_routed_and_back(True, True,
+ self.WITH_EH)
+
+ def test_1111_ip6_irb_1(self):
+ """ ACL IPv6+EH routed -> bridged, L3 ACL permit+reflect"""
+ self.run_test_ip46_routed_to_bridged_and_back(False, True,
+ self.WITH_EH)
+
+ def test_1112_ip6_irb_1(self):
+ """ ACL IPv6+EH bridged -> routed, L3 ACL permit+reflect"""
+ self.run_test_ip46_bridged_to_routed_and_back(False, True,
+ self.WITH_EH)
+
+ # IPv4 with "MF" bit set
+
+ def test_1201_ip6_irb_1(self):
+ """ ACL IPv4+MF routed -> bridged, L2 ACL deny"""
+ self.run_test_ip46_routed_to_bridged(True, False, False,
+ self.WITH_EH)
+
+ def test_1202_ip6_irb_1(self):
+ """ ACL IPv4+MF routed -> bridged, L3 ACL deny"""
+ self.run_test_ip46_routed_to_bridged(False, False, False,
+ self.WITH_EH)
+
+ def test_1205_ip6_irb_1(self):
+ """ ACL IPv4+MF bridged -> routed, L2 ACL deny """
+ self.run_test_ip46_bridged_to_routed(True, False, False,
+ self.WITH_EH)
+
+ def test_1206_ip6_irb_1(self):
+ """ ACL IPv4+MF bridged -> routed, L3 ACL deny """
+ self.run_test_ip46_bridged_to_routed(False, False, False,
+ self.WITH_EH)
+
+ def test_1301_ip6_irb_1(self):
+ """ ACL IPv4+MF routed -> bridged, L2 ACL permit+reflect"""
+ self.run_test_ip46_routed_to_bridged_and_back(True, False,
+ self.WITH_EH)
+
+ def test_1302_ip6_irb_1(self):
+ """ ACL IPv4+MF bridged -> routed, L2 ACL permit+reflect"""
+ self.run_test_ip46_bridged_to_routed_and_back(True, False,
+ self.WITH_EH)
+
+ def test_1311_ip6_irb_1(self):
+ """ ACL IPv4+MF routed -> bridged, L3 ACL permit+reflect"""
+ self.run_test_ip46_routed_to_bridged_and_back(False, False,
+ self.WITH_EH)
+
+ def test_1312_ip6_irb_1(self):
+ """ ACL IPv4+MF bridged -> routed, L3 ACL permit+reflect"""
+ self.run_test_ip46_bridged_to_routed_and_back(False, False,
+ self.WITH_EH)
+
+if __name__ == '__main__':
+ unittest.main(testRunner=VppTestRunner)
diff --git a/test/test_acl_plugin_macip.py b/test/test_acl_plugin_macip.py
new file mode 100644
index 00000000..f10d6cc6
--- /dev/null
+++ b/test/test_acl_plugin_macip.py
@@ -0,0 +1,961 @@
+#!/usr/bin/env python
+"""ACL plugin - MACIP tests
+"""
+import random
+import re
+import unittest
+
+from socket import inet_ntop, inet_pton, AF_INET, AF_INET6
+from struct import *
+from scapy.packet import Raw
+from scapy.layers.l2 import Ether
+from scapy.layers.inet import IP, UDP, TCP
+from scapy.layers.inet6 import IPv6
+
+from framework import VppTestCase, VppTestRunner, running_extended_tests
+from vpp_lo_interface import VppLoInterface
+
+
+class TestMACIP(VppTestCase):
+ """MACIP Test Case"""
+
+ DEBUG = False
+
+ BRIDGED = True
+ ROUTED = False
+
+ IS_IP4 = False
+ IS_IP6 = True
+
+ # rule types
+ DENY = 0
+ PERMIT = 1
+
+ # ACL types
+ EXACT_IP = 1
+ SUBNET_IP = 2
+ WILD_IP = 3
+
+ EXACT_MAC = 1
+ WILD_MAC = 2
+ OUI_MAC = 3
+
+ ACLS = []
+
+ @classmethod
+ def setUpClass(cls):
+ """
+ Perform standard class setup (defined by class method setUpClass in
+ class VppTestCase) before running the test case, set test case related
+ variables and configure VPP.
+ """
+ super(TestMACIP, cls).setUpClass()
+
+ cls.pg_if_packet_sizes = [64, 512, 1518, 9018] # packet sizes
+ cls.bd_id = 10
+ cls.remote_hosts_count = 250
+
+ try:
+ # create 3 pg interfaces, 1 loopback interface
+ cls.create_pg_interfaces(range(3))
+ cls.create_loopback_interfaces(range(1))
+
+ cls.interfaces = list(cls.pg_interfaces)
+ cls.interfaces.extend(cls.lo_interfaces)
+
+ for i in cls.interfaces:
+ i.admin_up()
+
+ # Create BD with MAC learning enabled and put interfaces to this BD
+ cls.vapi.sw_interface_set_l2_bridge(
+ cls.loop0.sw_if_index, bd_id=cls.bd_id, bvi=1)
+ cls.vapi.sw_interface_set_l2_bridge(
+ cls.pg0.sw_if_index, bd_id=cls.bd_id)
+ cls.vapi.sw_interface_set_l2_bridge(
+ cls.pg1.sw_if_index, bd_id=cls.bd_id)
+
+ # Configure IPv4 addresses on loop interface and routed interface
+ cls.loop0.config_ip4()
+ cls.loop0.config_ip6()
+ cls.pg2.config_ip4()
+ cls.pg2.config_ip6()
+
+ # Configure MAC address binding to IPv4 neighbors on loop0
+ cls.loop0.generate_remote_hosts(cls.remote_hosts_count)
+ # Modify host mac addresses to have different OUI parts
+ for i in range(2, cls.remote_hosts_count + 2):
+ mac = cls.loop0.remote_hosts[i-2]._mac.split(':')
+ mac[2] = format(int(mac[2], 16) + i, "02x")
+ cls.loop0.remote_hosts[i - 2]._mac = ":".join(mac)
+
+ cls.loop0.configure_ipv4_neighbors()
+ cls.loop0.configure_ipv6_neighbors()
+ # configure MAC address on pg2
+ cls.pg2.resolve_arp()
+ cls.pg2.resolve_ndp()
+
+ # Loopback BVI interface has remote hosts
+ # one half of hosts are behind pg0 second behind pg1
+ half = cls.remote_hosts_count // 2
+ cls.pg0.remote_hosts = cls.loop0.remote_hosts[:half]
+ cls.pg1.remote_hosts = cls.loop0.remote_hosts[half:]
+
+ except Exception:
+ super(TestMACIP, cls).tearDownClass()
+ raise
+
+ def setUp(self):
+ super(TestMACIP, self).setUp()
+ self.reset_packet_infos()
+ del self.ACLS[:]
+
+ def tearDown(self):
+ """
+ Show various debug prints after each test.
+ """
+ super(TestMACIP, self).tearDown()
+ if not self.vpp_dead:
+ self.logger.info(self.vapi.ppcli("show interface address"))
+ self.logger.info(self.vapi.ppcli("show hardware"))
+ self.logger.info(self.vapi.ppcli("sh acl-plugin macip acl"))
+ self.logger.info(self.vapi.ppcli("sh acl-plugin macip interface"))
+ self.logger.info(self.vapi.ppcli("sh classify tables verbose"))
+ # print self.vapi.ppcli("show interface address")
+ # print self.vapi.ppcli("show hardware")
+ # print self.vapi.ppcli("sh acl-plugin macip interface")
+ # print self.vapi.ppcli("sh acl-plugin macip acl")
+ self.delete_acls()
+
+ def macip_acl_dump_debug(self):
+ acls = self.vapi.macip_acl_dump()
+ if self.DEBUG:
+ for acl in acls:
+ print "ACL #"+str(acl.acl_index)
+ for r in acl.r:
+ rule = "ACTION"
+ if r.is_permit == 1:
+ rule = "PERMIT"
+ elif r.is_permit == 0:
+ rule = "DENY "
+ print " IP6" if r.is_ipv6 else " IP4", \
+ rule, \
+ r.src_mac.encode('hex'), \
+ r.src_mac_mask.encode('hex'),\
+ unpack('<16B', r.src_ip_addr), \
+ r.src_ip_prefix_len
+ return acls
+
+ def create_rules(self, mac_type=EXACT_MAC, ip_type=EXACT_IP,
+ acl_count=1, rules_count=[1]):
+ acls = []
+ src_mac = int("220000dead00", 16)
+ for acl in range(2, (acl_count+1) * 2):
+ rules = []
+ host = random.choice(self.loop0.remote_hosts)
+ is_ip6 = acl % 2
+ ip4 = host.ip4.split('.')
+ ip6 = list(unpack('<16B', inet_pton(AF_INET6, host.ip6)))
+
+ if ip_type == self.EXACT_IP:
+ prefix_len4 = 32
+ prefix_len6 = 128
+ elif ip_type == self.WILD_IP:
+ ip4 = [0, 0, 0, 0]
+ ip6 = [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]
+ prefix_len4 = 0
+ prefix_len6 = 0
+ rules_count[(acl / 2) - 1] = 1
+ else:
+ prefix_len4 = 24
+ prefix_len6 = 64
+
+ if mac_type == self.EXACT_MAC:
+ mask = "ff:ff:ff:ff:ff:ff"
+ elif mac_type == self.WILD_MAC:
+ mask = "00:00:00:00:00:00"
+ elif mac_type == self.OUI_MAC:
+ mask = "ff:ff:ff:00:00:00"
+ else:
+ mask = "ff:ff:ff:ff:ff:00"
+
+ ip = ip6 if is_ip6 else ip4
+ ip_len = prefix_len6 if is_ip6 else prefix_len4
+
+ for i in range(0, rules_count[(acl / 2) - 1]):
+ src_mac += 16777217
+ if mac_type == self.WILD_MAC:
+ mac = "00:00:00:00:00:00"
+ elif mac_type == self.OUI_MAC:
+ mac = ':'.join(re.findall('..', '{:02x}'.format(
+ src_mac))[:3])+":00:00:00"
+ else:
+ mac = ':'.join(re.findall('..', '{:02x}'.format(src_mac)))
+
+ if ip_type == self.EXACT_IP:
+ ip4[3] = random.randint(100, 200)
+ ip6[15] = random.randint(100, 200)
+ elif ip_type == self.SUBNET_IP:
+ ip4[2] = random.randint(100, 200)
+ ip4[3] = 0
+ ip6[8] = random.randint(100, 200)
+ ip6[15] = 0
+ ip_pack = ''
+ for j in range(0, len(ip)):
+ ip_pack += pack('<B', int(ip[j]))
+
+ rule = ({'is_permit': self.PERMIT,
+ 'is_ipv6': is_ip6,
+ 'src_ip_addr': ip_pack,
+ 'src_ip_prefix_len': ip_len,
+ 'src_mac': mac.replace(':', '').decode('hex'),
+ 'src_mac_mask': mask.replace(':', '').decode('hex')})
+ rules.append(rule)
+ if ip_type == self.WILD_IP:
+ break
+
+ acls.append(rules)
+ src_mac += 1099511627776
+ return acls
+
+ def apply_rules(self, acls):
+ for acl in acls:
+ reply = self.vapi.macip_acl_add(acl)
+ self.assertEqual(reply.retval, 0)
+ self.ACLS.append(reply.acl_index)
+
+ def verify_acls(self, acl_count, rules_count, expected_count=2):
+ reply = self.macip_acl_dump_debug()
+ for acl in range(2, (acl_count+1) * 2):
+ self.assertEqual(reply[acl - 2].count, rules_count[acl/2-1])
+
+ self.vapi.macip_acl_interface_get()
+
+ self.vapi.macip_acl_interface_add_del(sw_if_index=0, acl_index=0)
+ self.vapi.macip_acl_interface_add_del(sw_if_index=1, acl_index=1)
+
+ reply = self.vapi.macip_acl_interface_get()
+ self.assertEqual(reply.count, expected_count)
+
+ def delete_acls(self):
+ for acl in range(len(self.ACLS)-1, -1, -1):
+ self.vapi.macip_acl_del(self.ACLS[acl])
+
+ reply = self.vapi.macip_acl_dump()
+ self.assertEqual(len(reply), 0)
+
+ def create_stream(self, mac_type, ip_type, packet_count,
+ src_ip_if, dst_ip_if, bridged_routed, is_ip6):
+ # exact MAC and exact IP
+ # exact MAC and subnet of IPs
+ # exact MAC and wildcard IP
+ # wildcard MAC and exact IP
+ # wildcard MAC and subnet of IPs
+ # wildcard MAC and wildcard IP
+ # OUI restricted MAC and exact IP
+ # OUI restricted MAC and subnet of IPs
+ # OUI restricted MAC and wildcard IP
+
+ packets = []
+ rules = []
+ ip_permit = ""
+ mac_permit = ""
+ dst_mac = ""
+ mac_rule = "00:00:00:00:00:00"
+ mac_mask = "00:00:00:00:00:00"
+ for p in range(0, packet_count):
+ remote_dst_index = p % len(dst_ip_if.remote_hosts)
+ remote_dst_host = dst_ip_if.remote_hosts[remote_dst_index]
+
+ dst_port = 1234 + p
+ src_port = 4321 + p
+ is_permit = self.PERMIT if p % 3 == 0 else self.DENY
+ denyMAC = True if not is_permit and p % 3 == 1 else False
+ denyIP = True if not is_permit and p % 3 == 2 else False
+ if not is_permit and ip_type == self.WILD_IP:
+ denyMAC = True
+ if not is_permit and mac_type == self.WILD_MAC:
+ denyIP = True
+
+ if bridged_routed == self.BRIDGED:
+ if is_permit:
+ src_mac = remote_dst_host._mac
+ dst_mac = 'de:ad:00:00:00:00'
+ src_ip4 = remote_dst_host.ip4
+ dst_ip4 = src_ip_if.remote_ip4
+ src_ip6 = remote_dst_host.ip6
+ dst_ip6 = src_ip_if.remote_ip6
+ ip_permit = src_ip6 if is_ip6 else src_ip4
+ mac_permit = src_mac
+ if denyMAC:
+ mac = src_mac.split(':')
+ mac[0] = format(int(mac[0], 16)+1, "02x")
+ src_mac = ":".join(mac)
+ if is_ip6:
+ src_ip6 = ip_permit
+ else:
+ src_ip4 = ip_permit
+ if denyIP:
+ if ip_type != self.WILD_IP:
+ src_mac = mac_permit
+ src_ip4 = remote_dst_host.ip4
+ dst_ip4 = src_ip_if.remote_ip4
+ src_ip6 = remote_dst_host.ip6
+ dst_ip6 = src_ip_if.remote_ip6
+ else:
+ if is_permit:
+ src_mac = remote_dst_host._mac
+ dst_mac = src_ip_if.local_mac
+ src_ip4 = src_ip_if.remote_ip4
+ dst_ip4 = remote_dst_host.ip4
+ src_ip6 = src_ip_if.remote_ip6
+ dst_ip6 = remote_dst_host.ip6
+ ip_permit = src_ip6 if is_ip6 else src_ip4
+ mac_permit = src_mac
+ if denyMAC:
+ mac = src_mac.split(':')
+ mac[0] = format(int(mac[0], 16) + 1, "02x")
+ src_mac = ":".join(mac)
+ if is_ip6:
+ src_ip6 = ip_permit
+ else:
+ src_ip4 = ip_permit
+ if denyIP:
+ src_mac = remote_dst_host._mac
+ if ip_type != self.WILD_IP:
+ src_mac = mac_permit
+ src_ip4 = remote_dst_host.ip4
+ dst_ip4 = src_ip_if.remote_ip4
+ src_ip6 = remote_dst_host.ip6
+ dst_ip6 = src_ip_if.remote_ip6
+
+ if is_permit:
+ info = self.create_packet_info(src_ip_if, dst_ip_if)
+ payload = self.info_to_payload(info)
+ else:
+ payload = "to be blocked"
+
+ if mac_type == self.WILD_MAC:
+ mac = src_mac.split(':')
+ for i in range(1, 5):
+ mac[i] = format(random.randint(0, 255), "02x")
+ src_mac = ":".join(mac)
+
+ # create packet
+ packet = Ether(src=src_mac, dst=dst_mac)
+ ip_rule = src_ip6 if is_ip6 else src_ip4
+ if is_ip6:
+ if ip_type != self.EXACT_IP:
+ sub_ip = list(unpack('<16B', inet_pton(AF_INET6, ip_rule)))
+ if ip_type == self.WILD_IP:
+ sub_ip[0] = random.randint(240, 254)
+ sub_ip[1] = random.randint(230, 239)
+ sub_ip[14] = random.randint(100, 199)
+ sub_ip[15] = random.randint(200, 255)
+ elif ip_type == self.SUBNET_IP:
+ if denyIP:
+ sub_ip[2] = str(int(sub_ip[2]) + 1)
+ sub_ip[14] = random.randint(100, 199)
+ sub_ip[15] = random.randint(200, 255)
+ src_ip6 = inet_ntop(AF_INET6, str(bytearray(sub_ip)))
+ packet /= IPv6(src=src_ip6, dst=dst_ip6)
+ else:
+ if ip_type != self.EXACT_IP:
+ sub_ip = ip_rule.split('.')
+ if ip_type == self.WILD_IP:
+ sub_ip[0] = str(random.randint(1, 49))
+ sub_ip[1] = str(random.randint(50, 99))
+ sub_ip[2] = str(random.randint(100, 199))
+ sub_ip[3] = str(random.randint(200, 255))
+ elif ip_type == self.SUBNET_IP:
+ if denyIP:
+ sub_ip[1] = str(int(sub_ip[1])+1)
+ sub_ip[2] = str(random.randint(100, 199))
+ sub_ip[3] = str(random.randint(200, 255))
+ src_ip4 = ".".join(sub_ip)
+ packet /= IP(src=src_ip4, dst=dst_ip4, frag=0, flags=0)
+
+ packet /= UDP(sport=src_port, dport=dst_port)/Raw(payload)
+
+ packet[Raw].load += " mac:"+src_mac
+
+ size = self.pg_if_packet_sizes[p % len(self.pg_if_packet_sizes)]
+ self.extend_packet(packet, size)
+ packets.append(packet)
+
+ # create suitable rule
+ if mac_type == self.EXACT_MAC:
+ mac_rule = src_mac
+ mac_mask = "ff:ff:ff:ff:ff:ff"
+ elif mac_type == self.WILD_MAC:
+ mac_rule = "00:00:00:00:00:00"
+ mac_mask = "00:00:00:00:00:00"
+ elif mac_type == self.OUI_MAC:
+ mac = src_mac.split(':')
+ mac[3] = mac[4] = mac[5] = '00'
+ mac_rule = ":".join(mac)
+ mac_mask = "ff:ff:ff:00:00:00"
+
+ if is_ip6:
+ if ip_type == self.WILD_IP:
+ ip = "0::0"
+ else:
+ ip = src_ip6
+ if ip_type == self.SUBNET_IP:
+ sub_ip = list(unpack('<16B', inet_pton(AF_INET6, ip)))
+ for i in range(8, 16):
+ sub_ip[i] = 0
+ ip = inet_ntop(AF_INET6, str(bytearray(sub_ip)))
+ else:
+ if ip_type == self.WILD_IP:
+ ip = "0.0.0.0"
+ else:
+ ip = src_ip4
+ if ip_type == self.SUBNET_IP:
+ sub_ip = ip.split('.')
+ sub_ip[2] = sub_ip[3] = '0'
+ ip = ".".join(sub_ip)
+
+ prefix_len = 128 if is_ip6 else 32
+ if ip_type == self.WILD_IP:
+ prefix_len = 0
+ elif ip_type == self.SUBNET_IP:
+ prefix_len = 64 if is_ip6 else 16
+ ip_rule = inet_pton(AF_INET6 if is_ip6 else AF_INET, ip)
+
+ if mac_type == self.WILD_MAC and ip_type == self.WILD_IP and p > 0:
+ continue
+
+ if is_permit:
+ rule = ({'is_permit': is_permit,
+ 'is_ipv6': is_ip6,
+ 'src_ip_addr': ip_rule,
+ 'src_ip_prefix_len': prefix_len,
+ 'src_mac': mac_rule.replace(':', '').decode('hex'),
+ 'src_mac_mask': mac_mask.replace(':', '').decode(
+ 'hex')})
+ rules.append(rule)
+
+ # deny all other packets
+ if not (mac_type == self.WILD_MAC and ip_type == self.WILD_IP):
+ rule = ({'is_permit': 0,
+ 'is_ipv6': is_ip6,
+ 'src_ip_addr': "",
+ 'src_ip_prefix_len': 0,
+ 'src_mac': "",
+ 'src_mac_mask': ""})
+ rules.append(rule)
+
+ return {'stream': packets, 'rules': rules}
+
+ def verify_capture(self, stream, capture, is_ip6):
+ p_l3 = IPv6 if is_ip6 else IP
+ if self.DEBUG:
+ for p in stream:
+ print p[Ether].src, p[Ether].dst, p[p_l3].src, p[p_l3].dst
+
+ acls = self.macip_acl_dump_debug()
+
+ # TODO : verify
+ # for acl in acls:
+ # for r in acl.r:
+ # print r.src_mac.encode('hex'), \
+ # r.src_mac_mask.encode('hex'),\
+ # unpack('<16B', r.src_ip_addr), \
+ # r.src_ip_prefix_len
+ #
+ # for p in capture:
+ # print p[Ether].src, p[Ether].dst, p[p_l3].src, p[p_l3].dst
+ # data = p[Raw].load.split(':',1)[1]
+ # print p[p_l3].src, data
+
+ def run_traffic(self, mac_type, ip_type, bridged_routed, is_ip6, packets,
+ do_not_expected_capture=False):
+ self.reset_packet_infos()
+
+ tx_if = self.pg0 if bridged_routed == self.BRIDGED else self.pg2
+ rx_if = self.pg2 if bridged_routed == self.BRIDGED else self.pg0
+
+ test_dict = self.create_stream(mac_type, ip_type, packets,
+ self.pg2, self.loop0,
+ bridged_routed,
+ is_ip6)
+
+ reply = self.vapi.macip_acl_add(test_dict['rules'])
+ self.assertEqual(reply.retval, 0)
+ acl_index = reply.acl_index
+
+ self.vapi.macip_acl_interface_add_del(sw_if_index=tx_if.sw_if_index,
+ acl_index=acl_index)
+ reply = self.vapi.macip_acl_interface_get()
+ self.assertEqual(reply.acls[tx_if.sw_if_index], acl_index)
+ self.ACLS.append(reply.acls[tx_if.sw_if_index])
+
+ tx_if.add_stream(test_dict['stream'])
+ self.pg_enable_capture(self.pg_interfaces)
+ self.pg_start()
+
+ if do_not_expected_capture:
+ rx_if.get_capture(0)
+ else:
+ packet_count = self.get_packet_count_for_if_idx(
+ self.loop0.sw_if_index)
+ if mac_type == self.WILD_MAC and ip_type == self.WILD_IP:
+ packet_count = packets if bridged_routed else packet_count
+ capture = rx_if.get_capture(packet_count)
+ self.verify_capture(test_dict['stream'], capture, is_ip6)
+
+ def run_test_acls(self, mac_type, ip_type, acl_count,
+ rules_count, traffic=None, ip=None):
+ self.apply_rules(self.create_rules(mac_type, ip_type, acl_count,
+ rules_count))
+ self.verify_acls(acl_count, rules_count)
+
+ if traffic is not None:
+ self.run_traffic(self.EXACT_MAC, self.EXACT_IP, traffic, ip, 9)
+
+ def test_acl_bridged_ip4_exactMAC_exactIP(self):
+ """ IP4 MACIP exactMAC|exactIP ACL bridged traffic
+ """
+ self.run_traffic(self.EXACT_MAC, self.EXACT_IP,
+ self.BRIDGED, self.IS_IP4, 9)
+
+ def test_acl_bridged_ip6_exactMAC_exactIP(self):
+ """ IP6 MACIP exactMAC|exactIP ACL bridged traffic
+ """
+
+ self.run_traffic(self.EXACT_MAC, self.EXACT_IP,
+ self.BRIDGED, self.IS_IP6, 9)
+
+ def test_acl_bridged_ip4_exactMAC_subnetIP(self):
+ """ IP4 MACIP exactMAC|subnetIP ACL bridged traffic
+ """
+
+ self.run_traffic(self.EXACT_MAC, self.SUBNET_IP,
+ self.BRIDGED, self.IS_IP4, 9)
+
+ def test_acl_bridged_ip6_exactMAC_subnetIP(self):
+ """ IP6 MACIP exactMAC|subnetIP ACL bridged traffic
+ """
+
+ self.run_traffic(self.EXACT_MAC, self.SUBNET_IP,
+ self.BRIDGED, self.IS_IP6, 9)
+
+ def test_acl_bridged_ip4_exactMAC_wildIP(self):
+ """ IP4 MACIP exactMAC|wildIP ACL bridged traffic
+ """
+
+ self.run_traffic(self.EXACT_MAC, self.WILD_IP,
+ self.BRIDGED, self.IS_IP4, 9)
+
+ def test_acl_bridged_ip6_exactMAC_wildIP(self):
+ """ IP6 MACIP exactMAC|wildIP ACL bridged traffic
+ """
+
+ self.run_traffic(self.EXACT_MAC, self.WILD_IP,
+ self.BRIDGED, self.IS_IP6, 9)
+
+ def test_acl_bridged_ip4_ouiMAC_exactIP(self):
+ """ IP4 MACIP ouiMAC|exactIP ACL bridged traffic
+ """
+
+ self.run_traffic(self.OUI_MAC, self.EXACT_IP,
+ self.BRIDGED, self.IS_IP4, 3)
+
+ def test_acl_bridged_ip6_ouiMAC_exactIP(self):
+ """ IP6 MACIP oui_MAC|exactIP ACL bridged traffic
+ """
+
+ self.run_traffic(self.OUI_MAC, self.EXACT_IP,
+ self.BRIDGED, self.IS_IP6, 9)
+
+ def test_acl_bridged_ip4_ouiMAC_subnetIP(self):
+ """ IP4 MACIP ouiMAC|subnetIP ACL bridged traffic
+ """
+
+ self.run_traffic(self.OUI_MAC, self.SUBNET_IP,
+ self.BRIDGED, self.IS_IP4, 9)
+
+ def test_acl_bridged_ip6_ouiMAC_subnetIP(self):
+ """ IP6 MACIP ouiMAC|subnetIP ACL bridged traffic
+ """
+
+ self.run_traffic(self.OUI_MAC, self.SUBNET_IP,
+ self.BRIDGED, self.IS_IP6, 9)
+
+ def test_acl_bridged_ip4_ouiMAC_wildIP(self):
+ """ IP4 MACIP ouiMAC|wildIP ACL bridged traffic
+ """
+
+ self.run_traffic(self.OUI_MAC, self.WILD_IP,
+ self.BRIDGED, self.IS_IP4, 9)
+
+ def test_acl_bridged_ip6_ouiMAC_wildIP(self):
+ """ IP6 MACIP ouiMAC|wildIP ACL bridged traffic
+ """
+
+ self.run_traffic(self.OUI_MAC, self.WILD_IP,
+ self.BRIDGED, self.IS_IP6, 9)
+
+ def test_ac_bridgedl_ip4_wildMAC_exactIP(self):
+ """ IP4 MACIP wildcardMAC|exactIP ACL bridged traffic
+ """
+
+ self.run_traffic(self.WILD_MAC, self.EXACT_IP,
+ self.BRIDGED, self.IS_IP4, 9)
+
+ def test_acl_bridged_ip6_wildMAC_exactIP(self):
+ """ IP6 MACIP wildcardMAC|exactIP ACL bridged traffic
+ """
+
+ self.run_traffic(self.WILD_MAC, self.EXACT_IP,
+ self.BRIDGED, self.IS_IP6, 9)
+
+ def test_acl_bridged_ip4_wildMAC_subnetIP(self):
+ """ IP4 MACIP wildcardMAC|subnetIP ACL bridged traffic
+ """
+
+ self.run_traffic(self.WILD_MAC, self.SUBNET_IP,
+ self.BRIDGED, self.IS_IP4, 9)
+
+ def test_acl_bridged_ip6_wildMAC_subnetIP(self):
+ """ IP6 MACIP wildcardMAC|subnetIP ACL bridged traffic
+ """
+
+ self.run_traffic(self.WILD_MAC, self.SUBNET_IP,
+ self.BRIDGED, self.IS_IP6, 9)
+
+ def test_acl_bridged_ip4_wildMAC_wildIP(self):
+ """ IP4 MACIP wildcardMAC|wildIP ACL bridged traffic
+ """
+
+ self.run_traffic(self.WILD_MAC, self.WILD_IP,
+ self.BRIDGED, self.IS_IP4, 9)
+
+ def test_acl_bridged_ip6_wildMAC_wildIP(self):
+ """ IP6 MACIP wildcardMAC|wildIP ACL bridged traffic
+ """
+
+ self.run_traffic(self.WILD_MAC, self.WILD_IP,
+ self.BRIDGED, self.IS_IP6, 9)
+
+ def test_acl_routed_ip4_exactMAC_exactIP(self):
+ """ IP4 MACIP exactMAC|exactIP ACL routed traffic
+ """
+ self.run_traffic(self.EXACT_MAC, self.EXACT_IP,
+ self.ROUTED, self.IS_IP4, 9)
+
+ def test_acl_routed_ip6_exactMAC_exactIP(self):
+ """ IP6 MACIP exactMAC|exactIP ACL routed traffic
+ """
+
+ self.run_traffic(self.EXACT_MAC, self.EXACT_IP,
+ self.ROUTED, self.IS_IP6, 9)
+
+ def test_acl_routed_ip4_exactMAC_subnetIP(self):
+ """ IP4 MACIP exactMAC|subnetIP ACL routed traffic
+ """
+ self.run_traffic(self.EXACT_MAC, self.SUBNET_IP,
+ self.ROUTED, self.IS_IP4, 9)
+
+ def test_acl_routed_ip6_exactMAC_subnetIP(self):
+ """ IP6 MACIP exactMAC|subnetIP ACL routed traffic
+ """
+
+ self.run_traffic(self.EXACT_MAC, self.SUBNET_IP,
+ self.ROUTED, self.IS_IP6, 9)
+
+ def test_acl_routed_ip4_exactMAC_wildIP(self):
+ """ IP4 MACIP exactMAC|wildIP ACL routed traffic
+ """
+ self.run_traffic(self.EXACT_MAC, self.WILD_IP,
+ self.ROUTED, self.IS_IP4, 9)
+
+ def test_acl_routed_ip6_exactMAC_wildIP(self):
+ """ IP6 MACIP exactMAC|wildIP ACL routed traffic
+ """
+
+ self.run_traffic(self.EXACT_MAC, self.WILD_IP,
+ self.ROUTED, self.IS_IP6, 9)
+
+ def test_acl_routed_ip4_ouiMAC_exactIP(self):
+ """ IP4 MACIP ouiMAC|exactIP ACL routed traffic
+ """
+
+ self.run_traffic(self.OUI_MAC, self.EXACT_IP,
+ self.ROUTED, self.IS_IP4, 9)
+
+ def test_acl_routed_ip6_ouiMAC_exactIP(self):
+ """ IP6 MACIP ouiMAC|exactIP ACL routed traffic
+ """
+
+ self.run_traffic(self.OUI_MAC, self.EXACT_IP,
+ self.ROUTED, self.IS_IP6, 9)
+
+ def test_acl_routed_ip4_ouiMAC_subnetIP(self):
+ """ IP4 MACIP ouiMAC|subnetIP ACL routed traffic
+ """
+
+ self.run_traffic(self.OUI_MAC, self.SUBNET_IP,
+ self.ROUTED, self.IS_IP4, 9)
+
+ def test_acl_routed_ip6_ouiMAC_subnetIP(self):
+ """ IP6 MACIP ouiMAC|subnetIP ACL routed traffic
+ """
+
+ self.run_traffic(self.OUI_MAC, self.SUBNET_IP,
+ self.ROUTED, self.IS_IP6, 9)
+
+ def test_acl_routed_ip4_ouiMAC_wildIP(self):
+ """ IP4 MACIP ouiMAC|wildIP ACL routed traffic
+ """
+
+ self.run_traffic(self.OUI_MAC, self.WILD_IP,
+ self.ROUTED, self.IS_IP4, 9)
+
+ def test_acl_routed_ip6_ouiMAC_wildIP(self):
+ """ IP6 MACIP ouiMAC|wildIP ACL routed traffic
+ """
+
+ self.run_traffic(self.OUI_MAC, self.WILD_IP,
+ self.ROUTED, self.IS_IP6, 9)
+
+ def test_acl_routed_ip4_wildMAC_exactIP(self):
+ """ IP4 MACIP wildcardMAC|exactIP ACL routed traffic
+ """
+
+ self.run_traffic(self.WILD_MAC, self.EXACT_IP,
+ self.ROUTED, self.IS_IP4, 9)
+
+ def test_acl_routed_ip6_wildMAC_exactIP(self):
+ """ IP6 MACIP wildcardMAC|exactIP ACL routed traffic
+ """
+
+ self.run_traffic(self.WILD_MAC, self.EXACT_IP,
+ self.ROUTED, self.IS_IP6, 9)
+
+ def test_acl_routed_ip4_wildMAC_subnetIP(self):
+ """ IP4 MACIP wildcardMAC|subnetIP ACL routed traffic
+ """
+
+ self.run_traffic(self.WILD_MAC, self.SUBNET_IP,
+ self.ROUTED, self.IS_IP4, 9)
+
+ def test_acl_routed_ip6_wildMAC_subnetIP(self):
+ """ IP6 MACIP wildcardMAC|subnetIP ACL routed traffic
+ """
+
+ self.run_traffic(self.WILD_MAC, self.SUBNET_IP,
+ self.ROUTED, self.IS_IP6, 9)
+
+ def test_acl_1_2(self):
+ """ MACIP ACL with 2 entries
+ """
+
+ self.run_test_acls(self.EXACT_MAC, self.WILD_IP, 1, [2])
+
+ def test_acl_1_5(self):
+ """ MACIP ACL with 5 entries
+ """
+
+ self.run_test_acls(self.EXACT_MAC, self.SUBNET_IP, 1, [5])
+
+ def test_acl_1_10(self):
+ """ MACIP ACL with 10 entries
+ """
+
+ self.run_test_acls(self.EXACT_MAC, self.EXACT_IP, 1, [10])
+
+ def test_acl_1_20(self):
+ """ MACIP ACL with 20 entries
+ """
+
+ self.run_test_acls(self.OUI_MAC, self.WILD_IP, 1, [20])
+
+ def test_acl_1_50(self):
+ """ MACIP ACL with 50 entries
+ """
+
+ self.run_test_acls(self.OUI_MAC, self.SUBNET_IP, 1, [50])
+
+ def test_acl_1_100(self):
+ """ MACIP ACL with 100 entries
+ """
+
+ self.run_test_acls(self.OUI_MAC, self.EXACT_IP, 1, [100])
+
+ def test_acl_2_X(self):
+ """ MACIP 2 ACLs each with 100+ entries
+ """
+
+ self.run_test_acls(self.OUI_MAC, self.SUBNET_IP, 2, [100, 200])
+
+ def test_acl_10_X(self):
+ """ MACIP 10 ACLs each with 100+ entries
+ """
+
+ self.run_test_acls(self.EXACT_MAC, self.EXACT_IP, 10,
+ [100, 120, 140, 160, 180, 200, 210, 220, 230, 240])
+
+ def test_acl_10_X_traffic_ip4(self):
+ """ MACIP 10 ACLs each with 100+ entries with IP4 traffic
+ """
+
+ self.run_test_acls(self.EXACT_MAC, self.EXACT_IP, 10,
+ [100, 120, 140, 160, 180, 200, 210, 220, 230, 240],
+ self.BRIDGED, self.IS_IP4)
+
+ def test_acl_10_X_traffic_ip6(self):
+ """ MACIP 10 ACLs each with 100+ entries with IP6 traffic
+ """
+
+ self.run_test_acls(self.EXACT_MAC, self.EXACT_IP, 10,
+ [100, 120, 140, 160, 180, 200, 210, 220, 230, 240],
+ self.BRIDGED, self.IS_IP6)
+
+ def test_acl_replace(self):
+ """ MACIP replace ACL
+ """
+
+ r1 = self.create_rules(acl_count=3, rules_count=[2, 2, 2])
+ r2 = self.create_rules(mac_type=self.OUI_MAC, ip_type=self.SUBNET_IP)
+ self.apply_rules(r1)
+
+ acls_before = self.macip_acl_dump_debug()
+
+ # replace acls #2, #3 with new
+ reply = self.vapi.macip_acl_add_replace(r2[0], 2)
+ self.assertEqual(reply.retval, 0)
+ self.assertEqual(reply.acl_index, 2)
+ reply = self.vapi.macip_acl_add_replace(r2[1], 3)
+ self.assertEqual(reply.retval, 0)
+ self.assertEqual(reply.acl_index, 3)
+
+ acls_after = self.macip_acl_dump_debug()
+
+ # verify changes
+ self.assertEqual(len(acls_before), len(acls_after))
+ for acl1, acl2 in zip(
+ acls_before[:2]+acls_before[4:],
+ acls_after[:2]+acls_after[4:]):
+ self.assertEqual(len(acl1), len(acl2))
+
+ self.assertEqual(len(acl1.r), len(acl2.r))
+ for r1, r2 in zip(acl1.r, acl2.r):
+ self.assertEqual(len(acl1.r), len(acl2.r))
+ self.assertEqual(acl1.r, acl2.r)
+ for acl1, acl2 in zip(
+ acls_before[2:4],
+ acls_after[2:4]):
+ self.assertEqual(len(acl1), len(acl2))
+
+ self.assertNotEqual(len(acl1.r), len(acl2.r))
+ for r1, r2 in zip(acl1.r, acl2.r):
+ self.assertNotEqual(len(acl1.r), len(acl2.r))
+ self.assertNotEqual(acl1.r, acl2.r)
+
+ def test_acl_replace_traffic_ip4(self):
+ """ MACIP replace ACL with IP4 traffic
+ """
+ self.run_traffic(self.OUI_MAC, self.SUBNET_IP,
+ self.BRIDGED, self.IS_IP4, 9)
+
+ r = self.create_rules()
+ # replace acls #2, #3 with new
+ reply = self.vapi.macip_acl_add_replace(r[0], 0)
+ self.assertEqual(reply.retval, 0)
+ self.assertEqual(reply.acl_index, 0)
+
+ self.run_traffic(self.EXACT_MAC, self.EXACT_IP,
+ self.BRIDGED, self.IS_IP4, 9, True)
+
+ def test_acl_replace_traffic_ip6(self):
+ """ MACIP replace ACL with IP6 traffic
+ """
+ self.run_traffic(self.OUI_MAC, self.SUBNET_IP,
+ self.BRIDGED, self.IS_IP6, 9)
+
+ r = self.create_rules()
+ # replace acls #2, #3 with new
+ reply = self.vapi.macip_acl_add_replace(r[0], 0)
+ self.assertEqual(reply.retval, 0)
+ self.assertEqual(reply.acl_index, 0)
+
+ self.run_traffic(self.EXACT_MAC, self.EXACT_IP,
+ self.BRIDGED, self.IS_IP6, 9, True)
+
+ def test_delete_intf(self):
+ """ MACIP ACL delete intf with acl
+ """
+
+ intf_count = len(self.interfaces)+1
+ intf = []
+ self.apply_rules(self.create_rules(acl_count=3, rules_count=[3, 5, 4]))
+
+ intf.append(VppLoInterface(self, 0))
+ intf.append(VppLoInterface(self, 1))
+
+ sw_if_index0 = intf[0].sw_if_index
+ self.vapi.macip_acl_interface_add_del(sw_if_index0, 1)
+
+ reply = self.vapi.macip_acl_interface_get()
+ self.assertEqual(reply.count, intf_count+1)
+ self.assertEqual(reply.acls[sw_if_index0], 1)
+
+ sw_if_index1 = intf[1].sw_if_index
+ self.vapi.macip_acl_interface_add_del(sw_if_index1, 0)
+
+ reply = self.vapi.macip_acl_interface_get()
+ self.assertEqual(reply.count, intf_count+2)
+ self.assertEqual(reply.acls[sw_if_index1], 0)
+
+ intf[0].remove_vpp_config()
+ reply = self.vapi.macip_acl_interface_get()
+ self.assertEqual(reply.count, intf_count+2)
+ self.assertEqual(reply.acls[sw_if_index0], 4294967295)
+ self.assertEqual(reply.acls[sw_if_index1], 0)
+
+ intf.append(VppLoInterface(self, 2))
+ intf.append(VppLoInterface(self, 3))
+ sw_if_index2 = intf[2].sw_if_index
+ sw_if_index3 = intf[3].sw_if_index
+ self.vapi.macip_acl_interface_add_del(sw_if_index2, 1)
+ self.vapi.macip_acl_interface_add_del(sw_if_index3, 1)
+
+ reply = self.vapi.macip_acl_interface_get()
+ self.assertEqual(reply.count, intf_count+3)
+ self.assertEqual(reply.acls[sw_if_index1], 0)
+ self.assertEqual(reply.acls[sw_if_index2], 1)
+ self.assertEqual(reply.acls[sw_if_index3], 1)
+
+ intf[2].remove_vpp_config()
+ intf[1].remove_vpp_config()
+
+ reply = self.vapi.macip_acl_interface_get()
+ self.assertEqual(reply.count, intf_count+3)
+ self.assertEqual(reply.acls[sw_if_index0], 4294967295)
+ self.assertEqual(reply.acls[sw_if_index1], 4294967295)
+ self.assertEqual(reply.acls[sw_if_index2], 4294967295)
+ self.assertEqual(reply.acls[sw_if_index3], 1)
+
+ intf[3].remove_vpp_config()
+ reply = self.vapi.macip_acl_interface_get()
+
+ self.assertEqual(len([x for x in reply.acls if x != 4294967295]), 0)
+
+ def test_acl_routed_ip4_wildMAC_wildIP(self):
+ """ IP4 MACIP wildcardMAC|wildIP ACL
+ """
+
+ self.run_traffic(self.WILD_MAC, self.WILD_IP,
+ self.ROUTED, self.IS_IP4, 9)
+
+ def test_acl_routed_ip6_wildMAC_wildIP(self):
+ """ IP6 MACIP wildcardMAC|wildIP ACL
+ """
+
+ self.run_traffic(self.WILD_MAC, self.WILD_IP,
+ self.ROUTED, self.IS_IP6, 9)
+
+
+if __name__ == '__main__':
+ unittest.main(testRunner=VppTestRunner)
diff --git a/test/test_bfd.py b/test/test_bfd.py
new file mode 100644
index 00000000..4cb6d379
--- /dev/null
+++ b/test/test_bfd.py
@@ -0,0 +1,2591 @@
+#!/usr/bin/env python
+""" BFD tests """
+
+from __future__ import division
+import unittest
+import hashlib
+import binascii
+import time
+from struct import pack, unpack
+from random import randint, shuffle, getrandbits
+from socket import AF_INET, AF_INET6, inet_ntop
+from scapy.packet import Raw
+from scapy.layers.l2 import Ether
+from scapy.layers.inet import UDP, IP
+from scapy.layers.inet6 import IPv6
+from bfd import VppBFDAuthKey, BFD, BFDAuthType, VppBFDUDPSession, \
+ BFDDiagCode, BFDState, BFD_vpp_echo
+from framework import VppTestCase, VppTestRunner, running_extended_tests
+from vpp_pg_interface import CaptureTimeoutError, is_ipv6_misc
+from vpp_lo_interface import VppLoInterface
+from util import ppp
+from vpp_papi_provider import UnexpectedApiReturnValueError
+from vpp_ip_route import VppIpRoute, VppRoutePath, DpoProto
+
+USEC_IN_SEC = 1000000
+
+
+class AuthKeyFactory(object):
+ """Factory class for creating auth keys with unique conf key ID"""
+
+ def __init__(self):
+ self._conf_key_ids = {}
+
+ def create_random_key(self, test, auth_type=BFDAuthType.keyed_sha1):
+ """ create a random key with unique conf key id """
+ conf_key_id = randint(0, 0xFFFFFFFF)
+ while conf_key_id in self._conf_key_ids:
+ conf_key_id = randint(0, 0xFFFFFFFF)
+ self._conf_key_ids[conf_key_id] = 1
+ key = str(bytearray([randint(0, 255) for _ in range(randint(1, 20))]))
+ return VppBFDAuthKey(test=test, auth_type=auth_type,
+ conf_key_id=conf_key_id, key=key)
+
+
+@unittest.skipUnless(running_extended_tests(), "part of extended tests")
+class BFDAPITestCase(VppTestCase):
+ """Bidirectional Forwarding Detection (BFD) - API"""
+
+ pg0 = None
+ pg1 = None
+
+ @classmethod
+ def setUpClass(cls):
+ super(BFDAPITestCase, cls).setUpClass()
+
+ try:
+ cls.create_pg_interfaces(range(2))
+ for i in cls.pg_interfaces:
+ i.config_ip4()
+ i.config_ip6()
+ i.resolve_arp()
+
+ except Exception:
+ super(BFDAPITestCase, cls).tearDownClass()
+ raise
+
+ def setUp(self):
+ super(BFDAPITestCase, self).setUp()
+ self.factory = AuthKeyFactory()
+
+ 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", session.state)
+ session.remove_vpp_config()
+ session.add_vpp_config()
+ self.logger.debug("Session state is %s", 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()
+
+ with self.vapi.expect_negative_api_retval():
+ session.add_vpp_config()
+
+ session.remove_vpp_config()
+
+ def test_add_bfd6(self):
+ """ create IPv6 BFD session """
+ session = VppBFDUDPSession(
+ self, self.pg0, self.pg0.remote_ip6, af=AF_INET6)
+ session.add_vpp_config()
+ self.logger.debug("Session state is %s", session.state)
+ session.remove_vpp_config()
+ session.add_vpp_config()
+ self.logger.debug("Session state is %s", session.state)
+ session.remove_vpp_config()
+
+ def test_mod_bfd(self):
+ """ modify BFD session parameters """
+ session = VppBFDUDPSession(self, self.pg0, self.pg0.remote_ip4,
+ desired_min_tx=50000,
+ required_min_rx=10000,
+ detect_mult=1)
+ session.add_vpp_config()
+ s = session.get_bfd_udp_session_dump_entry()
+ self.assert_equal(session.desired_min_tx,
+ s.desired_min_tx,
+ "desired min transmit interval")
+ self.assert_equal(session.required_min_rx,
+ s.required_min_rx,
+ "required min receive interval")
+ self.assert_equal(session.detect_mult, s.detect_mult, "detect mult")
+ session.modify_parameters(desired_min_tx=session.desired_min_tx * 2,
+ required_min_rx=session.required_min_rx * 2,
+ detect_mult=session.detect_mult * 2)
+ s = session.get_bfd_udp_session_dump_entry()
+ self.assert_equal(session.desired_min_tx,
+ s.desired_min_tx,
+ "desired min transmit interval")
+ self.assert_equal(session.required_min_rx,
+ s.required_min_rx,
+ "required min receive interval")
+ self.assert_equal(session.detect_mult, s.detect_mult, "detect mult")
+
+ def test_add_sha1_keys(self):
+ """ add SHA1 keys """
+ key_count = 10
+ keys = [self.factory.create_random_key(
+ self) for i in range(0, key_count)]
+ for key in keys:
+ self.assertFalse(key.query_vpp_config())
+ for key in keys:
+ key.add_vpp_config()
+ for key in keys:
+ self.assertTrue(key.query_vpp_config())
+ # remove randomly
+ indexes = range(key_count)
+ shuffle(indexes)
+ removed = []
+ for i in indexes:
+ key = keys[i]
+ key.remove_vpp_config()
+ removed.append(i)
+ for j in range(key_count):
+ key = keys[j]
+ if j in removed:
+ self.assertFalse(key.query_vpp_config())
+ else:
+ self.assertTrue(key.query_vpp_config())
+ # should be removed now
+ for key in keys:
+ self.assertFalse(key.query_vpp_config())
+ # add back and remove again
+ for key in keys:
+ key.add_vpp_config()
+ for key in keys:
+ self.assertTrue(key.query_vpp_config())
+ for key in keys:
+ key.remove_vpp_config()
+ for key in keys:
+ self.assertFalse(key.query_vpp_config())
+
+ def test_add_bfd_sha1(self):
+ """ create a BFD session (SHA1) """
+ key = self.factory.create_random_key(self)
+ key.add_vpp_config()
+ session = VppBFDUDPSession(self, self.pg0, self.pg0.remote_ip4,
+ sha1_key=key)
+ session.add_vpp_config()
+ self.logger.debug("Session state is %s", session.state)
+ session.remove_vpp_config()
+ session.add_vpp_config()
+ self.logger.debug("Session state is %s", session.state)
+ session.remove_vpp_config()
+
+ def test_double_add_sha1(self):
+ """ create the same BFD session twice (negative case) (SHA1) """
+ key = self.factory.create_random_key(self)
+ key.add_vpp_config()
+ session = VppBFDUDPSession(self, self.pg0, self.pg0.remote_ip4,
+ sha1_key=key)
+ session.add_vpp_config()
+ with self.assertRaises(Exception):
+ session.add_vpp_config()
+
+ def test_add_auth_nonexistent_key(self):
+ """ create BFD session using non-existent SHA1 (negative case) """
+ session = VppBFDUDPSession(
+ self, self.pg0, self.pg0.remote_ip4,
+ sha1_key=self.factory.create_random_key(self))
+ with self.assertRaises(Exception):
+ session.add_vpp_config()
+
+ def test_shared_sha1_key(self):
+ """ share single SHA1 key between multiple BFD sessions """
+ key = self.factory.create_random_key(self)
+ key.add_vpp_config()
+ sessions = [
+ VppBFDUDPSession(self, self.pg0, self.pg0.remote_ip4,
+ sha1_key=key),
+ VppBFDUDPSession(self, self.pg0, self.pg0.remote_ip6,
+ sha1_key=key, af=AF_INET6),
+ VppBFDUDPSession(self, self.pg1, self.pg1.remote_ip4,
+ sha1_key=key),
+ VppBFDUDPSession(self, self.pg1, self.pg1.remote_ip6,
+ sha1_key=key, af=AF_INET6)]
+ for s in sessions:
+ s.add_vpp_config()
+ removed = 0
+ for s in sessions:
+ e = key.get_bfd_auth_keys_dump_entry()
+ self.assert_equal(e.use_count, len(sessions) - removed,
+ "Use count for shared key")
+ s.remove_vpp_config()
+ removed += 1
+ e = key.get_bfd_auth_keys_dump_entry()
+ self.assert_equal(e.use_count, len(sessions) - removed,
+ "Use count for shared key")
+
+ def test_activate_auth(self):
+ """ activate SHA1 authentication """
+ key = self.factory.create_random_key(self)
+ key.add_vpp_config()
+ session = VppBFDUDPSession(self, self.pg0, self.pg0.remote_ip4)
+ session.add_vpp_config()
+ session.activate_auth(key)
+
+ def test_deactivate_auth(self):
+ """ deactivate SHA1 authentication """
+ key = self.factory.create_random_key(self)
+ key.add_vpp_config()
+ session = VppBFDUDPSession(self, self.pg0, self.pg0.remote_ip4)
+ session.add_vpp_config()
+ session.activate_auth(key)
+ session.deactivate_auth()
+
+ def test_change_key(self):
+ """ change SHA1 key """
+ key1 = self.factory.create_random_key(self)
+ key2 = self.factory.create_random_key(self)
+ while key2.conf_key_id == key1.conf_key_id:
+ key2 = self.factory.create_random_key(self)
+ key1.add_vpp_config()
+ key2.add_vpp_config()
+ session = VppBFDUDPSession(self, self.pg0, self.pg0.remote_ip4,
+ sha1_key=key1)
+ session.add_vpp_config()
+ session.activate_auth(key2)
+
+
+@unittest.skipUnless(running_extended_tests(), "part of extended tests")
+class BFDTestSession(object):
+ """ BFD session as seen from test framework side """
+
+ def __init__(self, test, interface, af, detect_mult=3, sha1_key=None,
+ bfd_key_id=None, our_seq_number=None):
+ self.test = test
+ self.af = af
+ self.sha1_key = sha1_key
+ self.bfd_key_id = bfd_key_id
+ self.interface = interface
+ self.udp_sport = randint(49152, 65535)
+ if our_seq_number is None:
+ self.our_seq_number = randint(0, 40000000)
+ else:
+ self.our_seq_number = our_seq_number
+ self.vpp_seq_number = None
+ self.my_discriminator = 0
+ self.desired_min_tx = 300000
+ self.required_min_rx = 300000
+ self.required_min_echo_rx = None
+ self.detect_mult = detect_mult
+ self.diag = BFDDiagCode.no_diagnostic
+ self.your_discriminator = None
+ self.state = BFDState.down
+ self.auth_type = BFDAuthType.no_auth
+
+ def inc_seq_num(self):
+ """ increment sequence number, wrapping if needed """
+ if self.our_seq_number == 0xFFFFFFFF:
+ self.our_seq_number = 0
+ else:
+ self.our_seq_number += 1
+
+ def update(self, my_discriminator=None, your_discriminator=None,
+ desired_min_tx=None, required_min_rx=None,
+ required_min_echo_rx=None, detect_mult=None,
+ diag=None, state=None, auth_type=None):
+ """ update BFD parameters associated with session """
+ if my_discriminator is not None:
+ self.my_discriminator = my_discriminator
+ if your_discriminator is not None:
+ self.your_discriminator = your_discriminator
+ if required_min_rx is not None:
+ self.required_min_rx = required_min_rx
+ if required_min_echo_rx is not None:
+ self.required_min_echo_rx = required_min_echo_rx
+ if desired_min_tx is not None:
+ self.desired_min_tx = desired_min_tx
+ if detect_mult is not None:
+ self.detect_mult = detect_mult
+ if diag is not None:
+ self.diag = diag
+ if state is not None:
+ self.state = state
+ if auth_type is not None:
+ self.auth_type = auth_type
+
+ def fill_packet_fields(self, packet):
+ """ set packet fields with known values in packet """
+ bfd = packet[BFD]
+ if self.my_discriminator:
+ self.test.logger.debug("BFD: setting packet.my_discriminator=%s",
+ self.my_discriminator)
+ bfd.my_discriminator = self.my_discriminator
+ if self.your_discriminator:
+ self.test.logger.debug("BFD: setting packet.your_discriminator=%s",
+ self.your_discriminator)
+ bfd.your_discriminator = self.your_discriminator
+ if self.required_min_rx:
+ self.test.logger.debug(
+ "BFD: setting packet.required_min_rx_interval=%s",
+ self.required_min_rx)
+ bfd.required_min_rx_interval = self.required_min_rx
+ if self.required_min_echo_rx:
+ self.test.logger.debug(
+ "BFD: setting packet.required_min_echo_rx=%s",
+ self.required_min_echo_rx)
+ bfd.required_min_echo_rx_interval = self.required_min_echo_rx
+ if self.desired_min_tx:
+ self.test.logger.debug(
+ "BFD: setting packet.desired_min_tx_interval=%s",
+ self.desired_min_tx)
+ bfd.desired_min_tx_interval = self.desired_min_tx
+ if self.detect_mult:
+ self.test.logger.debug(
+ "BFD: setting packet.detect_mult=%s", self.detect_mult)
+ bfd.detect_mult = self.detect_mult
+ if self.diag:
+ self.test.logger.debug("BFD: setting packet.diag=%s", self.diag)
+ bfd.diag = self.diag
+ if self.state:
+ self.test.logger.debug("BFD: setting packet.state=%s", self.state)
+ bfd.state = self.state
+ if self.auth_type:
+ # this is used by a negative test-case
+ self.test.logger.debug("BFD: setting packet.auth_type=%s",
+ self.auth_type)
+ bfd.auth_type = self.auth_type
+
+ def create_packet(self):
+ """ create a BFD packet, reflecting the current state of session """
+ if self.sha1_key:
+ bfd = BFD(flags="A")
+ bfd.auth_type = self.sha1_key.auth_type
+ bfd.auth_len = BFD.sha1_auth_len
+ bfd.auth_key_id = self.bfd_key_id
+ bfd.auth_seq_num = self.our_seq_number
+ bfd.length = BFD.sha1_auth_len + BFD.bfd_pkt_len
+ else:
+ bfd = BFD()
+ if self.af == AF_INET6:
+ packet = (Ether(src=self.interface.remote_mac,
+ dst=self.interface.local_mac) /
+ IPv6(src=self.interface.remote_ip6,
+ dst=self.interface.local_ip6,
+ hlim=255) /
+ UDP(sport=self.udp_sport, dport=BFD.udp_dport) /
+ bfd)
+ else:
+ packet = (Ether(src=self.interface.remote_mac,
+ dst=self.interface.local_mac) /
+ IP(src=self.interface.remote_ip4,
+ dst=self.interface.local_ip4,
+ ttl=255) /
+ UDP(sport=self.udp_sport, dport=BFD.udp_dport) /
+ bfd)
+ self.test.logger.debug("BFD: Creating packet")
+ self.fill_packet_fields(packet)
+ if self.sha1_key:
+ hash_material = str(packet[BFD])[:32] + self.sha1_key.key + \
+ "\0" * (20 - len(self.sha1_key.key))
+ self.test.logger.debug("BFD: Calculated SHA1 hash: %s" %
+ hashlib.sha1(hash_material).hexdigest())
+ packet[BFD].auth_key_hash = hashlib.sha1(hash_material).digest()
+ return packet
+
+ def send_packet(self, packet=None, interface=None):
+ """ send packet on interface, creating the packet if needed """
+ if packet is None:
+ packet = self.create_packet()
+ if interface is None:
+ interface = self.test.pg0
+ self.test.logger.debug(ppp("Sending packet:", packet))
+ interface.add_stream(packet)
+ self.test.pg_start()
+
+ def verify_sha1_auth(self, packet):
+ """ Verify correctness of authentication in BFD layer. """
+ bfd = packet[BFD]
+ self.test.assert_equal(bfd.auth_len, 28, "Auth section length")
+ self.test.assert_equal(bfd.auth_type, self.sha1_key.auth_type,
+ BFDAuthType)
+ self.test.assert_equal(bfd.auth_key_id, self.bfd_key_id, "Key ID")
+ self.test.assert_equal(bfd.auth_reserved, 0, "Reserved")
+ if self.vpp_seq_number is None:
+ self.vpp_seq_number = bfd.auth_seq_num
+ self.test.logger.debug("Received initial sequence number: %s" %
+ self.vpp_seq_number)
+ else:
+ recvd_seq_num = bfd.auth_seq_num
+ self.test.logger.debug("Received followup sequence number: %s" %
+ recvd_seq_num)
+ if self.vpp_seq_number < 0xffffffff:
+ if self.sha1_key.auth_type == \
+ BFDAuthType.meticulous_keyed_sha1:
+ self.test.assert_equal(recvd_seq_num,
+ self.vpp_seq_number + 1,
+ "BFD sequence number")
+ else:
+ self.test.assert_in_range(recvd_seq_num,
+ self.vpp_seq_number,
+ self.vpp_seq_number + 1,
+ "BFD sequence number")
+ else:
+ if self.sha1_key.auth_type == \
+ BFDAuthType.meticulous_keyed_sha1:
+ self.test.assert_equal(recvd_seq_num, 0,
+ "BFD sequence number")
+ else:
+ self.test.assertIn(recvd_seq_num, (self.vpp_seq_number, 0),
+ "BFD sequence number not one of "
+ "(%s, 0)" % self.vpp_seq_number)
+ self.vpp_seq_number = recvd_seq_num
+ # last 20 bytes represent the hash - so replace them with the key,
+ # pad the result with zeros and hash the result
+ hash_material = bfd.original[:-20] + self.sha1_key.key + \
+ "\0" * (20 - len(self.sha1_key.key))
+ expected_hash = hashlib.sha1(hash_material).hexdigest()
+ self.test.assert_equal(binascii.hexlify(bfd.auth_key_hash),
+ expected_hash, "Auth key hash")
+
+ def verify_bfd(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.my_discriminator,
+ "BFD - your discriminator")
+ if self.sha1_key:
+ self.verify_sha1_auth(packet)
+
+
+def bfd_session_up(test):
+ """ Bring BFD session up """
+ test.logger.info("BFD: Waiting for slow hello")
+ p = wait_for_bfd_packet(test, 2)
+ old_offset = None
+ if hasattr(test, 'vpp_clock_offset'):
+ old_offset = test.vpp_clock_offset
+ test.vpp_clock_offset = time.time() - p.time
+ test.logger.debug("BFD: Calculated vpp clock offset: %s",
+ test.vpp_clock_offset)
+ if old_offset:
+ test.assertAlmostEqual(
+ old_offset, test.vpp_clock_offset, delta=0.5,
+ msg="vpp clock offset not stable (new: %s, old: %s)" %
+ (test.vpp_clock_offset, old_offset))
+ test.logger.info("BFD: Sending Init")
+ test.test_session.update(my_discriminator=randint(0, 40000000),
+ your_discriminator=p[BFD].my_discriminator,
+ state=BFDState.init)
+ if test.test_session.sha1_key and test.test_session.sha1_key.auth_type == \
+ BFDAuthType.meticulous_keyed_sha1:
+ test.test_session.inc_seq_num()
+ test.test_session.send_packet()
+ test.logger.info("BFD: Waiting for event")
+ e = test.vapi.wait_for_event(1, "bfd_udp_session_details")
+ verify_event(test, e, expected_state=BFDState.up)
+ test.logger.info("BFD: Session is Up")
+ test.test_session.update(state=BFDState.up)
+ if test.test_session.sha1_key and test.test_session.sha1_key.auth_type == \
+ BFDAuthType.meticulous_keyed_sha1:
+ test.test_session.inc_seq_num()
+ test.test_session.send_packet()
+ test.assert_equal(test.vpp_session.state, BFDState.up, BFDState)
+
+
+def bfd_session_down(test):
+ """ Bring BFD session down """
+ test.assert_equal(test.vpp_session.state, BFDState.up, BFDState)
+ test.test_session.update(state=BFDState.down)
+ if test.test_session.sha1_key and test.test_session.sha1_key.auth_type == \
+ BFDAuthType.meticulous_keyed_sha1:
+ test.test_session.inc_seq_num()
+ test.test_session.send_packet()
+ test.logger.info("BFD: Waiting for event")
+ e = test.vapi.wait_for_event(1, "bfd_udp_session_details")
+ verify_event(test, e, expected_state=BFDState.down)
+ test.logger.info("BFD: Session is Down")
+ test.assert_equal(test.vpp_session.state, BFDState.down, BFDState)
+
+
+def verify_bfd_session_config(test, session, state=None):
+ dump = session.get_bfd_udp_session_dump_entry()
+ test.assertIsNotNone(dump)
+ # since dump is not none, we have verified that sw_if_index and addresses
+ # are valid (in get_bfd_udp_session_dump_entry)
+ if state:
+ test.assert_equal(dump.state, state, "session state")
+ test.assert_equal(dump.required_min_rx, session.required_min_rx,
+ "required min rx interval")
+ test.assert_equal(dump.desired_min_tx, session.desired_min_tx,
+ "desired min tx interval")
+ test.assert_equal(dump.detect_mult, session.detect_mult,
+ "detect multiplier")
+ if session.sha1_key is None:
+ test.assert_equal(dump.is_authenticated, 0, "is_authenticated flag")
+ else:
+ test.assert_equal(dump.is_authenticated, 1, "is_authenticated flag")
+ test.assert_equal(dump.bfd_key_id, session.bfd_key_id,
+ "bfd key id")
+ test.assert_equal(dump.conf_key_id,
+ session.sha1_key.conf_key_id,
+ "config key id")
+
+
+def verify_ip(test, packet):
+ """ Verify correctness of IP layer. """
+ if test.vpp_session.af == AF_INET6:
+ ip = packet[IPv6]
+ local_ip = test.pg0.local_ip6
+ remote_ip = test.pg0.remote_ip6
+ test.assert_equal(ip.hlim, 255, "IPv6 hop limit")
+ else:
+ ip = packet[IP]
+ local_ip = test.pg0.local_ip4
+ remote_ip = test.pg0.remote_ip4
+ test.assert_equal(ip.ttl, 255, "IPv4 TTL")
+ test.assert_equal(ip.src, local_ip, "IP source address")
+ test.assert_equal(ip.dst, remote_ip, "IP destination address")
+
+
+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")
+
+
+def verify_event(test, event, expected_state):
+ """ Verify correctness of event values. """
+ e = event
+ test.logger.debug("BFD: Event: %s" % repr(e))
+ test.assert_equal(e.sw_if_index,
+ test.vpp_session.interface.sw_if_index,
+ "BFD interface index")
+ is_ipv6 = 0
+ if test.vpp_session.af == AF_INET6:
+ is_ipv6 = 1
+ test.assert_equal(e.is_ipv6, is_ipv6, "is_ipv6")
+ if test.vpp_session.af == AF_INET:
+ test.assert_equal(e.local_addr[:4], test.vpp_session.local_addr_n,
+ "Local IPv4 address")
+ test.assert_equal(e.peer_addr[:4], test.vpp_session.peer_addr_n,
+ "Peer IPv4 address")
+ else:
+ test.assert_equal(e.local_addr, test.vpp_session.local_addr_n,
+ "Local IPv6 address")
+ test.assert_equal(e.peer_addr, test.vpp_session.peer_addr_n,
+ "Peer IPv6 address")
+ test.assert_equal(e.state, expected_state, BFDState)
+
+
+def wait_for_bfd_packet(test, timeout=1, pcap_time_min=None):
+ """ wait for BFD packet and verify its correctness
+
+ :param timeout: how long to wait
+ :param pcap_time_min: ignore packets with pcap timestamp lower than this
+
+ :returns: tuple (packet, time spent waiting for packet)
+ """
+ test.logger.info("BFD: Waiting for BFD packet")
+ deadline = time.time() + timeout
+ counter = 0
+ while True:
+ counter += 1
+ # sanity check
+ test.assert_in_range(counter, 0, 100, "number of packets ignored")
+ time_left = deadline - time.time()
+ if time_left < 0:
+ raise CaptureTimeoutError("Packet did not arrive within timeout")
+ p = test.pg0.wait_for_packet(timeout=time_left)
+ test.logger.debug(ppp("BFD: Got packet:", p))
+ if pcap_time_min is not None and p.time < pcap_time_min:
+ test.logger.debug(ppp("BFD: ignoring packet (pcap time %s < "
+ "pcap time min %s):" %
+ (p.time, pcap_time_min), p))
+ else:
+ break
+ 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(test, p)
+ verify_udp(test, p)
+ test.test_session.verify_bfd(p)
+ return p
+
+
+@unittest.skipUnless(running_extended_tests(), "part of extended tests")
+class BFD4TestCase(VppTestCase):
+ """Bidirectional Forwarding Detection (BFD)"""
+
+ pg0 = None
+ vpp_clock_offset = None
+ vpp_session = None
+ test_session = None
+
+ @classmethod
+ def setUpClass(cls):
+ super(BFD4TestCase, cls).setUpClass()
+ try:
+ cls.create_pg_interfaces([0])
+ cls.create_loopback_interfaces([0])
+ cls.loopback0 = cls.lo_interfaces[0]
+ cls.loopback0.config_ip4()
+ cls.loopback0.admin_up()
+ cls.pg0.config_ip4()
+ cls.pg0.configure_ipv4_neighbors()
+ cls.pg0.admin_up()
+ cls.pg0.resolve_arp()
+
+ except Exception:
+ super(BFD4TestCase, cls).tearDownClass()
+ raise
+
+ def setUp(self):
+ super(BFD4TestCase, self).setUp()
+ self.factory = AuthKeyFactory()
+ self.vapi.want_bfd_events()
+ self.pg0.enable_capture()
+ try:
+ 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, AF_INET)
+ except:
+ self.vapi.want_bfd_events(enable_disable=0)
+ raise
+
+ def tearDown(self):
+ if not self.vpp_dead:
+ self.vapi.want_bfd_events(enable_disable=0)
+ self.vapi.collect_events() # clear the event queue
+ super(BFD4TestCase, self).tearDown()
+
+ def test_session_up(self):
+ """ bring BFD session up """
+ bfd_session_up(self)
+
+ def test_session_up_by_ip(self):
+ """ bring BFD session up - first frame looked up by address pair """
+ self.logger.info("BFD: Sending Slow control frame")
+ self.test_session.update(my_discriminator=randint(0, 40000000))
+ self.test_session.send_packet()
+ self.pg0.enable_capture()
+ p = self.pg0.wait_for_packet(1)
+ self.assert_equal(p[BFD].your_discriminator,
+ self.test_session.my_discriminator,
+ "BFD - your discriminator")
+ self.assert_equal(p[BFD].state, BFDState.init, BFDState)
+ self.test_session.update(your_discriminator=p[BFD].my_discriminator,
+ state=BFDState.up)
+ self.logger.info("BFD: Waiting for event")
+ e = self.vapi.wait_for_event(1, "bfd_udp_session_details")
+ verify_event(self, e, expected_state=BFDState.init)
+ self.logger.info("BFD: Sending Up")
+ self.test_session.send_packet()
+ self.logger.info("BFD: Waiting for event")
+ e = self.vapi.wait_for_event(1, "bfd_udp_session_details")
+ verify_event(self, e, expected_state=BFDState.up)
+ self.logger.info("BFD: Session is Up")
+ self.test_session.update(state=BFDState.up)
+ self.test_session.send_packet()
+ self.assert_equal(self.vpp_session.state, BFDState.up, BFDState)
+
+ def test_session_down(self):
+ """ bring BFD session down """
+ bfd_session_up(self)
+ bfd_session_down(self)
+
+ @unittest.skipUnless(running_extended_tests(), "part of extended tests")
+ def test_hold_up(self):
+ """ hold BFD session up """
+ bfd_session_up(self)
+ for dummy in range(self.test_session.detect_mult * 2):
+ wait_for_bfd_packet(self)
+ self.test_session.send_packet()
+ self.assert_equal(len(self.vapi.collect_events()), 0,
+ "number of bfd events")
+
+ @unittest.skipUnless(running_extended_tests(), "part of extended tests")
+ def test_slow_timer(self):
+ """ verify slow periodic control frames while session down """
+ packet_count = 3
+ self.logger.info("BFD: Waiting for %d BFD packets", packet_count)
+ prev_packet = wait_for_bfd_packet(self, 2)
+ for dummy in range(packet_count):
+ next_packet = wait_for_bfd_packet(self, 2)
+ time_diff = next_packet.time - prev_packet.time
+ # spec says the range should be <0.75, 1>, allow extra 0.05 margin
+ # to work around timing issues
+ self.assert_in_range(
+ time_diff, 0.70, 1.05, "time between slow packets")
+ prev_packet = next_packet
+
+ @unittest.skipUnless(running_extended_tests(), "part of extended tests")
+ def test_zero_remote_min_rx(self):
+ """ no packets when zero remote required min rx interval """
+ bfd_session_up(self)
+ self.test_session.update(required_min_rx=0)
+ self.test_session.send_packet()
+ for dummy in range(self.test_session.detect_mult):
+ self.sleep(self.vpp_session.required_min_rx / USEC_IN_SEC,
+ "sleep before transmitting bfd packet")
+ self.test_session.send_packet()
+ try:
+ p = wait_for_bfd_packet(self, timeout=0)
+ self.logger.error(ppp("Received unexpected packet:", p))
+ except CaptureTimeoutError:
+ pass
+ self.assert_equal(
+ len(self.vapi.collect_events()), 0, "number of bfd events")
+ self.test_session.update(required_min_rx=300000)
+ for dummy in range(3):
+ self.test_session.send_packet()
+ wait_for_bfd_packet(
+ self, timeout=self.test_session.required_min_rx / USEC_IN_SEC)
+ self.assert_equal(
+ len(self.vapi.collect_events()), 0, "number of bfd events")
+
+ @unittest.skipUnless(running_extended_tests(), "part of extended tests")
+ def test_conn_down(self):
+ """ verify session goes down after inactivity """
+ bfd_session_up(self)
+ detection_time = self.test_session.detect_mult *\
+ self.vpp_session.required_min_rx / USEC_IN_SEC
+ self.sleep(detection_time, "waiting for BFD session time-out")
+ e = self.vapi.wait_for_event(1, "bfd_udp_session_details")
+ verify_event(self, e, expected_state=BFDState.down)
+
+ @unittest.skipUnless(running_extended_tests(), "part of extended tests")
+ def test_large_required_min_rx(self):
+ """ large remote required min rx interval """
+ bfd_session_up(self)
+ p = wait_for_bfd_packet(self)
+ interval = 3000000
+ self.test_session.update(required_min_rx=interval)
+ self.test_session.send_packet()
+ time_mark = time.time()
+ count = 0
+ # busy wait here, trying to collect a packet or event, vpp is not
+ # allowed to send packets and the session will timeout first - so the
+ # Up->Down event must arrive before any packets do
+ while time.time() < time_mark + interval / USEC_IN_SEC:
+ try:
+ p = wait_for_bfd_packet(self, timeout=0)
+ # if vpp managed to send a packet before we did the session
+ # session update, then that's fine, ignore it
+ if p.time < time_mark - self.vpp_clock_offset:
+ continue
+ self.logger.error(ppp("Received unexpected packet:", p))
+ count += 1
+ except CaptureTimeoutError:
+ pass
+ events = self.vapi.collect_events()
+ if len(events) > 0:
+ verify_event(self, events[0], BFDState.down)
+ break
+ self.assert_equal(count, 0, "number of packets received")
+
+ @unittest.skipUnless(running_extended_tests(), "part of extended tests")
+ def test_immediate_remote_min_rx_reduction(self):
+ """ immediately honor remote required min rx reduction """
+ self.vpp_session.remove_vpp_config()
+ self.vpp_session = VppBFDUDPSession(
+ self, self.pg0, self.pg0.remote_ip4, desired_min_tx=10000)
+ self.pg0.enable_capture()
+ self.vpp_session.add_vpp_config()
+ self.test_session.update(desired_min_tx=1000000,
+ required_min_rx=1000000)
+ bfd_session_up(self)
+ reference_packet = wait_for_bfd_packet(self)
+ time_mark = time.time()
+ interval = 300000
+ self.test_session.update(required_min_rx=interval)
+ self.test_session.send_packet()
+ extra_time = time.time() - time_mark
+ p = wait_for_bfd_packet(self)
+ # first packet is allowed to be late by time we spent doing the update
+ # calculated in extra_time
+ self.assert_in_range(p.time - reference_packet.time,
+ .95 * 0.75 * interval / USEC_IN_SEC,
+ 1.05 * interval / USEC_IN_SEC + extra_time,
+ "time between BFD packets")
+ reference_packet = p
+ for dummy in range(3):
+ p = wait_for_bfd_packet(self)
+ diff = p.time - reference_packet.time
+ self.assert_in_range(diff, .95 * .75 * interval / USEC_IN_SEC,
+ 1.05 * interval / USEC_IN_SEC,
+ "time between BFD packets")
+ reference_packet = p
+
+ @unittest.skipUnless(running_extended_tests(), "part of extended tests")
+ def test_modify_req_min_rx_double(self):
+ """ modify session - double required min rx """
+ bfd_session_up(self)
+ p = wait_for_bfd_packet(self)
+ self.test_session.update(desired_min_tx=10000,
+ required_min_rx=10000)
+ self.test_session.send_packet()
+ # double required min rx
+ self.vpp_session.modify_parameters(
+ required_min_rx=2 * self.vpp_session.required_min_rx)
+ p = wait_for_bfd_packet(
+ self, pcap_time_min=time.time() - self.vpp_clock_offset)
+ # poll bit needs to be set
+ self.assertIn("P", p.sprintf("%BFD.flags%"),
+ "Poll bit not set in BFD packet")
+ # finish poll sequence with final packet
+ final = self.test_session.create_packet()
+ final[BFD].flags = "F"
+ timeout = self.test_session.detect_mult * \
+ max(self.test_session.desired_min_tx,
+ self.vpp_session.required_min_rx) / USEC_IN_SEC
+ self.test_session.send_packet(final)
+ time_mark = time.time()
+ e = self.vapi.wait_for_event(2 * timeout, "bfd_udp_session_details")
+ verify_event(self, e, expected_state=BFDState.down)
+ time_to_event = time.time() - time_mark
+ self.assert_in_range(time_to_event, .9 * timeout,
+ 1.1 * timeout, "session timeout")
+
+ @unittest.skipUnless(running_extended_tests(), "part of extended tests")
+ def test_modify_req_min_rx_halve(self):
+ """ modify session - halve required min rx """
+ self.vpp_session.modify_parameters(
+ required_min_rx=2 * self.vpp_session.required_min_rx)
+ bfd_session_up(self)
+ p = wait_for_bfd_packet(self)
+ self.test_session.update(desired_min_tx=10000,
+ required_min_rx=10000)
+ self.test_session.send_packet()
+ p = wait_for_bfd_packet(
+ self, pcap_time_min=time.time() - self.vpp_clock_offset)
+ # halve required min rx
+ old_required_min_rx = self.vpp_session.required_min_rx
+ self.vpp_session.modify_parameters(
+ required_min_rx=0.5 * self.vpp_session.required_min_rx)
+ # now we wait 0.8*3*old-req-min-rx and the session should still be up
+ self.sleep(0.8 * self.vpp_session.detect_mult *
+ old_required_min_rx / USEC_IN_SEC,
+ "wait before finishing poll sequence")
+ self.assert_equal(len(self.vapi.collect_events()), 0,
+ "number of bfd events")
+ p = wait_for_bfd_packet(self)
+ # poll bit needs to be set
+ self.assertIn("P", p.sprintf("%BFD.flags%"),
+ "Poll bit not set in BFD packet")
+ # finish poll sequence with final packet
+ final = self.test_session.create_packet()
+ final[BFD].flags = "F"
+ self.test_session.send_packet(final)
+ # now the session should time out under new conditions
+ detection_time = self.test_session.detect_mult *\
+ self.vpp_session.required_min_rx / USEC_IN_SEC
+ before = time.time()
+ e = self.vapi.wait_for_event(
+ 2 * detection_time, "bfd_udp_session_details")
+ after = time.time()
+ self.assert_in_range(after - before,
+ 0.9 * detection_time,
+ 1.1 * detection_time,
+ "time before bfd session goes down")
+ verify_event(self, e, expected_state=BFDState.down)
+
+ @unittest.skipUnless(running_extended_tests(), "part of extended tests")
+ def test_modify_detect_mult(self):
+ """ modify detect multiplier """
+ bfd_session_up(self)
+ p = wait_for_bfd_packet(self)
+ self.vpp_session.modify_parameters(detect_mult=1)
+ p = wait_for_bfd_packet(
+ self, pcap_time_min=time.time() - self.vpp_clock_offset)
+ self.assert_equal(self.vpp_session.detect_mult,
+ p[BFD].detect_mult,
+ "detect mult")
+ # poll bit must not be set
+ self.assertNotIn("P", p.sprintf("%BFD.flags%"),
+ "Poll bit not set in BFD packet")
+ self.vpp_session.modify_parameters(detect_mult=10)
+ p = wait_for_bfd_packet(
+ self, pcap_time_min=time.time() - self.vpp_clock_offset)
+ self.assert_equal(self.vpp_session.detect_mult,
+ p[BFD].detect_mult,
+ "detect mult")
+ # poll bit must not be set
+ self.assertNotIn("P", p.sprintf("%BFD.flags%"),
+ "Poll bit not set in BFD packet")
+
+ @unittest.skipUnless(running_extended_tests(), "part of extended tests")
+ def test_queued_poll(self):
+ """ test poll sequence queueing """
+ bfd_session_up(self)
+ p = wait_for_bfd_packet(self)
+ self.vpp_session.modify_parameters(
+ required_min_rx=2 * self.vpp_session.required_min_rx)
+ p = wait_for_bfd_packet(self)
+ poll_sequence_start = time.time()
+ poll_sequence_length_min = 0.5
+ send_final_after = time.time() + poll_sequence_length_min
+ # poll bit needs to be set
+ self.assertIn("P", p.sprintf("%BFD.flags%"),
+ "Poll bit not set in BFD packet")
+ self.assert_equal(p[BFD].required_min_rx_interval,
+ self.vpp_session.required_min_rx,
+ "BFD required min rx interval")
+ self.vpp_session.modify_parameters(
+ required_min_rx=2 * self.vpp_session.required_min_rx)
+ # 2nd poll sequence should be queued now
+ # don't send the reply back yet, wait for some time to emulate
+ # longer round-trip time
+ packet_count = 0
+ while time.time() < send_final_after:
+ self.test_session.send_packet()
+ p = wait_for_bfd_packet(self)
+ self.assert_equal(len(self.vapi.collect_events()), 0,
+ "number of bfd events")
+ self.assert_equal(p[BFD].required_min_rx_interval,
+ self.vpp_session.required_min_rx,
+ "BFD required min rx interval")
+ packet_count += 1
+ # poll bit must be set
+ self.assertIn("P", p.sprintf("%BFD.flags%"),
+ "Poll bit not set in BFD packet")
+ final = self.test_session.create_packet()
+ final[BFD].flags = "F"
+ self.test_session.send_packet(final)
+ # finish 1st with final
+ poll_sequence_length = time.time() - poll_sequence_start
+ # vpp must wait for some time before starting new poll sequence
+ poll_no_2_started = False
+ for dummy in range(2 * packet_count):
+ p = wait_for_bfd_packet(self)
+ self.assert_equal(len(self.vapi.collect_events()), 0,
+ "number of bfd events")
+ if "P" in p.sprintf("%BFD.flags%"):
+ poll_no_2_started = True
+ if time.time() < poll_sequence_start + poll_sequence_length:
+ raise Exception("VPP started 2nd poll sequence too soon")
+ final = self.test_session.create_packet()
+ final[BFD].flags = "F"
+ self.test_session.send_packet(final)
+ break
+ else:
+ self.test_session.send_packet()
+ self.assertTrue(poll_no_2_started, "2nd poll sequence not performed")
+ # finish 2nd with final
+ final = self.test_session.create_packet()
+ final[BFD].flags = "F"
+ self.test_session.send_packet(final)
+ p = wait_for_bfd_packet(self)
+ # poll bit must not be set
+ self.assertNotIn("P", p.sprintf("%BFD.flags%"),
+ "Poll bit set in BFD packet")
+
+ @unittest.skipUnless(running_extended_tests(), "part of extended tests")
+ def test_poll_response(self):
+ """ test correct response to control frame with poll bit set """
+ bfd_session_up(self)
+ poll = self.test_session.create_packet()
+ poll[BFD].flags = "P"
+ self.test_session.send_packet(poll)
+ final = wait_for_bfd_packet(
+ self, pcap_time_min=time.time() - self.vpp_clock_offset)
+ self.assertIn("F", final.sprintf("%BFD.flags%"))
+
+ @unittest.skipUnless(running_extended_tests(), "part of extended tests")
+ def test_no_periodic_if_remote_demand(self):
+ """ no periodic frames outside poll sequence if remote demand set """
+ bfd_session_up(self)
+ demand = self.test_session.create_packet()
+ demand[BFD].flags = "D"
+ self.test_session.send_packet(demand)
+ transmit_time = 0.9 \
+ * max(self.vpp_session.required_min_rx,
+ self.test_session.desired_min_tx) \
+ / USEC_IN_SEC
+ count = 0
+ for dummy in range(self.test_session.detect_mult * 2):
+ time.sleep(transmit_time)
+ self.test_session.send_packet(demand)
+ try:
+ p = wait_for_bfd_packet(self, timeout=0)
+ self.logger.error(ppp("Received unexpected packet:", p))
+ count += 1
+ except CaptureTimeoutError:
+ pass
+ events = self.vapi.collect_events()
+ for e in events:
+ self.logger.error("Received unexpected event: %s", e)
+ self.assert_equal(count, 0, "number of packets received")
+ self.assert_equal(len(events), 0, "number of events received")
+
+ def test_echo_looped_back(self):
+ """ echo packets looped back """
+ # don't need a session in this case..
+ self.vpp_session.remove_vpp_config()
+ self.pg0.enable_capture()
+ echo_packet_count = 10
+ # random source port low enough to increment a few times..
+ udp_sport_tx = randint(1, 50000)
+ udp_sport_rx = udp_sport_tx
+ echo_packet = (Ether(src=self.pg0.remote_mac,
+ dst=self.pg0.local_mac) /
+ IP(src=self.pg0.remote_ip4,
+ dst=self.pg0.remote_ip4) /
+ UDP(dport=BFD.udp_dport_echo) /
+ Raw("this should be looped back"))
+ for dummy in range(echo_packet_count):
+ self.sleep(.01, "delay between echo packets")
+ echo_packet[UDP].sport = udp_sport_tx
+ udp_sport_tx += 1
+ self.logger.debug(ppp("Sending packet:", echo_packet))
+ self.pg0.add_stream(echo_packet)
+ self.pg_start()
+ for dummy in range(echo_packet_count):
+ p = self.pg0.wait_for_packet(1)
+ self.logger.debug(ppp("Got packet:", p))
+ ether = p[Ether]
+ self.assert_equal(self.pg0.remote_mac,
+ ether.dst, "Destination MAC")
+ self.assert_equal(self.pg0.local_mac, ether.src, "Source MAC")
+ ip = p[IP]
+ self.assert_equal(self.pg0.remote_ip4, ip.dst, "Destination IP")
+ self.assert_equal(self.pg0.remote_ip4, ip.src, "Destination IP")
+ udp = p[UDP]
+ self.assert_equal(udp.dport, BFD.udp_dport_echo,
+ "UDP destination port")
+ self.assert_equal(udp.sport, udp_sport_rx, "UDP source port")
+ udp_sport_rx += 1
+ # need to compare the hex payload here, otherwise BFD_vpp_echo
+ # gets in way
+ self.assertEqual(str(p[UDP].payload),
+ str(echo_packet[UDP].payload),
+ "Received packet is not the echo packet sent")
+ self.assert_equal(udp_sport_tx, udp_sport_rx, "UDP source port (== "
+ "ECHO packet identifier for test purposes)")
+
+ def test_echo(self):
+ """ echo function """
+ bfd_session_up(self)
+ self.test_session.update(required_min_echo_rx=150000)
+ self.test_session.send_packet()
+ detection_time = self.test_session.detect_mult *\
+ self.vpp_session.required_min_rx / USEC_IN_SEC
+ # echo shouldn't work without echo source set
+ for dummy in range(10):
+ sleep = self.vpp_session.required_min_rx / USEC_IN_SEC
+ self.sleep(sleep, "delay before sending bfd packet")
+ self.test_session.send_packet()
+ p = wait_for_bfd_packet(
+ self, pcap_time_min=time.time() - self.vpp_clock_offset)
+ self.assert_equal(p[BFD].required_min_rx_interval,
+ self.vpp_session.required_min_rx,
+ "BFD required min rx interval")
+ self.test_session.send_packet()
+ self.vapi.bfd_udp_set_echo_source(self.loopback0.sw_if_index)
+ echo_seen = False
+ # should be turned on - loopback echo packets
+ for dummy in range(3):
+ loop_until = time.time() + 0.75 * detection_time
+ while time.time() < loop_until:
+ p = self.pg0.wait_for_packet(1)
+ self.logger.debug(ppp("Got packet:", p))
+ if p[UDP].dport == BFD.udp_dport_echo:
+ self.assert_equal(
+ p[IP].dst, self.pg0.local_ip4, "BFD ECHO dst IP")
+ self.assertNotEqual(p[IP].src, self.loopback0.local_ip4,
+ "BFD ECHO src IP equal to loopback IP")
+ self.logger.debug(ppp("Looping back packet:", p))
+ self.assert_equal(p[Ether].dst, self.pg0.remote_mac,
+ "ECHO packet destination MAC address")
+ p[Ether].dst = self.pg0.local_mac
+ self.pg0.add_stream(p)
+ self.pg_start()
+ echo_seen = True
+ elif p.haslayer(BFD):
+ if echo_seen:
+ self.assertGreaterEqual(
+ p[BFD].required_min_rx_interval,
+ 1000000)
+ if "P" in p.sprintf("%BFD.flags%"):
+ final = self.test_session.create_packet()
+ final[BFD].flags = "F"
+ self.test_session.send_packet(final)
+ else:
+ raise Exception(ppp("Received unknown packet:", p))
+
+ self.assert_equal(len(self.vapi.collect_events()), 0,
+ "number of bfd events")
+ self.test_session.send_packet()
+ self.assertTrue(echo_seen, "No echo packets received")
+
+ @unittest.skipUnless(running_extended_tests(), "part of extended tests")
+ def test_echo_fail(self):
+ """ session goes down if echo function fails """
+ bfd_session_up(self)
+ self.test_session.update(required_min_echo_rx=150000)
+ self.test_session.send_packet()
+ detection_time = self.test_session.detect_mult *\
+ self.vpp_session.required_min_rx / USEC_IN_SEC
+ self.vapi.bfd_udp_set_echo_source(self.loopback0.sw_if_index)
+ # echo function should be used now, but we will drop the echo packets
+ verified_diag = False
+ for dummy in range(3):
+ loop_until = time.time() + 0.75 * detection_time
+ while time.time() < loop_until:
+ p = self.pg0.wait_for_packet(1)
+ self.logger.debug(ppp("Got packet:", p))
+ if p[UDP].dport == BFD.udp_dport_echo:
+ # dropped
+ pass
+ elif p.haslayer(BFD):
+ if "P" in p.sprintf("%BFD.flags%"):
+ self.assertGreaterEqual(
+ p[BFD].required_min_rx_interval,
+ 1000000)
+ final = self.test_session.create_packet()
+ final[BFD].flags = "F"
+ self.test_session.send_packet(final)
+ if p[BFD].state == BFDState.down:
+ self.assert_equal(p[BFD].diag,
+ BFDDiagCode.echo_function_failed,
+ BFDDiagCode)
+ verified_diag = True
+ else:
+ raise Exception(ppp("Received unknown packet:", p))
+ self.test_session.send_packet()
+ events = self.vapi.collect_events()
+ self.assert_equal(len(events), 1, "number of bfd events")
+ self.assert_equal(events[0].state, BFDState.down, BFDState)
+ self.assertTrue(verified_diag, "Incorrect diagnostics code received")
+
+ @unittest.skipUnless(running_extended_tests(), "part of extended tests")
+ def test_echo_stop(self):
+ """ echo function stops if peer sets required min echo rx zero """
+ bfd_session_up(self)
+ self.test_session.update(required_min_echo_rx=150000)
+ self.test_session.send_packet()
+ self.vapi.bfd_udp_set_echo_source(self.loopback0.sw_if_index)
+ # wait for first echo packet
+ while True:
+ p = self.pg0.wait_for_packet(1)
+ self.logger.debug(ppp("Got packet:", p))
+ if p[UDP].dport == BFD.udp_dport_echo:
+ self.logger.debug(ppp("Looping back packet:", p))
+ p[Ether].dst = self.pg0.local_mac
+ self.pg0.add_stream(p)
+ self.pg_start()
+ break
+ elif p.haslayer(BFD):
+ # ignore BFD
+ pass
+ else:
+ raise Exception(ppp("Received unknown packet:", p))
+ self.test_session.update(required_min_echo_rx=0)
+ self.test_session.send_packet()
+ # echo packets shouldn't arrive anymore
+ for dummy in range(5):
+ wait_for_bfd_packet(
+ self, pcap_time_min=time.time() - self.vpp_clock_offset)
+ self.test_session.send_packet()
+ events = self.vapi.collect_events()
+ self.assert_equal(len(events), 0, "number of bfd events")
+
+ @unittest.skipUnless(running_extended_tests(), "part of extended tests")
+ def test_echo_source_removed(self):
+ """ echo function stops if echo source is removed """
+ bfd_session_up(self)
+ self.test_session.update(required_min_echo_rx=150000)
+ self.test_session.send_packet()
+ self.vapi.bfd_udp_set_echo_source(self.loopback0.sw_if_index)
+ # wait for first echo packet
+ while True:
+ p = self.pg0.wait_for_packet(1)
+ self.logger.debug(ppp("Got packet:", p))
+ if p[UDP].dport == BFD.udp_dport_echo:
+ self.logger.debug(ppp("Looping back packet:", p))
+ p[Ether].dst = self.pg0.local_mac
+ self.pg0.add_stream(p)
+ self.pg_start()
+ break
+ elif p.haslayer(BFD):
+ # ignore BFD
+ pass
+ else:
+ raise Exception(ppp("Received unknown packet:", p))
+ self.vapi.bfd_udp_del_echo_source()
+ self.test_session.send_packet()
+ # echo packets shouldn't arrive anymore
+ for dummy in range(5):
+ wait_for_bfd_packet(
+ self, pcap_time_min=time.time() - self.vpp_clock_offset)
+ self.test_session.send_packet()
+ events = self.vapi.collect_events()
+ self.assert_equal(len(events), 0, "number of bfd events")
+
+ @unittest.skipUnless(running_extended_tests(), "part of extended tests")
+ def test_stale_echo(self):
+ """ stale echo packets don't keep a session up """
+ bfd_session_up(self)
+ self.test_session.update(required_min_echo_rx=150000)
+ self.vapi.bfd_udp_set_echo_source(self.loopback0.sw_if_index)
+ self.test_session.send_packet()
+ # should be turned on - loopback echo packets
+ echo_packet = None
+ timeout_at = None
+ timeout_ok = False
+ for dummy in range(10 * self.vpp_session.detect_mult):
+ p = self.pg0.wait_for_packet(1)
+ if p[UDP].dport == BFD.udp_dport_echo:
+ if echo_packet is None:
+ self.logger.debug(ppp("Got first echo packet:", p))
+ echo_packet = p
+ timeout_at = time.time() + self.vpp_session.detect_mult * \
+ self.test_session.required_min_echo_rx / USEC_IN_SEC
+ else:
+ self.logger.debug(ppp("Got followup echo packet:", p))
+ self.logger.debug(ppp("Looping back first echo packet:", p))
+ echo_packet[Ether].dst = self.pg0.local_mac
+ self.pg0.add_stream(echo_packet)
+ self.pg_start()
+ elif p.haslayer(BFD):
+ self.logger.debug(ppp("Got packet:", p))
+ if "P" in p.sprintf("%BFD.flags%"):
+ final = self.test_session.create_packet()
+ final[BFD].flags = "F"
+ self.test_session.send_packet(final)
+ if p[BFD].state == BFDState.down:
+ self.assertIsNotNone(
+ timeout_at,
+ "Session went down before first echo packet received")
+ now = time.time()
+ self.assertGreaterEqual(
+ now, timeout_at,
+ "Session timeout at %s, but is expected at %s" %
+ (now, timeout_at))
+ self.assert_equal(p[BFD].diag,
+ BFDDiagCode.echo_function_failed,
+ BFDDiagCode)
+ events = self.vapi.collect_events()
+ self.assert_equal(len(events), 1, "number of bfd events")
+ self.assert_equal(events[0].state, BFDState.down, BFDState)
+ timeout_ok = True
+ break
+ else:
+ raise Exception(ppp("Received unknown packet:", p))
+ self.test_session.send_packet()
+ self.assertTrue(timeout_ok, "Expected timeout event didn't occur")
+
+ @unittest.skipUnless(running_extended_tests(), "part of extended tests")
+ def test_invalid_echo_checksum(self):
+ """ echo packets with invalid checksum don't keep a session up """
+ bfd_session_up(self)
+ self.test_session.update(required_min_echo_rx=150000)
+ self.vapi.bfd_udp_set_echo_source(self.loopback0.sw_if_index)
+ self.test_session.send_packet()
+ # should be turned on - loopback echo packets
+ timeout_at = None
+ timeout_ok = False
+ for dummy in range(10 * self.vpp_session.detect_mult):
+ p = self.pg0.wait_for_packet(1)
+ if p[UDP].dport == BFD.udp_dport_echo:
+ self.logger.debug(ppp("Got echo packet:", p))
+ if timeout_at is None:
+ timeout_at = time.time() + self.vpp_session.detect_mult * \
+ self.test_session.required_min_echo_rx / USEC_IN_SEC
+ p[BFD_vpp_echo].checksum = getrandbits(64)
+ p[Ether].dst = self.pg0.local_mac
+ self.logger.debug(ppp("Looping back modified echo packet:", p))
+ self.pg0.add_stream(p)
+ self.pg_start()
+ elif p.haslayer(BFD):
+ self.logger.debug(ppp("Got packet:", p))
+ if "P" in p.sprintf("%BFD.flags%"):
+ final = self.test_session.create_packet()
+ final[BFD].flags = "F"
+ self.test_session.send_packet(final)
+ if p[BFD].state == BFDState.down:
+ self.assertIsNotNone(
+ timeout_at,
+ "Session went down before first echo packet received")
+ now = time.time()
+ self.assertGreaterEqual(
+ now, timeout_at,
+ "Session timeout at %s, but is expected at %s" %
+ (now, timeout_at))
+ self.assert_equal(p[BFD].diag,
+ BFDDiagCode.echo_function_failed,
+ BFDDiagCode)
+ events = self.vapi.collect_events()
+ self.assert_equal(len(events), 1, "number of bfd events")
+ self.assert_equal(events[0].state, BFDState.down, BFDState)
+ timeout_ok = True
+ break
+ else:
+ raise Exception(ppp("Received unknown packet:", p))
+ self.test_session.send_packet()
+ self.assertTrue(timeout_ok, "Expected timeout event didn't occur")
+
+ @unittest.skipUnless(running_extended_tests(), "part of extended tests")
+ def test_admin_up_down(self):
+ """ put session admin-up and admin-down """
+ bfd_session_up(self)
+ self.vpp_session.admin_down()
+ self.pg0.enable_capture()
+ e = self.vapi.wait_for_event(1, "bfd_udp_session_details")
+ verify_event(self, e, expected_state=BFDState.admin_down)
+ for dummy in range(2):
+ p = wait_for_bfd_packet(self)
+ self.assert_equal(p[BFD].state, BFDState.admin_down, BFDState)
+ # try to bring session up - shouldn't be possible
+ self.test_session.update(state=BFDState.init)
+ self.test_session.send_packet()
+ for dummy in range(2):
+ p = wait_for_bfd_packet(self)
+ self.assert_equal(p[BFD].state, BFDState.admin_down, BFDState)
+ self.vpp_session.admin_up()
+ self.test_session.update(state=BFDState.down)
+ e = self.vapi.wait_for_event(1, "bfd_udp_session_details")
+ verify_event(self, e, expected_state=BFDState.down)
+ p = wait_for_bfd_packet(
+ self, pcap_time_min=time.time() - self.vpp_clock_offset)
+ self.assert_equal(p[BFD].state, BFDState.down, BFDState)
+ self.test_session.send_packet()
+ p = wait_for_bfd_packet(
+ self, pcap_time_min=time.time() - self.vpp_clock_offset)
+ self.assert_equal(p[BFD].state, BFDState.init, BFDState)
+ e = self.vapi.wait_for_event(1, "bfd_udp_session_details")
+ verify_event(self, e, expected_state=BFDState.init)
+ self.test_session.update(state=BFDState.up)
+ self.test_session.send_packet()
+ p = wait_for_bfd_packet(
+ self, pcap_time_min=time.time() - self.vpp_clock_offset)
+ self.assert_equal(p[BFD].state, BFDState.up, BFDState)
+ e = self.vapi.wait_for_event(1, "bfd_udp_session_details")
+ verify_event(self, e, expected_state=BFDState.up)
+
+ @unittest.skipUnless(running_extended_tests(), "part of extended tests")
+ def test_config_change_remote_demand(self):
+ """ configuration change while peer in demand mode """
+ bfd_session_up(self)
+ demand = self.test_session.create_packet()
+ demand[BFD].flags = "D"
+ self.test_session.send_packet(demand)
+ self.vpp_session.modify_parameters(
+ required_min_rx=2 * self.vpp_session.required_min_rx)
+ p = wait_for_bfd_packet(
+ self, pcap_time_min=time.time() - self.vpp_clock_offset)
+ # poll bit must be set
+ self.assertIn("P", p.sprintf("%BFD.flags%"), "Poll bit not set")
+ # terminate poll sequence
+ final = self.test_session.create_packet()
+ final[BFD].flags = "D+F"
+ self.test_session.send_packet(final)
+ # vpp should be quiet now again
+ transmit_time = 0.9 \
+ * max(self.vpp_session.required_min_rx,
+ self.test_session.desired_min_tx) \
+ / USEC_IN_SEC
+ count = 0
+ for dummy in range(self.test_session.detect_mult * 2):
+ time.sleep(transmit_time)
+ self.test_session.send_packet(demand)
+ try:
+ p = wait_for_bfd_packet(self, timeout=0)
+ self.logger.error(ppp("Received unexpected packet:", p))
+ count += 1
+ except CaptureTimeoutError:
+ pass
+ events = self.vapi.collect_events()
+ for e in events:
+ self.logger.error("Received unexpected event: %s", e)
+ self.assert_equal(count, 0, "number of packets received")
+ self.assert_equal(len(events), 0, "number of events received")
+
+ def test_intf_deleted(self):
+ """ interface with bfd session deleted """
+ intf = VppLoInterface(self, 0)
+ intf.config_ip4()
+ intf.admin_up()
+ sw_if_index = intf.sw_if_index
+ vpp_session = VppBFDUDPSession(self, intf, intf.remote_ip4)
+ vpp_session.add_vpp_config()
+ vpp_session.admin_up()
+ intf.remove_vpp_config()
+ e = self.vapi.wait_for_event(1, "bfd_udp_session_details")
+ self.assert_equal(e.sw_if_index, sw_if_index, "sw_if_index")
+ self.assertFalse(vpp_session.query_vpp_config())
+
+
+@unittest.skipUnless(running_extended_tests(), "part of extended tests")
+class BFD6TestCase(VppTestCase):
+ """Bidirectional Forwarding Detection (BFD) (IPv6) """
+
+ pg0 = None
+ vpp_clock_offset = None
+ vpp_session = None
+ test_session = None
+
+ @classmethod
+ def setUpClass(cls):
+ super(BFD6TestCase, cls).setUpClass()
+ try:
+ cls.create_pg_interfaces([0])
+ cls.pg0.config_ip6()
+ cls.pg0.configure_ipv6_neighbors()
+ cls.pg0.admin_up()
+ cls.pg0.resolve_ndp()
+ cls.create_loopback_interfaces([0])
+ cls.loopback0 = cls.lo_interfaces[0]
+ cls.loopback0.config_ip6()
+ cls.loopback0.admin_up()
+
+ except Exception:
+ super(BFD6TestCase, cls).tearDownClass()
+ raise
+
+ def setUp(self):
+ super(BFD6TestCase, self).setUp()
+ self.factory = AuthKeyFactory()
+ self.vapi.want_bfd_events()
+ self.pg0.enable_capture()
+ try:
+ self.vpp_session = VppBFDUDPSession(self, self.pg0,
+ self.pg0.remote_ip6,
+ af=AF_INET6)
+ self.vpp_session.add_vpp_config()
+ self.vpp_session.admin_up()
+ self.test_session = BFDTestSession(self, self.pg0, AF_INET6)
+ self.logger.debug(self.vapi.cli("show adj nbr"))
+ except:
+ self.vapi.want_bfd_events(enable_disable=0)
+ raise
+
+ def tearDown(self):
+ if not self.vpp_dead:
+ self.vapi.want_bfd_events(enable_disable=0)
+ self.vapi.collect_events() # clear the event queue
+ super(BFD6TestCase, self).tearDown()
+
+ def test_session_up(self):
+ """ bring BFD session up """
+ bfd_session_up(self)
+
+ def test_session_up_by_ip(self):
+ """ bring BFD session up - first frame looked up by address pair """
+ self.logger.info("BFD: Sending Slow control frame")
+ self.test_session.update(my_discriminator=randint(0, 40000000))
+ self.test_session.send_packet()
+ self.pg0.enable_capture()
+ p = self.pg0.wait_for_packet(1)
+ self.assert_equal(p[BFD].your_discriminator,
+ self.test_session.my_discriminator,
+ "BFD - your discriminator")
+ self.assert_equal(p[BFD].state, BFDState.init, BFDState)
+ self.test_session.update(your_discriminator=p[BFD].my_discriminator,
+ state=BFDState.up)
+ self.logger.info("BFD: Waiting for event")
+ e = self.vapi.wait_for_event(1, "bfd_udp_session_details")
+ verify_event(self, e, expected_state=BFDState.init)
+ self.logger.info("BFD: Sending Up")
+ self.test_session.send_packet()
+ self.logger.info("BFD: Waiting for event")
+ e = self.vapi.wait_for_event(1, "bfd_udp_session_details")
+ verify_event(self, e, expected_state=BFDState.up)
+ self.logger.info("BFD: Session is Up")
+ self.test_session.update(state=BFDState.up)
+ self.test_session.send_packet()
+ self.assert_equal(self.vpp_session.state, BFDState.up, BFDState)
+
+ @unittest.skipUnless(running_extended_tests(), "part of extended tests")
+ def test_hold_up(self):
+ """ hold BFD session up """
+ bfd_session_up(self)
+ for dummy in range(self.test_session.detect_mult * 2):
+ wait_for_bfd_packet(self)
+ self.test_session.send_packet()
+ self.assert_equal(len(self.vapi.collect_events()), 0,
+ "number of bfd events")
+ self.assert_equal(self.vpp_session.state, BFDState.up, BFDState)
+
+ def test_echo_looped_back(self):
+ """ echo packets looped back """
+ # don't need a session in this case..
+ self.vpp_session.remove_vpp_config()
+ self.pg0.enable_capture()
+ echo_packet_count = 10
+ # random source port low enough to increment a few times..
+ udp_sport_tx = randint(1, 50000)
+ udp_sport_rx = udp_sport_tx
+ echo_packet = (Ether(src=self.pg0.remote_mac,
+ dst=self.pg0.local_mac) /
+ IPv6(src=self.pg0.remote_ip6,
+ dst=self.pg0.remote_ip6) /
+ UDP(dport=BFD.udp_dport_echo) /
+ Raw("this should be looped back"))
+ for dummy in range(echo_packet_count):
+ self.sleep(.01, "delay between echo packets")
+ echo_packet[UDP].sport = udp_sport_tx
+ udp_sport_tx += 1
+ self.logger.debug(ppp("Sending packet:", echo_packet))
+ self.pg0.add_stream(echo_packet)
+ self.pg_start()
+ for dummy in range(echo_packet_count):
+ p = self.pg0.wait_for_packet(1)
+ self.logger.debug(ppp("Got packet:", p))
+ ether = p[Ether]
+ self.assert_equal(self.pg0.remote_mac,
+ ether.dst, "Destination MAC")
+ self.assert_equal(self.pg0.local_mac, ether.src, "Source MAC")
+ ip = p[IPv6]
+ self.assert_equal(self.pg0.remote_ip6, ip.dst, "Destination IP")
+ self.assert_equal(self.pg0.remote_ip6, ip.src, "Destination IP")
+ udp = p[UDP]
+ self.assert_equal(udp.dport, BFD.udp_dport_echo,
+ "UDP destination port")
+ self.assert_equal(udp.sport, udp_sport_rx, "UDP source port")
+ udp_sport_rx += 1
+ # need to compare the hex payload here, otherwise BFD_vpp_echo
+ # gets in way
+ self.assertEqual(str(p[UDP].payload),
+ str(echo_packet[UDP].payload),
+ "Received packet is not the echo packet sent")
+ self.assert_equal(udp_sport_tx, udp_sport_rx, "UDP source port (== "
+ "ECHO packet identifier for test purposes)")
+ self.assert_equal(udp_sport_tx, udp_sport_rx, "UDP source port (== "
+ "ECHO packet identifier for test purposes)")
+
+ def test_echo(self):
+ """ echo function """
+ bfd_session_up(self)
+ self.test_session.update(required_min_echo_rx=150000)
+ self.test_session.send_packet()
+ detection_time = self.test_session.detect_mult *\
+ self.vpp_session.required_min_rx / USEC_IN_SEC
+ # echo shouldn't work without echo source set
+ for dummy in range(10):
+ sleep = self.vpp_session.required_min_rx / USEC_IN_SEC
+ self.sleep(sleep, "delay before sending bfd packet")
+ self.test_session.send_packet()
+ p = wait_for_bfd_packet(
+ self, pcap_time_min=time.time() - self.vpp_clock_offset)
+ self.assert_equal(p[BFD].required_min_rx_interval,
+ self.vpp_session.required_min_rx,
+ "BFD required min rx interval")
+ self.test_session.send_packet()
+ self.vapi.bfd_udp_set_echo_source(self.loopback0.sw_if_index)
+ echo_seen = False
+ # should be turned on - loopback echo packets
+ for dummy in range(3):
+ loop_until = time.time() + 0.75 * detection_time
+ while time.time() < loop_until:
+ p = self.pg0.wait_for_packet(1)
+ self.logger.debug(ppp("Got packet:", p))
+ if p[UDP].dport == BFD.udp_dport_echo:
+ self.assert_equal(
+ p[IPv6].dst, self.pg0.local_ip6, "BFD ECHO dst IP")
+ self.assertNotEqual(p[IPv6].src, self.loopback0.local_ip6,
+ "BFD ECHO src IP equal to loopback IP")
+ self.logger.debug(ppp("Looping back packet:", p))
+ self.assert_equal(p[Ether].dst, self.pg0.remote_mac,
+ "ECHO packet destination MAC address")
+ p[Ether].dst = self.pg0.local_mac
+ self.pg0.add_stream(p)
+ self.pg_start()
+ echo_seen = True
+ elif p.haslayer(BFD):
+ if echo_seen:
+ self.assertGreaterEqual(
+ p[BFD].required_min_rx_interval,
+ 1000000)
+ if "P" in p.sprintf("%BFD.flags%"):
+ final = self.test_session.create_packet()
+ final[BFD].flags = "F"
+ self.test_session.send_packet(final)
+ else:
+ raise Exception(ppp("Received unknown packet:", p))
+
+ self.assert_equal(len(self.vapi.collect_events()), 0,
+ "number of bfd events")
+ self.test_session.send_packet()
+ self.assertTrue(echo_seen, "No echo packets received")
+
+ def test_intf_deleted(self):
+ """ interface with bfd session deleted """
+ intf = VppLoInterface(self, 0)
+ intf.config_ip6()
+ intf.admin_up()
+ sw_if_index = intf.sw_if_index
+ vpp_session = VppBFDUDPSession(
+ self, intf, intf.remote_ip6, af=AF_INET6)
+ vpp_session.add_vpp_config()
+ vpp_session.admin_up()
+ intf.remove_vpp_config()
+ e = self.vapi.wait_for_event(1, "bfd_udp_session_details")
+ self.assert_equal(e.sw_if_index, sw_if_index, "sw_if_index")
+ self.assertFalse(vpp_session.query_vpp_config())
+
+
+@unittest.skipUnless(running_extended_tests(), "part of extended tests")
+class BFDFIBTestCase(VppTestCase):
+ """ BFD-FIB interactions (IPv6) """
+
+ vpp_session = None
+ test_session = None
+
+ def setUp(self):
+ super(BFDFIBTestCase, self).setUp()
+ self.create_pg_interfaces(range(1))
+
+ self.vapi.want_bfd_events()
+ self.pg0.enable_capture()
+
+ for i in self.pg_interfaces:
+ i.admin_up()
+ i.config_ip6()
+ i.configure_ipv6_neighbors()
+
+ def tearDown(self):
+ if not self.vpp_dead:
+ self.vapi.want_bfd_events(enable_disable=0)
+
+ super(BFDFIBTestCase, self).tearDown()
+
+ @staticmethod
+ def pkt_is_not_data_traffic(p):
+ """ not data traffic implies BFD or the usual IPv6 ND/RA"""
+ if p.haslayer(BFD) or is_ipv6_misc(p):
+ return True
+ return False
+
+ def test_session_with_fib(self):
+ """ BFD-FIB interactions """
+
+ # packets to match against both of the routes
+ p = [(Ether(dst=self.pg0.local_mac, src=self.pg0.remote_mac) /
+ IPv6(src="3001::1", dst="2001::1") /
+ UDP(sport=1234, dport=1234) /
+ Raw('\xa5' * 100)),
+ (Ether(dst=self.pg0.local_mac, src=self.pg0.remote_mac) /
+ IPv6(src="3001::1", dst="2002::1") /
+ UDP(sport=1234, dport=1234) /
+ Raw('\xa5' * 100))]
+
+ # A recursive and a non-recursive route via a next-hop that
+ # will have a BFD session
+ ip_2001_s_64 = VppIpRoute(self, "2001::", 64,
+ [VppRoutePath(self.pg0.remote_ip6,
+ self.pg0.sw_if_index,
+ proto=DPO_PROTO_IP6)],
+ is_ip6=1)
+ ip_2002_s_64 = VppIpRoute(self, "2002::", 64,
+ [VppRoutePath(self.pg0.remote_ip6,
+ 0xffffffff,
+ proto=DPO_PROTO_IP6)],
+ is_ip6=1)
+ ip_2001_s_64.add_vpp_config()
+ ip_2002_s_64.add_vpp_config()
+
+ # bring the session up now the routes are present
+ self.vpp_session = VppBFDUDPSession(self,
+ self.pg0,
+ self.pg0.remote_ip6,
+ af=AF_INET6)
+ self.vpp_session.add_vpp_config()
+ self.vpp_session.admin_up()
+ self.test_session = BFDTestSession(self, self.pg0, AF_INET6)
+
+ # session is up - traffic passes
+ bfd_session_up(self)
+
+ self.pg0.add_stream(p)
+ self.pg_start()
+ for packet in p:
+ captured = self.pg0.wait_for_packet(
+ 1,
+ filter_out_fn=self.pkt_is_not_data_traffic)
+ self.assertEqual(captured[IPv6].dst,
+ packet[IPv6].dst)
+
+ # session is up - traffic is dropped
+ bfd_session_down(self)
+
+ self.pg0.add_stream(p)
+ self.pg_start()
+ with self.assertRaises(CaptureTimeoutError):
+ self.pg0.wait_for_packet(1, self.pkt_is_not_data_traffic)
+
+ # session is up - traffic passes
+ bfd_session_up(self)
+
+ self.pg0.add_stream(p)
+ self.pg_start()
+ for packet in p:
+ captured = self.pg0.wait_for_packet(
+ 1,
+ filter_out_fn=self.pkt_is_not_data_traffic)
+ self.assertEqual(captured[IPv6].dst,
+ packet[IPv6].dst)
+
+
+@unittest.skipUnless(running_extended_tests(), "part of extended tests")
+class BFDSHA1TestCase(VppTestCase):
+ """Bidirectional Forwarding Detection (BFD) (SHA1 auth) """
+
+ pg0 = None
+ vpp_clock_offset = None
+ vpp_session = None
+ test_session = None
+
+ @classmethod
+ def setUpClass(cls):
+ super(BFDSHA1TestCase, cls).setUpClass()
+ try:
+ cls.create_pg_interfaces([0])
+ cls.pg0.config_ip4()
+ cls.pg0.admin_up()
+ cls.pg0.resolve_arp()
+
+ except Exception:
+ super(BFDSHA1TestCase, cls).tearDownClass()
+ raise
+
+ def setUp(self):
+ super(BFDSHA1TestCase, self).setUp()
+ self.factory = AuthKeyFactory()
+ self.vapi.want_bfd_events()
+ self.pg0.enable_capture()
+
+ def tearDown(self):
+ if not self.vpp_dead:
+ self.vapi.want_bfd_events(enable_disable=0)
+ self.vapi.collect_events() # clear the event queue
+ super(BFDSHA1TestCase, self).tearDown()
+
+ def test_session_up(self):
+ """ bring BFD session up """
+ key = self.factory.create_random_key(self)
+ key.add_vpp_config()
+ self.vpp_session = VppBFDUDPSession(self, self.pg0,
+ self.pg0.remote_ip4,
+ sha1_key=key)
+ self.vpp_session.add_vpp_config()
+ self.vpp_session.admin_up()
+ self.test_session = BFDTestSession(
+ self, self.pg0, AF_INET, sha1_key=key,
+ bfd_key_id=self.vpp_session.bfd_key_id)
+ bfd_session_up(self)
+
+ @unittest.skipUnless(running_extended_tests(), "part of extended tests")
+ def test_hold_up(self):
+ """ hold BFD session up """
+ key = self.factory.create_random_key(self)
+ key.add_vpp_config()
+ self.vpp_session = VppBFDUDPSession(self, self.pg0,
+ self.pg0.remote_ip4,
+ sha1_key=key)
+ self.vpp_session.add_vpp_config()
+ self.vpp_session.admin_up()
+ self.test_session = BFDTestSession(
+ self, self.pg0, AF_INET, sha1_key=key,
+ bfd_key_id=self.vpp_session.bfd_key_id)
+ bfd_session_up(self)
+ for dummy in range(self.test_session.detect_mult * 2):
+ wait_for_bfd_packet(self)
+ self.test_session.send_packet()
+ self.assert_equal(self.vpp_session.state, BFDState.up, BFDState)
+
+ @unittest.skipUnless(running_extended_tests(), "part of extended tests")
+ def test_hold_up_meticulous(self):
+ """ hold BFD session up - meticulous auth """
+ key = self.factory.create_random_key(
+ self, BFDAuthType.meticulous_keyed_sha1)
+ key.add_vpp_config()
+ self.vpp_session = VppBFDUDPSession(self, self.pg0,
+ self.pg0.remote_ip4, sha1_key=key)
+ self.vpp_session.add_vpp_config()
+ self.vpp_session.admin_up()
+ # specify sequence number so that it wraps
+ self.test_session = BFDTestSession(
+ self, self.pg0, AF_INET, sha1_key=key,
+ bfd_key_id=self.vpp_session.bfd_key_id,
+ our_seq_number=0xFFFFFFFF - 4)
+ bfd_session_up(self)
+ for dummy in range(30):
+ wait_for_bfd_packet(self)
+ self.test_session.inc_seq_num()
+ self.test_session.send_packet()
+ self.assert_equal(self.vpp_session.state, BFDState.up, BFDState)
+
+ @unittest.skipUnless(running_extended_tests(), "part of extended tests")
+ def test_send_bad_seq_number(self):
+ """ session is not kept alive by msgs with bad sequence numbers"""
+ key = self.factory.create_random_key(
+ self, BFDAuthType.meticulous_keyed_sha1)
+ key.add_vpp_config()
+ self.vpp_session = VppBFDUDPSession(self, self.pg0,
+ self.pg0.remote_ip4, sha1_key=key)
+ self.vpp_session.add_vpp_config()
+ self.test_session = BFDTestSession(
+ self, self.pg0, AF_INET, sha1_key=key,
+ bfd_key_id=self.vpp_session.bfd_key_id)
+ bfd_session_up(self)
+ detection_time = self.test_session.detect_mult *\
+ self.vpp_session.required_min_rx / USEC_IN_SEC
+ send_until = time.time() + 2 * detection_time
+ while time.time() < send_until:
+ self.test_session.send_packet()
+ self.sleep(0.7 * self.vpp_session.required_min_rx / USEC_IN_SEC,
+ "time between bfd packets")
+ e = self.vapi.collect_events()
+ # session should be down now, because the sequence numbers weren't
+ # updated
+ self.assert_equal(len(e), 1, "number of bfd events")
+ verify_event(self, e[0], expected_state=BFDState.down)
+
+ def execute_rogue_session_scenario(self, vpp_bfd_udp_session,
+ legitimate_test_session,
+ rogue_test_session,
+ rogue_bfd_values=None):
+ """ execute a rogue session interaction scenario
+
+ 1. create vpp session, add config
+ 2. bring the legitimate session up
+ 3. copy the bfd values from legitimate session to rogue session
+ 4. apply rogue_bfd_values to rogue session
+ 5. set rogue session state to down
+ 6. send message to take the session down from the rogue session
+ 7. assert that the legitimate session is unaffected
+ """
+
+ self.vpp_session = vpp_bfd_udp_session
+ self.vpp_session.add_vpp_config()
+ self.test_session = legitimate_test_session
+ # bring vpp session up
+ bfd_session_up(self)
+ # send packet from rogue session
+ rogue_test_session.update(
+ my_discriminator=self.test_session.my_discriminator,
+ your_discriminator=self.test_session.your_discriminator,
+ desired_min_tx=self.test_session.desired_min_tx,
+ required_min_rx=self.test_session.required_min_rx,
+ detect_mult=self.test_session.detect_mult,
+ diag=self.test_session.diag,
+ state=self.test_session.state,
+ auth_type=self.test_session.auth_type)
+ if rogue_bfd_values:
+ rogue_test_session.update(**rogue_bfd_values)
+ rogue_test_session.update(state=BFDState.down)
+ rogue_test_session.send_packet()
+ wait_for_bfd_packet(self)
+ self.assert_equal(self.vpp_session.state, BFDState.up, BFDState)
+
+ @unittest.skipUnless(running_extended_tests(), "part of extended tests")
+ def test_mismatch_auth(self):
+ """ session is not brought down by unauthenticated msg """
+ key = self.factory.create_random_key(self)
+ key.add_vpp_config()
+ vpp_session = VppBFDUDPSession(
+ self, self.pg0, self.pg0.remote_ip4, sha1_key=key)
+ legitimate_test_session = BFDTestSession(
+ self, self.pg0, AF_INET, sha1_key=key,
+ bfd_key_id=vpp_session.bfd_key_id)
+ rogue_test_session = BFDTestSession(self, self.pg0, AF_INET)
+ self.execute_rogue_session_scenario(vpp_session,
+ legitimate_test_session,
+ rogue_test_session)
+
+ @unittest.skipUnless(running_extended_tests(), "part of extended tests")
+ def test_mismatch_bfd_key_id(self):
+ """ session is not brought down by msg with non-existent key-id """
+ key = self.factory.create_random_key(self)
+ key.add_vpp_config()
+ vpp_session = VppBFDUDPSession(
+ self, self.pg0, self.pg0.remote_ip4, sha1_key=key)
+ # pick a different random bfd key id
+ x = randint(0, 255)
+ while x == vpp_session.bfd_key_id:
+ x = randint(0, 255)
+ legitimate_test_session = BFDTestSession(
+ self, self.pg0, AF_INET, sha1_key=key,
+ bfd_key_id=vpp_session.bfd_key_id)
+ rogue_test_session = BFDTestSession(
+ self, self.pg0, AF_INET, sha1_key=key, bfd_key_id=x)
+ self.execute_rogue_session_scenario(vpp_session,
+ legitimate_test_session,
+ rogue_test_session)
+
+ @unittest.skipUnless(running_extended_tests(), "part of extended tests")
+ def test_mismatched_auth_type(self):
+ """ session is not brought down by msg with wrong auth type """
+ key = self.factory.create_random_key(self)
+ key.add_vpp_config()
+ vpp_session = VppBFDUDPSession(
+ self, self.pg0, self.pg0.remote_ip4, sha1_key=key)
+ legitimate_test_session = BFDTestSession(
+ self, self.pg0, AF_INET, sha1_key=key,
+ bfd_key_id=vpp_session.bfd_key_id)
+ rogue_test_session = BFDTestSession(
+ self, self.pg0, AF_INET, sha1_key=key,
+ bfd_key_id=vpp_session.bfd_key_id)
+ self.execute_rogue_session_scenario(
+ vpp_session, legitimate_test_session, rogue_test_session,
+ {'auth_type': BFDAuthType.keyed_md5})
+
+ @unittest.skipUnless(running_extended_tests(), "part of extended tests")
+ def test_restart(self):
+ """ simulate remote peer restart and resynchronization """
+ key = self.factory.create_random_key(
+ self, BFDAuthType.meticulous_keyed_sha1)
+ key.add_vpp_config()
+ self.vpp_session = VppBFDUDPSession(self, self.pg0,
+ self.pg0.remote_ip4, sha1_key=key)
+ self.vpp_session.add_vpp_config()
+ self.test_session = BFDTestSession(
+ self, self.pg0, AF_INET, sha1_key=key,
+ bfd_key_id=self.vpp_session.bfd_key_id, our_seq_number=0)
+ bfd_session_up(self)
+ # don't send any packets for 2*detection_time
+ detection_time = self.test_session.detect_mult *\
+ self.vpp_session.required_min_rx / USEC_IN_SEC
+ self.sleep(2 * detection_time, "simulating peer restart")
+ events = self.vapi.collect_events()
+ self.assert_equal(len(events), 1, "number of bfd events")
+ verify_event(self, events[0], expected_state=BFDState.down)
+ self.test_session.update(state=BFDState.down)
+ # reset sequence number
+ self.test_session.our_seq_number = 0
+ self.test_session.vpp_seq_number = None
+ # now throw away any pending packets
+ self.pg0.enable_capture()
+ bfd_session_up(self)
+
+
+@unittest.skipUnless(running_extended_tests(), "part of extended tests")
+class BFDAuthOnOffTestCase(VppTestCase):
+ """Bidirectional Forwarding Detection (BFD) (changing auth) """
+
+ pg0 = None
+ vpp_session = None
+ test_session = None
+
+ @classmethod
+ def setUpClass(cls):
+ super(BFDAuthOnOffTestCase, cls).setUpClass()
+ try:
+ cls.create_pg_interfaces([0])
+ cls.pg0.config_ip4()
+ cls.pg0.admin_up()
+ cls.pg0.resolve_arp()
+
+ except Exception:
+ super(BFDAuthOnOffTestCase, cls).tearDownClass()
+ raise
+
+ def setUp(self):
+ super(BFDAuthOnOffTestCase, self).setUp()
+ self.factory = AuthKeyFactory()
+ self.vapi.want_bfd_events()
+ self.pg0.enable_capture()
+
+ def tearDown(self):
+ if not self.vpp_dead:
+ self.vapi.want_bfd_events(enable_disable=0)
+ self.vapi.collect_events() # clear the event queue
+ super(BFDAuthOnOffTestCase, self).tearDown()
+
+ def test_auth_on_immediate(self):
+ """ turn auth on without disturbing session state (immediate) """
+ key = self.factory.create_random_key(self)
+ key.add_vpp_config()
+ self.vpp_session = VppBFDUDPSession(self, self.pg0,
+ self.pg0.remote_ip4)
+ self.vpp_session.add_vpp_config()
+ self.test_session = BFDTestSession(self, self.pg0, AF_INET)
+ bfd_session_up(self)
+ for dummy in range(self.test_session.detect_mult * 2):
+ p = wait_for_bfd_packet(self)
+ self.assert_equal(p[BFD].state, BFDState.up, BFDState)
+ self.test_session.send_packet()
+ self.vpp_session.activate_auth(key)
+ self.test_session.bfd_key_id = self.vpp_session.bfd_key_id
+ self.test_session.sha1_key = key
+ for dummy in range(self.test_session.detect_mult * 2):
+ p = wait_for_bfd_packet(self)
+ self.assert_equal(p[BFD].state, BFDState.up, BFDState)
+ self.test_session.send_packet()
+ self.assert_equal(self.vpp_session.state, BFDState.up, BFDState)
+ self.assert_equal(len(self.vapi.collect_events()), 0,
+ "number of bfd events")
+
+ def test_auth_off_immediate(self):
+ """ turn auth off without disturbing session state (immediate) """
+ key = self.factory.create_random_key(self)
+ key.add_vpp_config()
+ self.vpp_session = VppBFDUDPSession(self, self.pg0,
+ self.pg0.remote_ip4, sha1_key=key)
+ self.vpp_session.add_vpp_config()
+ self.test_session = BFDTestSession(
+ self, self.pg0, AF_INET, sha1_key=key,
+ bfd_key_id=self.vpp_session.bfd_key_id)
+ bfd_session_up(self)
+ # self.vapi.want_bfd_events(enable_disable=0)
+ for dummy in range(self.test_session.detect_mult * 2):
+ p = wait_for_bfd_packet(self)
+ self.assert_equal(p[BFD].state, BFDState.up, BFDState)
+ self.test_session.inc_seq_num()
+ self.test_session.send_packet()
+ self.vpp_session.deactivate_auth()
+ self.test_session.bfd_key_id = None
+ self.test_session.sha1_key = None
+ for dummy in range(self.test_session.detect_mult * 2):
+ p = wait_for_bfd_packet(self)
+ self.assert_equal(p[BFD].state, BFDState.up, BFDState)
+ self.test_session.inc_seq_num()
+ self.test_session.send_packet()
+ self.assert_equal(self.vpp_session.state, BFDState.up, BFDState)
+ self.assert_equal(len(self.vapi.collect_events()), 0,
+ "number of bfd events")
+
+ def test_auth_change_key_immediate(self):
+ """ change auth key without disturbing session state (immediate) """
+ key1 = self.factory.create_random_key(self)
+ key1.add_vpp_config()
+ key2 = self.factory.create_random_key(self)
+ key2.add_vpp_config()
+ self.vpp_session = VppBFDUDPSession(self, self.pg0,
+ self.pg0.remote_ip4, sha1_key=key1)
+ self.vpp_session.add_vpp_config()
+ self.test_session = BFDTestSession(
+ self, self.pg0, AF_INET, sha1_key=key1,
+ bfd_key_id=self.vpp_session.bfd_key_id)
+ bfd_session_up(self)
+ for dummy in range(self.test_session.detect_mult * 2):
+ p = wait_for_bfd_packet(self)
+ self.assert_equal(p[BFD].state, BFDState.up, BFDState)
+ self.test_session.send_packet()
+ self.vpp_session.activate_auth(key2)
+ self.test_session.bfd_key_id = self.vpp_session.bfd_key_id
+ self.test_session.sha1_key = key2
+ for dummy in range(self.test_session.detect_mult * 2):
+ p = wait_for_bfd_packet(self)
+ self.assert_equal(p[BFD].state, BFDState.up, BFDState)
+ self.test_session.send_packet()
+ self.assert_equal(self.vpp_session.state, BFDState.up, BFDState)
+ self.assert_equal(len(self.vapi.collect_events()), 0,
+ "number of bfd events")
+
+ def test_auth_on_delayed(self):
+ """ turn auth on without disturbing session state (delayed) """
+ key = self.factory.create_random_key(self)
+ key.add_vpp_config()
+ self.vpp_session = VppBFDUDPSession(self, self.pg0,
+ self.pg0.remote_ip4)
+ self.vpp_session.add_vpp_config()
+ self.test_session = BFDTestSession(self, self.pg0, AF_INET)
+ bfd_session_up(self)
+ for dummy in range(self.test_session.detect_mult * 2):
+ wait_for_bfd_packet(self)
+ self.test_session.send_packet()
+ self.vpp_session.activate_auth(key, delayed=True)
+ for dummy in range(self.test_session.detect_mult * 2):
+ p = wait_for_bfd_packet(self)
+ self.assert_equal(p[BFD].state, BFDState.up, BFDState)
+ self.test_session.send_packet()
+ self.test_session.bfd_key_id = self.vpp_session.bfd_key_id
+ self.test_session.sha1_key = key
+ self.test_session.send_packet()
+ for dummy in range(self.test_session.detect_mult * 2):
+ p = wait_for_bfd_packet(self)
+ self.assert_equal(p[BFD].state, BFDState.up, BFDState)
+ self.test_session.send_packet()
+ self.assert_equal(self.vpp_session.state, BFDState.up, BFDState)
+ self.assert_equal(len(self.vapi.collect_events()), 0,
+ "number of bfd events")
+
+ def test_auth_off_delayed(self):
+ """ turn auth off without disturbing session state (delayed) """
+ key = self.factory.create_random_key(self)
+ key.add_vpp_config()
+ self.vpp_session = VppBFDUDPSession(self, self.pg0,
+ self.pg0.remote_ip4, sha1_key=key)
+ self.vpp_session.add_vpp_config()
+ self.test_session = BFDTestSession(
+ self, self.pg0, AF_INET, sha1_key=key,
+ bfd_key_id=self.vpp_session.bfd_key_id)
+ bfd_session_up(self)
+ for dummy in range(self.test_session.detect_mult * 2):
+ p = wait_for_bfd_packet(self)
+ self.assert_equal(p[BFD].state, BFDState.up, BFDState)
+ self.test_session.send_packet()
+ self.vpp_session.deactivate_auth(delayed=True)
+ for dummy in range(self.test_session.detect_mult * 2):
+ p = wait_for_bfd_packet(self)
+ self.assert_equal(p[BFD].state, BFDState.up, BFDState)
+ self.test_session.send_packet()
+ self.test_session.bfd_key_id = None
+ self.test_session.sha1_key = None
+ self.test_session.send_packet()
+ for dummy in range(self.test_session.detect_mult * 2):
+ p = wait_for_bfd_packet(self)
+ self.assert_equal(p[BFD].state, BFDState.up, BFDState)
+ self.test_session.send_packet()
+ self.assert_equal(self.vpp_session.state, BFDState.up, BFDState)
+ self.assert_equal(len(self.vapi.collect_events()), 0,
+ "number of bfd events")
+
+ def test_auth_change_key_delayed(self):
+ """ change auth key without disturbing session state (delayed) """
+ key1 = self.factory.create_random_key(self)
+ key1.add_vpp_config()
+ key2 = self.factory.create_random_key(self)
+ key2.add_vpp_config()
+ self.vpp_session = VppBFDUDPSession(self, self.pg0,
+ self.pg0.remote_ip4, sha1_key=key1)
+ self.vpp_session.add_vpp_config()
+ self.vpp_session.admin_up()
+ self.test_session = BFDTestSession(
+ self, self.pg0, AF_INET, sha1_key=key1,
+ bfd_key_id=self.vpp_session.bfd_key_id)
+ bfd_session_up(self)
+ for dummy in range(self.test_session.detect_mult * 2):
+ p = wait_for_bfd_packet(self)
+ self.assert_equal(p[BFD].state, BFDState.up, BFDState)
+ self.test_session.send_packet()
+ self.vpp_session.activate_auth(key2, delayed=True)
+ for dummy in range(self.test_session.detect_mult * 2):
+ p = wait_for_bfd_packet(self)
+ self.assert_equal(p[BFD].state, BFDState.up, BFDState)
+ self.test_session.send_packet()
+ self.test_session.bfd_key_id = self.vpp_session.bfd_key_id
+ self.test_session.sha1_key = key2
+ self.test_session.send_packet()
+ for dummy in range(self.test_session.detect_mult * 2):
+ p = wait_for_bfd_packet(self)
+ self.assert_equal(p[BFD].state, BFDState.up, BFDState)
+ self.test_session.send_packet()
+ self.assert_equal(self.vpp_session.state, BFDState.up, BFDState)
+ self.assert_equal(len(self.vapi.collect_events()), 0,
+ "number of bfd events")
+
+
+@unittest.skipUnless(running_extended_tests(), "part of extended tests")
+class BFDCLITestCase(VppTestCase):
+ """Bidirectional Forwarding Detection (BFD) (CLI) """
+ pg0 = None
+
+ @classmethod
+ def setUpClass(cls):
+ super(BFDCLITestCase, cls).setUpClass()
+
+ try:
+ cls.create_pg_interfaces((0,))
+ cls.pg0.config_ip4()
+ cls.pg0.config_ip6()
+ cls.pg0.resolve_arp()
+ cls.pg0.resolve_ndp()
+
+ except Exception:
+ super(BFDCLITestCase, cls).tearDownClass()
+ raise
+
+ def setUp(self):
+ super(BFDCLITestCase, self).setUp()
+ self.factory = AuthKeyFactory()
+ self.pg0.enable_capture()
+
+ def tearDown(self):
+ try:
+ self.vapi.want_bfd_events(enable_disable=0)
+ except UnexpectedApiReturnValueError:
+ # some tests aren't subscribed, so this is not an issue
+ pass
+ self.vapi.collect_events() # clear the event queue
+ super(BFDCLITestCase, self).tearDown()
+
+ def cli_verify_no_response(self, cli):
+ """ execute a CLI, asserting that the response is empty """
+ self.assert_equal(self.vapi.cli(cli),
+ "",
+ "CLI command response")
+
+ def cli_verify_response(self, cli, expected):
+ """ execute a CLI, asserting that the response matches expectation """
+ self.assert_equal(self.vapi.cli(cli).strip(),
+ expected,
+ "CLI command response")
+
+ def test_show(self):
+ """ show commands """
+ k1 = self.factory.create_random_key(self)
+ k1.add_vpp_config()
+ k2 = self.factory.create_random_key(
+ self, auth_type=BFDAuthType.meticulous_keyed_sha1)
+ k2.add_vpp_config()
+ s1 = VppBFDUDPSession(self, self.pg0, self.pg0.remote_ip4)
+ s1.add_vpp_config()
+ s2 = VppBFDUDPSession(self, self.pg0, self.pg0.remote_ip6, af=AF_INET6,
+ sha1_key=k2)
+ s2.add_vpp_config()
+ self.logger.info(self.vapi.ppcli("show bfd keys"))
+ self.logger.info(self.vapi.ppcli("show bfd sessions"))
+ self.logger.info(self.vapi.ppcli("show bfd"))
+
+ def test_set_del_sha1_key(self):
+ """ set/delete SHA1 auth key """
+ k = self.factory.create_random_key(self)
+ self.registry.register(k, self.logger)
+ self.cli_verify_no_response(
+ "bfd key set conf-key-id %s type keyed-sha1 secret %s" %
+ (k.conf_key_id,
+ "".join("{:02x}".format(ord(c)) for c in k.key)))
+ self.assertTrue(k.query_vpp_config())
+ self.vpp_session = VppBFDUDPSession(
+ self, self.pg0, self.pg0.remote_ip4, sha1_key=k)
+ self.vpp_session.add_vpp_config()
+ self.test_session = \
+ BFDTestSession(self, self.pg0, AF_INET, sha1_key=k,
+ bfd_key_id=self.vpp_session.bfd_key_id)
+ self.vapi.want_bfd_events()
+ bfd_session_up(self)
+ bfd_session_down(self)
+ # try to replace the secret for the key - should fail because the key
+ # is in-use
+ k2 = self.factory.create_random_key(self)
+ self.cli_verify_response(
+ "bfd key set conf-key-id %s type keyed-sha1 secret %s" %
+ (k.conf_key_id,
+ "".join("{:02x}".format(ord(c)) for c in k2.key)),
+ "bfd key set: `bfd_auth_set_key' API call failed, "
+ "rv=-103:BFD object in use")
+ # manipulating the session using old secret should still work
+ bfd_session_up(self)
+ bfd_session_down(self)
+ self.vpp_session.remove_vpp_config()
+ self.cli_verify_no_response(
+ "bfd key del conf-key-id %s" % k.conf_key_id)
+ self.assertFalse(k.query_vpp_config())
+
+ def test_set_del_meticulous_sha1_key(self):
+ """ set/delete meticulous SHA1 auth key """
+ k = self.factory.create_random_key(
+ self, auth_type=BFDAuthType.meticulous_keyed_sha1)
+ self.registry.register(k, self.logger)
+ self.cli_verify_no_response(
+ "bfd key set conf-key-id %s type meticulous-keyed-sha1 secret %s" %
+ (k.conf_key_id,
+ "".join("{:02x}".format(ord(c)) for c in k.key)))
+ self.assertTrue(k.query_vpp_config())
+ self.vpp_session = VppBFDUDPSession(self, self.pg0,
+ self.pg0.remote_ip6, af=AF_INET6,
+ sha1_key=k)
+ self.vpp_session.add_vpp_config()
+ self.vpp_session.admin_up()
+ self.test_session = \
+ BFDTestSession(self, self.pg0, AF_INET6, sha1_key=k,
+ bfd_key_id=self.vpp_session.bfd_key_id)
+ self.vapi.want_bfd_events()
+ bfd_session_up(self)
+ bfd_session_down(self)
+ # try to replace the secret for the key - should fail because the key
+ # is in-use
+ k2 = self.factory.create_random_key(self)
+ self.cli_verify_response(
+ "bfd key set conf-key-id %s type keyed-sha1 secret %s" %
+ (k.conf_key_id,
+ "".join("{:02x}".format(ord(c)) for c in k2.key)),
+ "bfd key set: `bfd_auth_set_key' API call failed, "
+ "rv=-103:BFD object in use")
+ # manipulating the session using old secret should still work
+ bfd_session_up(self)
+ bfd_session_down(self)
+ self.vpp_session.remove_vpp_config()
+ self.cli_verify_no_response(
+ "bfd key del conf-key-id %s" % k.conf_key_id)
+ self.assertFalse(k.query_vpp_config())
+
+ def test_add_mod_del_bfd_udp(self):
+ """ create/modify/delete IPv4 BFD UDP session """
+ vpp_session = VppBFDUDPSession(
+ self, self.pg0, self.pg0.remote_ip4)
+ self.registry.register(vpp_session, self.logger)
+ cli_add_cmd = "bfd udp session add interface %s local-addr %s " \
+ "peer-addr %s desired-min-tx %s required-min-rx %s "\
+ "detect-mult %s" % (self.pg0.name, self.pg0.local_ip4,
+ self.pg0.remote_ip4,
+ vpp_session.desired_min_tx,
+ vpp_session.required_min_rx,
+ vpp_session.detect_mult)
+ self.cli_verify_no_response(cli_add_cmd)
+ # 2nd add should fail
+ self.cli_verify_response(
+ cli_add_cmd,
+ "bfd udp session add: `bfd_add_add_session' API call"
+ " failed, rv=-101:Duplicate BFD object")
+ verify_bfd_session_config(self, vpp_session)
+ mod_session = VppBFDUDPSession(
+ self, self.pg0, self.pg0.remote_ip4,
+ required_min_rx=2 * vpp_session.required_min_rx,
+ desired_min_tx=3 * vpp_session.desired_min_tx,
+ detect_mult=4 * vpp_session.detect_mult)
+ self.cli_verify_no_response(
+ "bfd udp session mod interface %s local-addr %s peer-addr %s "
+ "desired-min-tx %s required-min-rx %s detect-mult %s" %
+ (self.pg0.name, self.pg0.local_ip4, self.pg0.remote_ip4,
+ mod_session.desired_min_tx, mod_session.required_min_rx,
+ mod_session.detect_mult))
+ verify_bfd_session_config(self, mod_session)
+ cli_del_cmd = "bfd udp session del interface %s local-addr %s "\
+ "peer-addr %s" % (self.pg0.name,
+ self.pg0.local_ip4, self.pg0.remote_ip4)
+ self.cli_verify_no_response(cli_del_cmd)
+ # 2nd del is expected to fail
+ self.cli_verify_response(
+ cli_del_cmd, "bfd udp session del: `bfd_udp_del_session' API call"
+ " failed, rv=-102:No such BFD object")
+ self.assertFalse(vpp_session.query_vpp_config())
+
+ def test_add_mod_del_bfd_udp6(self):
+ """ create/modify/delete IPv6 BFD UDP session """
+ vpp_session = VppBFDUDPSession(
+ self, self.pg0, self.pg0.remote_ip6, af=AF_INET6)
+ self.registry.register(vpp_session, self.logger)
+ cli_add_cmd = "bfd udp session add interface %s local-addr %s " \
+ "peer-addr %s desired-min-tx %s required-min-rx %s "\
+ "detect-mult %s" % (self.pg0.name, self.pg0.local_ip6,
+ self.pg0.remote_ip6,
+ vpp_session.desired_min_tx,
+ vpp_session.required_min_rx,
+ vpp_session.detect_mult)
+ self.cli_verify_no_response(cli_add_cmd)
+ # 2nd add should fail
+ self.cli_verify_response(
+ cli_add_cmd,
+ "bfd udp session add: `bfd_add_add_session' API call"
+ " failed, rv=-101:Duplicate BFD object")
+ verify_bfd_session_config(self, vpp_session)
+ mod_session = VppBFDUDPSession(
+ self, self.pg0, self.pg0.remote_ip6, af=AF_INET6,
+ required_min_rx=2 * vpp_session.required_min_rx,
+ desired_min_tx=3 * vpp_session.desired_min_tx,
+ detect_mult=4 * vpp_session.detect_mult)
+ self.cli_verify_no_response(
+ "bfd udp session mod interface %s local-addr %s peer-addr %s "
+ "desired-min-tx %s required-min-rx %s detect-mult %s" %
+ (self.pg0.name, self.pg0.local_ip6, self.pg0.remote_ip6,
+ mod_session.desired_min_tx,
+ mod_session.required_min_rx, mod_session.detect_mult))
+ verify_bfd_session_config(self, mod_session)
+ cli_del_cmd = "bfd udp session del interface %s local-addr %s "\
+ "peer-addr %s" % (self.pg0.name,
+ self.pg0.local_ip6, self.pg0.remote_ip6)
+ self.cli_verify_no_response(cli_del_cmd)
+ # 2nd del is expected to fail
+ self.cli_verify_response(
+ cli_del_cmd,
+ "bfd udp session del: `bfd_udp_del_session' API call"
+ " failed, rv=-102:No such BFD object")
+ self.assertFalse(vpp_session.query_vpp_config())
+
+ def test_add_mod_del_bfd_udp_auth(self):
+ """ create/modify/delete IPv4 BFD UDP session (authenticated) """
+ key = self.factory.create_random_key(self)
+ key.add_vpp_config()
+ vpp_session = VppBFDUDPSession(
+ self, self.pg0, self.pg0.remote_ip4, sha1_key=key)
+ self.registry.register(vpp_session, self.logger)
+ cli_add_cmd = "bfd udp session add interface %s local-addr %s " \
+ "peer-addr %s desired-min-tx %s required-min-rx %s "\
+ "detect-mult %s conf-key-id %s bfd-key-id %s"\
+ % (self.pg0.name, self.pg0.local_ip4, self.pg0.remote_ip4,
+ vpp_session.desired_min_tx, vpp_session.required_min_rx,
+ vpp_session.detect_mult, key.conf_key_id,
+ vpp_session.bfd_key_id)
+ self.cli_verify_no_response(cli_add_cmd)
+ # 2nd add should fail
+ self.cli_verify_response(
+ cli_add_cmd,
+ "bfd udp session add: `bfd_add_add_session' API call"
+ " failed, rv=-101:Duplicate BFD object")
+ verify_bfd_session_config(self, vpp_session)
+ mod_session = VppBFDUDPSession(
+ self, self.pg0, self.pg0.remote_ip4, sha1_key=key,
+ bfd_key_id=vpp_session.bfd_key_id,
+ required_min_rx=2 * vpp_session.required_min_rx,
+ desired_min_tx=3 * vpp_session.desired_min_tx,
+ detect_mult=4 * vpp_session.detect_mult)
+ self.cli_verify_no_response(
+ "bfd udp session mod interface %s local-addr %s peer-addr %s "
+ "desired-min-tx %s required-min-rx %s detect-mult %s" %
+ (self.pg0.name, self.pg0.local_ip4, self.pg0.remote_ip4,
+ mod_session.desired_min_tx,
+ mod_session.required_min_rx, mod_session.detect_mult))
+ verify_bfd_session_config(self, mod_session)
+ cli_del_cmd = "bfd udp session del interface %s local-addr %s "\
+ "peer-addr %s" % (self.pg0.name,
+ self.pg0.local_ip4, self.pg0.remote_ip4)
+ self.cli_verify_no_response(cli_del_cmd)
+ # 2nd del is expected to fail
+ self.cli_verify_response(
+ cli_del_cmd,
+ "bfd udp session del: `bfd_udp_del_session' API call"
+ " failed, rv=-102:No such BFD object")
+ self.assertFalse(vpp_session.query_vpp_config())
+
+ def test_add_mod_del_bfd_udp6_auth(self):
+ """ create/modify/delete IPv6 BFD UDP session (authenticated) """
+ key = self.factory.create_random_key(
+ self, auth_type=BFDAuthType.meticulous_keyed_sha1)
+ key.add_vpp_config()
+ vpp_session = VppBFDUDPSession(
+ self, self.pg0, self.pg0.remote_ip6, af=AF_INET6, sha1_key=key)
+ self.registry.register(vpp_session, self.logger)
+ cli_add_cmd = "bfd udp session add interface %s local-addr %s " \
+ "peer-addr %s desired-min-tx %s required-min-rx %s "\
+ "detect-mult %s conf-key-id %s bfd-key-id %s" \
+ % (self.pg0.name, self.pg0.local_ip6, self.pg0.remote_ip6,
+ vpp_session.desired_min_tx, vpp_session.required_min_rx,
+ vpp_session.detect_mult, key.conf_key_id,
+ vpp_session.bfd_key_id)
+ self.cli_verify_no_response(cli_add_cmd)
+ # 2nd add should fail
+ self.cli_verify_response(
+ cli_add_cmd,
+ "bfd udp session add: `bfd_add_add_session' API call"
+ " failed, rv=-101:Duplicate BFD object")
+ verify_bfd_session_config(self, vpp_session)
+ mod_session = VppBFDUDPSession(
+ self, self.pg0, self.pg0.remote_ip6, af=AF_INET6, sha1_key=key,
+ bfd_key_id=vpp_session.bfd_key_id,
+ required_min_rx=2 * vpp_session.required_min_rx,
+ desired_min_tx=3 * vpp_session.desired_min_tx,
+ detect_mult=4 * vpp_session.detect_mult)
+ self.cli_verify_no_response(
+ "bfd udp session mod interface %s local-addr %s peer-addr %s "
+ "desired-min-tx %s required-min-rx %s detect-mult %s" %
+ (self.pg0.name, self.pg0.local_ip6, self.pg0.remote_ip6,
+ mod_session.desired_min_tx,
+ mod_session.required_min_rx, mod_session.detect_mult))
+ verify_bfd_session_config(self, mod_session)
+ cli_del_cmd = "bfd udp session del interface %s local-addr %s "\
+ "peer-addr %s" % (self.pg0.name,
+ self.pg0.local_ip6, self.pg0.remote_ip6)
+ self.cli_verify_no_response(cli_del_cmd)
+ # 2nd del is expected to fail
+ self.cli_verify_response(
+ cli_del_cmd,
+ "bfd udp session del: `bfd_udp_del_session' API call"
+ " failed, rv=-102:No such BFD object")
+ self.assertFalse(vpp_session.query_vpp_config())
+
+ def test_auth_on_off(self):
+ """ turn authentication on and off """
+ key = self.factory.create_random_key(
+ self, auth_type=BFDAuthType.meticulous_keyed_sha1)
+ key.add_vpp_config()
+ session = VppBFDUDPSession(self, self.pg0, self.pg0.remote_ip4)
+ auth_session = VppBFDUDPSession(self, self.pg0, self.pg0.remote_ip4,
+ sha1_key=key)
+ session.add_vpp_config()
+ cli_activate = \
+ "bfd udp session auth activate interface %s local-addr %s "\
+ "peer-addr %s conf-key-id %s bfd-key-id %s"\
+ % (self.pg0.name, self.pg0.local_ip4, self.pg0.remote_ip4,
+ key.conf_key_id, auth_session.bfd_key_id)
+ self.cli_verify_no_response(cli_activate)
+ verify_bfd_session_config(self, auth_session)
+ self.cli_verify_no_response(cli_activate)
+ verify_bfd_session_config(self, auth_session)
+ cli_deactivate = \
+ "bfd udp session auth deactivate interface %s local-addr %s "\
+ "peer-addr %s "\
+ % (self.pg0.name, self.pg0.local_ip4, self.pg0.remote_ip4)
+ self.cli_verify_no_response(cli_deactivate)
+ verify_bfd_session_config(self, session)
+ self.cli_verify_no_response(cli_deactivate)
+ verify_bfd_session_config(self, session)
+
+ def test_auth_on_off_delayed(self):
+ """ turn authentication on and off (delayed) """
+ key = self.factory.create_random_key(
+ self, auth_type=BFDAuthType.meticulous_keyed_sha1)
+ key.add_vpp_config()
+ session = VppBFDUDPSession(self, self.pg0, self.pg0.remote_ip4)
+ auth_session = VppBFDUDPSession(self, self.pg0, self.pg0.remote_ip4,
+ sha1_key=key)
+ session.add_vpp_config()
+ cli_activate = \
+ "bfd udp session auth activate interface %s local-addr %s "\
+ "peer-addr %s conf-key-id %s bfd-key-id %s delayed yes"\
+ % (self.pg0.name, self.pg0.local_ip4, self.pg0.remote_ip4,
+ key.conf_key_id, auth_session.bfd_key_id)
+ self.cli_verify_no_response(cli_activate)
+ verify_bfd_session_config(self, auth_session)
+ self.cli_verify_no_response(cli_activate)
+ verify_bfd_session_config(self, auth_session)
+ cli_deactivate = \
+ "bfd udp session auth deactivate interface %s local-addr %s "\
+ "peer-addr %s delayed yes"\
+ % (self.pg0.name, self.pg0.local_ip4, self.pg0.remote_ip4)
+ self.cli_verify_no_response(cli_deactivate)
+ verify_bfd_session_config(self, session)
+ self.cli_verify_no_response(cli_deactivate)
+ verify_bfd_session_config(self, session)
+
+ def test_admin_up_down(self):
+ """ put session admin-up and admin-down """
+ session = VppBFDUDPSession(self, self.pg0, self.pg0.remote_ip4)
+ session.add_vpp_config()
+ cli_down = \
+ "bfd udp session set-flags admin down interface %s local-addr %s "\
+ "peer-addr %s "\
+ % (self.pg0.name, self.pg0.local_ip4, self.pg0.remote_ip4)
+ cli_up = \
+ "bfd udp session set-flags admin up interface %s local-addr %s "\
+ "peer-addr %s "\
+ % (self.pg0.name, self.pg0.local_ip4, self.pg0.remote_ip4)
+ self.cli_verify_no_response(cli_down)
+ verify_bfd_session_config(self, session, state=BFDState.admin_down)
+ self.cli_verify_no_response(cli_up)
+ verify_bfd_session_config(self, session, state=BFDState.down)
+
+ def test_set_del_udp_echo_source(self):
+ """ set/del udp echo source """
+ self.create_loopback_interfaces([0])
+ self.loopback0 = self.lo_interfaces[0]
+ self.loopback0.admin_up()
+ self.cli_verify_response("show bfd echo-source",
+ "UDP echo source is not set.")
+ cli_set = "bfd udp echo-source set interface %s" % self.loopback0.name
+ self.cli_verify_no_response(cli_set)
+ self.cli_verify_response("show bfd echo-source",
+ "UDP echo source is: %s\n"
+ "IPv4 address usable as echo source: none\n"
+ "IPv6 address usable as echo source: none" %
+ self.loopback0.name)
+ self.loopback0.config_ip4()
+ unpacked = unpack("!L", self.loopback0.local_ip4n)
+ echo_ip4 = inet_ntop(AF_INET, pack("!L", unpacked[0] ^ 1))
+ self.cli_verify_response("show bfd echo-source",
+ "UDP echo source is: %s\n"
+ "IPv4 address usable as echo source: %s\n"
+ "IPv6 address usable as echo source: none" %
+ (self.loopback0.name, echo_ip4))
+ unpacked = unpack("!LLLL", self.loopback0.local_ip6n)
+ echo_ip6 = inet_ntop(AF_INET6, pack("!LLLL", unpacked[0], unpacked[1],
+ unpacked[2], unpacked[3] ^ 1))
+ self.loopback0.config_ip6()
+ self.cli_verify_response("show bfd echo-source",
+ "UDP echo source is: %s\n"
+ "IPv4 address usable as echo source: %s\n"
+ "IPv6 address usable as echo source: %s" %
+ (self.loopback0.name, echo_ip4, echo_ip6))
+ cli_del = "bfd udp echo-source del"
+ self.cli_verify_no_response(cli_del)
+ self.cli_verify_response("show bfd echo-source",
+ "UDP echo source is not set.")
+
+if __name__ == '__main__':
+ unittest.main(testRunner=VppTestRunner)
diff --git a/test/test_classifier.py b/test/test_classifier.py
new file mode 100644
index 00000000..a43f7a3d
--- /dev/null
+++ b/test/test_classifier.py
@@ -0,0 +1,385 @@
+#!/usr/bin/env python
+
+import unittest
+import socket
+import binascii
+
+from framework import VppTestCase, VppTestRunner
+
+from scapy.packet import Raw
+from scapy.layers.l2 import Ether
+from scapy.layers.inet import IP, UDP
+from util import ppp
+
+
+class TestClassifier(VppTestCase):
+ """ Classifier Test Case """
+
+ def setUp(self):
+ """
+ Perform test setup before test case.
+
+ **Config:**
+ - create 4 pg interfaces
+ - untagged pg0/pg1/pg2 interface
+ pg0 -------> pg1 (IP ACL)
+ \
+ ---> pg2 (MAC ACL))
+ \
+ -> pg3 (PBR)
+ - setup interfaces:
+ - put it into UP state
+ - set IPv4 addresses
+ - resolve neighbor address using ARP
+
+ :ivar list interfaces: pg interfaces.
+ :ivar list pg_if_packet_sizes: packet sizes in test.
+ :ivar dict acl_tbl_idx: ACL table index.
+ :ivar int pbr_vrfid: VRF id for PBR test.
+ """
+ super(TestClassifier, self).setUp()
+
+ # create 4 pg interfaces
+ self.create_pg_interfaces(range(4))
+
+ # packet sizes to test
+ self.pg_if_packet_sizes = [64, 9018]
+
+ self.interfaces = list(self.pg_interfaces)
+
+ # ACL & PBR vars
+ self.acl_tbl_idx = {}
+ self.pbr_vrfid = 200
+
+ # setup all interfaces
+ for intf in self.interfaces:
+ intf.admin_up()
+ intf.config_ip4()
+ intf.resolve_arp()
+
+ def tearDown(self):
+ """Run standard test teardown and acl related log."""
+ super(TestClassifier, self).tearDown()
+ if not self.vpp_dead:
+ self.logger.info(self.vapi.cli("show classify table verbose"))
+ self.logger.info(self.vapi.cli("show ip fib"))
+
+ def config_pbr_fib_entry(self, intf, is_add=1):
+ """Configure fib entry to route traffic toward PBR VRF table
+
+ :param VppInterface intf: destination interface to be routed for PBR.
+
+ """
+ addr_len = 24
+ self.vapi.ip_add_del_route(intf.local_ip4n,
+ addr_len,
+ intf.remote_ip4n,
+ table_id=self.pbr_vrfid,
+ is_add=is_add)
+
+ def create_stream(self, src_if, dst_if, packet_sizes):
+ """Create input packet stream for defined interfaces.
+
+ :param VppInterface src_if: Source Interface for packet stream.
+ :param VppInterface dst_if: Destination Interface for packet stream.
+ :param list packet_sizes: packet size to test.
+ """
+ pkts = []
+ for size in packet_sizes:
+ info = self.create_packet_info(src_if, dst_if)
+ payload = self.info_to_payload(info)
+ p = (Ether(dst=src_if.local_mac, src=src_if.remote_mac) /
+ IP(src=src_if.remote_ip4, dst=dst_if.remote_ip4) /
+ UDP(sport=1234, dport=5678) /
+ Raw(payload))
+ info.data = p.copy()
+ self.extend_packet(p, size)
+ pkts.append(p)
+ return pkts
+
+ def verify_capture(self, dst_if, capture):
+ """Verify captured input packet stream for defined interface.
+
+ :param VppInterface dst_if: Interface to verify captured packet stream.
+ :param list capture: Captured packet stream.
+ """
+ self.logger.info("Verifying capture on interface %s" % dst_if.name)
+ last_info = dict()
+ for i in self.interfaces:
+ last_info[i.sw_if_index] = None
+ dst_sw_if_index = dst_if.sw_if_index
+ for packet in capture:
+ try:
+ ip = packet[IP]
+ udp = packet[UDP]
+ payload_info = self.payload_to_info(str(packet[Raw]))
+ packet_index = payload_info.index
+ self.assertEqual(payload_info.dst, dst_sw_if_index)
+ self.logger.debug(
+ "Got packet on port %s: src=%u (id=%u)" %
+ (dst_if.name, payload_info.src, packet_index))
+ next_info = self.get_next_packet_info_for_interface2(
+ payload_info.src, dst_sw_if_index,
+ last_info[payload_info.src])
+ last_info[payload_info.src] = next_info
+ self.assertTrue(next_info is not None)
+ self.assertEqual(packet_index, next_info.index)
+ saved_packet = next_info.data
+ # Check standard fields
+ self.assertEqual(ip.src, saved_packet[IP].src)
+ self.assertEqual(ip.dst, saved_packet[IP].dst)
+ self.assertEqual(udp.sport, saved_packet[UDP].sport)
+ self.assertEqual(udp.dport, saved_packet[UDP].dport)
+ except:
+ self.logger.error(ppp("Unexpected or invalid packet:", packet))
+ raise
+ for i in self.interfaces:
+ remaining_packet = self.get_next_packet_info_for_interface2(
+ i.sw_if_index, dst_sw_if_index, last_info[i.sw_if_index])
+ self.assertTrue(remaining_packet is None,
+ "Interface %s: Packet expected from interface %s "
+ "didn't arrive" % (dst_if.name, i.name))
+
+ def verify_vrf(self, vrf_id):
+ """
+ Check if the FIB table / VRF ID is configured.
+
+ :param int vrf_id: The FIB table / VRF ID to be verified.
+ :return: 1 if the FIB table / VRF ID is configured, otherwise return 0.
+ """
+ ip_fib_dump = self.vapi.ip_fib_dump()
+ vrf_count = 0
+ for ip_fib_details in ip_fib_dump:
+ if ip_fib_details[2] == vrf_id:
+ vrf_count += 1
+ if vrf_count == 0:
+ self.logger.info("IPv4 VRF ID %d is not configured" % vrf_id)
+ return 0
+ else:
+ self.logger.info("IPv4 VRF ID %d is configured" % vrf_id)
+ return 1
+
+ @staticmethod
+ def build_ip_mask(proto='', src_ip='', dst_ip='',
+ src_port='', dst_port=''):
+ """Build IP ACL mask data with hexstring format
+
+ :param str proto: protocol number <0-ff>
+ :param str src_ip: source ip address <0-ffffffff>
+ :param str dst_ip: destination ip address <0-ffffffff>
+ :param str src_port: source port number <0-ffff>
+ :param str dst_port: destination port number <0-ffff>
+ """
+
+ return ('{:0>20}{:0>12}{:0>8}{:0>12}{:0>4}'.format(
+ proto, src_ip, dst_ip, src_port, dst_port)).rstrip('0')
+
+ @staticmethod
+ def build_ip_match(proto='', src_ip='', dst_ip='',
+ src_port='', dst_port=''):
+ """Build IP ACL match data with hexstring format
+
+ :param str proto: protocol number with valid option "<0-ff>"
+ :param str src_ip: source ip address with format of "x.x.x.x"
+ :param str dst_ip: destination ip address with format of "x.x.x.x"
+ :param str src_port: source port number <0-ffff>
+ :param str dst_port: destination port number <0-ffff>
+ """
+ if src_ip:
+ src_ip = socket.inet_aton(src_ip).encode('hex')
+ if dst_ip:
+ dst_ip = socket.inet_aton(dst_ip).encode('hex')
+
+ return ('{:0>20}{:0>12}{:0>8}{:0>12}{:0>4}'.format(
+ proto, src_ip, dst_ip, src_port, dst_port)).rstrip('0')
+
+ @staticmethod
+ def build_mac_mask(dst_mac='', src_mac='', ether_type=''):
+ """Build MAC ACL mask data with hexstring format
+
+ :param str dst_mac: source MAC address <0-ffffffffffff>
+ :param str src_mac: destination MAC address <0-ffffffffffff>
+ :param str ether_type: ethernet type <0-ffff>
+ """
+
+ return ('{:0>12}{:0>12}{:0>4}'.format(dst_mac, src_mac,
+ ether_type)).rstrip('0')
+
+ @staticmethod
+ def build_mac_match(dst_mac='', src_mac='', ether_type=''):
+ """Build MAC ACL match data with hexstring format
+
+ :param str dst_mac: source MAC address <x:x:x:x:x:x>
+ :param str src_mac: destination MAC address <x:x:x:x:x:x>
+ :param str ether_type: ethernet type <0-ffff>
+ """
+ if dst_mac:
+ dst_mac = dst_mac.replace(':', '')
+ if src_mac:
+ src_mac = src_mac.replace(':', '')
+
+ return ('{:0>12}{:0>12}{:0>4}'.format(dst_mac, src_mac,
+ ether_type)).rstrip('0')
+
+ def create_classify_table(self, key, mask, data_offset=0, is_add=1):
+ """Create Classify Table
+
+ :param str key: key for classify table (ex, ACL name).
+ :param str mask: mask value for interested traffic.
+ :param int match_n_vectors:
+ :param int is_add: option to configure classify table.
+ - create(1) or delete(0)
+ """
+ r = self.vapi.classify_add_del_table(
+ is_add,
+ binascii.unhexlify(mask),
+ match_n_vectors=(len(mask) - 1) // 32 + 1,
+ miss_next_index=0,
+ current_data_flag=1,
+ current_data_offset=data_offset)
+ self.assertIsNotNone(r, msg='No response msg for add_del_table')
+ self.acl_tbl_idx[key] = r.new_table_index
+
+ def create_classify_session(self, intf, table_index, match,
+ pbr_option=0, vrfid=0, is_add=1):
+ """Create Classify Session
+
+ :param VppInterface intf: Interface to apply classify session.
+ :param int table_index: table index to identify classify table.
+ :param str match: matched value for interested traffic.
+ :param int pbr_action: enable/disable PBR feature.
+ :param int vrfid: VRF id.
+ :param int is_add: option to configure classify session.
+ - create(1) or delete(0)
+ """
+ r = self.vapi.classify_add_del_session(
+ is_add,
+ table_index,
+ binascii.unhexlify(match),
+ opaque_index=0,
+ action=pbr_option,
+ metadata=vrfid)
+ self.assertIsNotNone(r, msg='No response msg for add_del_session')
+
+ def input_acl_set_interface(self, intf, table_index, is_add=1):
+ """Configure Input ACL interface
+
+ :param VppInterface intf: Interface to apply Input ACL feature.
+ :param int table_index: table index to identify classify table.
+ :param int is_add: option to configure classify session.
+ - enable(1) or disable(0)
+ """
+ r = self.vapi.input_acl_set_interface(
+ is_add,
+ intf.sw_if_index,
+ ip4_table_index=table_index)
+ self.assertIsNotNone(r, msg='No response msg for acl_set_interface')
+
+ def test_acl_ip(self):
+ """ IP ACL test
+
+ Test scenario for basic IP ACL with source IP
+ - Create IPv4 stream for pg0 -> pg1 interface.
+ - Create ACL with source IP address.
+ - Send and verify received packets on pg1 interface.
+ """
+
+ # Basic ACL testing with source IP
+ pkts = self.create_stream(self.pg0, self.pg1, self.pg_if_packet_sizes)
+ self.pg0.add_stream(pkts)
+
+ self.create_classify_table('ip', self.build_ip_mask(src_ip='ffffffff'))
+ self.create_classify_session(
+ self.pg0, self.acl_tbl_idx.get('ip'),
+ self.build_ip_match(src_ip=self.pg0.remote_ip4))
+ self.input_acl_set_interface(self.pg0, self.acl_tbl_idx.get('ip'))
+
+ self.pg_enable_capture(self.pg_interfaces)
+ self.pg_start()
+
+ pkts = self.pg1.get_capture(len(pkts))
+ self.verify_capture(self.pg1, pkts)
+ self.input_acl_set_interface(self.pg0, self.acl_tbl_idx.get('ip'), 0)
+ self.pg0.assert_nothing_captured(remark="packets forwarded")
+ self.pg2.assert_nothing_captured(remark="packets forwarded")
+ self.pg3.assert_nothing_captured(remark="packets forwarded")
+
+ def test_acl_mac(self):
+ """ MAC ACL test
+
+ Test scenario for basic MAC ACL with source MAC
+ - Create IPv4 stream for pg0 -> pg2 interface.
+ - Create ACL with source MAC address.
+ - Send and verify received packets on pg2 interface.
+ """
+
+ # Basic ACL testing with source MAC
+ pkts = self.create_stream(self.pg0, self.pg2, self.pg_if_packet_sizes)
+ self.pg0.add_stream(pkts)
+
+ self.create_classify_table('mac',
+ self.build_mac_mask(src_mac='ffffffffffff'),
+ data_offset=-14)
+ self.create_classify_session(
+ self.pg0, self.acl_tbl_idx.get('mac'),
+ self.build_mac_match(src_mac=self.pg0.remote_mac))
+ self.input_acl_set_interface(self.pg0, self.acl_tbl_idx.get('mac'))
+
+ self.pg_enable_capture(self.pg_interfaces)
+ self.pg_start()
+
+ pkts = self.pg2.get_capture(len(pkts))
+ self.verify_capture(self.pg2, pkts)
+ self.input_acl_set_interface(self.pg0, self.acl_tbl_idx.get('mac'), 0)
+ self.pg0.assert_nothing_captured(remark="packets forwarded")
+ self.pg1.assert_nothing_captured(remark="packets forwarded")
+ self.pg3.assert_nothing_captured(remark="packets forwarded")
+
+ def test_acl_pbr(self):
+ """ IP PBR test
+
+ Test scenario for PBR with source IP
+ - Create IPv4 stream for pg0 -> pg3 interface.
+ - Configure PBR fib entry for packet forwarding.
+ - Send and verify received packets on pg3 interface.
+ """
+
+ # PBR testing with source IP
+ pkts = self.create_stream(self.pg0, self.pg3, self.pg_if_packet_sizes)
+ self.pg0.add_stream(pkts)
+
+ self.create_classify_table(
+ 'pbr', self.build_ip_mask(
+ src_ip='ffffffff'))
+ pbr_option = 1
+ # this will create the VRF/table in which we will insert the route
+ self.create_classify_session(
+ self.pg0, self.acl_tbl_idx.get('pbr'),
+ self.build_ip_match(src_ip=self.pg0.remote_ip4),
+ pbr_option, self.pbr_vrfid)
+ self.assertTrue(self.verify_vrf(self.pbr_vrfid))
+ self.config_pbr_fib_entry(self.pg3)
+ self.input_acl_set_interface(self.pg0, self.acl_tbl_idx.get('pbr'))
+
+ self.pg_enable_capture(self.pg_interfaces)
+ self.pg_start()
+
+ pkts = self.pg3.get_capture(len(pkts))
+ self.verify_capture(self.pg3, pkts)
+ self.input_acl_set_interface(self.pg0, self.acl_tbl_idx.get('pbr'), 0)
+ self.pg0.assert_nothing_captured(remark="packets forwarded")
+ self.pg1.assert_nothing_captured(remark="packets forwarded")
+ self.pg2.assert_nothing_captured(remark="packets forwarded")
+
+ # remove the classify session and the route
+ self.config_pbr_fib_entry(self.pg3, is_add=0)
+ self.create_classify_session(
+ self.pg0, self.acl_tbl_idx.get('pbr'),
+ self.build_ip_match(src_ip=self.pg0.remote_ip4),
+ pbr_option, self.pbr_vrfid, is_add=0)
+
+ # and the table should be gone.
+ self.assertFalse(self.verify_vrf(self.pbr_vrfid))
+
+if __name__ == '__main__':
+ unittest.main(testRunner=VppTestRunner)
diff --git a/test/test_dhcp.py b/test/test_dhcp.py
new file mode 100644
index 00000000..42b80af3
--- /dev/null
+++ b/test/test_dhcp.py
@@ -0,0 +1,1209 @@
+#!/usr/bin/env python
+
+import unittest
+import socket
+import struct
+
+from framework import VppTestCase, VppTestRunner
+from vpp_neighbor import VppNeighbor
+from vpp_ip_route import find_route, VppIpTable
+from util import mk_ll_addr
+
+from scapy.layers.l2 import Ether, getmacbyip, ARP
+from scapy.layers.inet import IP, UDP, ICMP
+from scapy.layers.inet6 import IPv6, in6_getnsmac, in6_mactoifaceid
+from scapy.layers.dhcp import DHCP, BOOTP, DHCPTypes
+from scapy.layers.dhcp6 import DHCP6, DHCP6_Solicit, DHCP6_RelayForward, \
+ DHCP6_RelayReply, DHCP6_Advertise, DHCP6OptRelayMsg, DHCP6OptIfaceId, \
+ DHCP6OptStatusCode, DHCP6OptVSS, DHCP6OptClientLinkLayerAddr, DHCP6_Request
+from socket import AF_INET, AF_INET6
+from scapy.utils import inet_pton, inet_ntop
+from scapy.utils6 import in6_ptop
+from util import mactobinary
+
+DHCP4_CLIENT_PORT = 68
+DHCP4_SERVER_PORT = 67
+DHCP6_CLIENT_PORT = 547
+DHCP6_SERVER_PORT = 546
+
+
+class TestDHCP(VppTestCase):
+ """ DHCP Test Case """
+
+ def setUp(self):
+ super(TestDHCP, self).setUp()
+
+ # create 3 pg interfaces
+ self.create_pg_interfaces(range(4))
+ self.tables = []
+
+ # pg0 and 1 are IP configured in VRF 0 and 1.
+ # pg2 and 3 are non IP-configured in VRF 0 and 1
+ table_id = 0
+ for table_id in range(1, 4):
+ tbl4 = VppIpTable(self, table_id)
+ tbl4.add_vpp_config()
+ self.tables.append(tbl4)
+ tbl6 = VppIpTable(self, table_id, is_ip6=1)
+ tbl6.add_vpp_config()
+ self.tables.append(tbl6)
+
+ table_id = 0
+ for i in self.pg_interfaces[:2]:
+ i.admin_up()
+ i.set_table_ip4(table_id)
+ i.set_table_ip6(table_id)
+ i.config_ip4()
+ i.resolve_arp()
+ i.config_ip6()
+ i.resolve_ndp()
+ table_id += 1
+
+ table_id = 0
+ for i in self.pg_interfaces[2:]:
+ i.admin_up()
+ i.set_table_ip4(table_id)
+ i.set_table_ip6(table_id)
+ table_id += 1
+
+ def tearDown(self):
+ for i in self.pg_interfaces[:2]:
+ i.unconfig_ip4()
+ i.unconfig_ip6()
+
+ for i in self.pg_interfaces:
+ i.set_table_ip4(0)
+ i.set_table_ip6(0)
+ i.admin_down()
+ super(TestDHCP, self).tearDown()
+
+ def send_and_assert_no_replies(self, intf, pkts, remark):
+ intf.add_stream(pkts)
+ self.pg_enable_capture(self.pg_interfaces)
+ self.pg_start()
+ for i in self.pg_interfaces:
+ i.assert_nothing_captured(remark=remark)
+
+ def verify_dhcp_has_option(self, pkt, option, value):
+ dhcp = pkt[DHCP]
+ found = False
+
+ for i in dhcp.options:
+ if type(i) is tuple:
+ if i[0] == option:
+ self.assertEqual(i[1], value)
+ found = True
+
+ self.assertTrue(found)
+
+ def validate_relay_options(self, pkt, intf, ip_addr, fib_id, oui):
+ dhcp = pkt[DHCP]
+ found = 0
+ data = []
+
+ for i in dhcp.options:
+ if type(i) is tuple:
+ if i[0] == "relay_agent_Information":
+ #
+ # There are two sb-options present - each of length 6.
+ #
+ data = i[1]
+ if oui != 0:
+ self.assertEqual(len(data), 24)
+ else:
+ self.assertEqual(len(data), 12)
+
+ #
+ # First sub-option is ID 1, len 4, then encoded
+ # sw_if_index. This test uses low valued indicies
+ # so [2:4] are 0.
+ # The ID space is VPP internal - so no matching value
+ # scapy
+ #
+ self.assertEqual(ord(data[0]), 1)
+ self.assertEqual(ord(data[1]), 4)
+ self.assertEqual(ord(data[2]), 0)
+ self.assertEqual(ord(data[3]), 0)
+ self.assertEqual(ord(data[4]), 0)
+ self.assertEqual(ord(data[5]), intf._sw_if_index)
+
+ #
+ # next sub-option is the IP address of the client side
+ # interface.
+ # sub-option ID=5, length (of a v4 address)=4
+ #
+ claddr = socket.inet_pton(AF_INET, ip_addr)
+
+ self.assertEqual(ord(data[6]), 5)
+ self.assertEqual(ord(data[7]), 4)
+ self.assertEqual(data[8], claddr[0])
+ self.assertEqual(data[9], claddr[1])
+ self.assertEqual(data[10], claddr[2])
+ self.assertEqual(data[11], claddr[3])
+
+ if oui != 0:
+ # sub-option 151 encodes the 3 byte oui
+ # and the 4 byte fib_id
+ self.assertEqual(ord(data[12]), 151)
+ self.assertEqual(ord(data[13]), 8)
+ self.assertEqual(ord(data[14]), 1)
+ self.assertEqual(ord(data[15]), 0)
+ self.assertEqual(ord(data[16]), 0)
+ self.assertEqual(ord(data[17]), oui)
+ self.assertEqual(ord(data[18]), 0)
+ self.assertEqual(ord(data[19]), 0)
+ self.assertEqual(ord(data[20]), 0)
+ self.assertEqual(ord(data[21]), fib_id)
+
+ # VSS control sub-option
+ self.assertEqual(ord(data[22]), 152)
+ self.assertEqual(ord(data[23]), 0)
+
+ found = 1
+ self.assertTrue(found)
+
+ return data
+
+ def verify_dhcp_msg_type(self, pkt, name):
+ dhcp = pkt[DHCP]
+ found = False
+ for o in dhcp.options:
+ if type(o) is tuple:
+ if o[0] == "message-type" \
+ and DHCPTypes[o[1]] == name:
+ found = True
+ self.assertTrue(found)
+
+ def verify_dhcp_offer(self, pkt, intf, fib_id=0, oui=0):
+ ether = pkt[Ether]
+ self.assertEqual(ether.dst, "ff:ff:ff:ff:ff:ff")
+ self.assertEqual(ether.src, intf.local_mac)
+
+ ip = pkt[IP]
+ self.assertEqual(ip.dst, "255.255.255.255")
+ self.assertEqual(ip.src, intf.local_ip4)
+
+ udp = pkt[UDP]
+ self.assertEqual(udp.dport, DHCP4_CLIENT_PORT)
+ self.assertEqual(udp.sport, DHCP4_SERVER_PORT)
+
+ self.verify_dhcp_msg_type(pkt, "offer")
+ data = self.validate_relay_options(pkt, intf, intf.local_ip4,
+ fib_id, oui)
+
+ def verify_orig_dhcp_pkt(self, pkt, intf):
+ ether = pkt[Ether]
+ self.assertEqual(ether.dst, "ff:ff:ff:ff:ff:ff")
+ self.assertEqual(ether.src, intf.local_mac)
+
+ ip = pkt[IP]
+ self.assertEqual(ip.dst, "255.255.255.255")
+ self.assertEqual(ip.src, "0.0.0.0")
+
+ udp = pkt[UDP]
+ self.assertEqual(udp.dport, DHCP4_SERVER_PORT)
+ self.assertEqual(udp.sport, DHCP4_CLIENT_PORT)
+
+ def verify_orig_dhcp_discover(self, pkt, intf, hostname, client_id=None):
+ self.verify_orig_dhcp_pkt(pkt, intf)
+
+ self.verify_dhcp_msg_type(pkt, "discover")
+ self.verify_dhcp_has_option(pkt, "hostname", hostname)
+ if client_id:
+ self.verify_dhcp_has_option(pkt, "client_id", client_id)
+ bootp = pkt[BOOTP]
+ self.assertEqual(bootp.ciaddr, "0.0.0.0")
+ self.assertEqual(bootp.giaddr, "0.0.0.0")
+ self.assertEqual(bootp.flags, 0x8000)
+
+ def verify_orig_dhcp_request(self, pkt, intf, hostname, ip):
+ self.verify_orig_dhcp_pkt(pkt, intf)
+
+ self.verify_dhcp_msg_type(pkt, "request")
+ self.verify_dhcp_has_option(pkt, "hostname", hostname)
+ self.verify_dhcp_has_option(pkt, "requested_addr", ip)
+ bootp = pkt[BOOTP]
+ self.assertEqual(bootp.ciaddr, "0.0.0.0")
+ self.assertEqual(bootp.giaddr, "0.0.0.0")
+ self.assertEqual(bootp.flags, 0x8000)
+
+ def verify_relayed_dhcp_discover(self, pkt, intf, src_intf=None,
+ fib_id=0, oui=0,
+ dst_mac=None, dst_ip=None):
+ if not dst_mac:
+ dst_mac = intf.remote_mac
+ if not dst_ip:
+ dst_ip = intf.remote_ip4
+
+ ether = pkt[Ether]
+ self.assertEqual(ether.dst, dst_mac)
+ self.assertEqual(ether.src, intf.local_mac)
+
+ ip = pkt[IP]
+ self.assertEqual(ip.dst, dst_ip)
+ self.assertEqual(ip.src, intf.local_ip4)
+
+ udp = pkt[UDP]
+ self.assertEqual(udp.dport, DHCP4_SERVER_PORT)
+ self.assertEqual(udp.sport, DHCP4_CLIENT_PORT)
+
+ dhcp = pkt[DHCP]
+
+ is_discover = False
+ for o in dhcp.options:
+ if type(o) is tuple:
+ if o[0] == "message-type" \
+ and DHCPTypes[o[1]] == "discover":
+ is_discover = True
+ self.assertTrue(is_discover)
+
+ data = self.validate_relay_options(pkt, src_intf,
+ src_intf.local_ip4,
+ fib_id, oui)
+ return data
+
+ def verify_dhcp6_solicit(self, pkt, intf,
+ peer_ip, peer_mac,
+ fib_id=0,
+ oui=0,
+ dst_mac=None,
+ dst_ip=None):
+ if not dst_mac:
+ dst_mac = intf.remote_mac
+ if not dst_ip:
+ dst_ip = in6_ptop(intf.remote_ip6)
+
+ ether = pkt[Ether]
+ self.assertEqual(ether.dst, dst_mac)
+ self.assertEqual(ether.src, intf.local_mac)
+
+ ip = pkt[IPv6]
+ self.assertEqual(in6_ptop(ip.dst), dst_ip)
+ self.assertEqual(in6_ptop(ip.src), in6_ptop(intf.local_ip6))
+
+ udp = pkt[UDP]
+ self.assertEqual(udp.dport, DHCP6_CLIENT_PORT)
+ self.assertEqual(udp.sport, DHCP6_SERVER_PORT)
+
+ relay = pkt[DHCP6_RelayForward]
+ self.assertEqual(in6_ptop(relay.peeraddr), in6_ptop(peer_ip))
+ oid = pkt[DHCP6OptIfaceId]
+ cll = pkt[DHCP6OptClientLinkLayerAddr]
+ self.assertEqual(cll.optlen, 8)
+ self.assertEqual(cll.lltype, 1)
+ self.assertEqual(cll.clladdr, peer_mac)
+
+ if fib_id != 0:
+ vss = pkt[DHCP6OptVSS]
+ self.assertEqual(vss.optlen, 8)
+ self.assertEqual(vss.type, 1)
+ # the OUI and FIB-id are really 3 and 4 bytes resp.
+ # but the tested range is small
+ self.assertEqual(ord(vss.data[0]), 0)
+ self.assertEqual(ord(vss.data[1]), 0)
+ self.assertEqual(ord(vss.data[2]), oui)
+ self.assertEqual(ord(vss.data[3]), 0)
+ self.assertEqual(ord(vss.data[4]), 0)
+ self.assertEqual(ord(vss.data[5]), 0)
+ self.assertEqual(ord(vss.data[6]), fib_id)
+
+ # the relay message should be an encoded Solicit
+ msg = pkt[DHCP6OptRelayMsg]
+ sol = DHCP6_Solicit()
+ self.assertEqual(msg.optlen, len(str(sol)))
+ self.assertEqual(str(sol), (str(msg[1]))[:msg.optlen])
+
+ def verify_dhcp6_advert(self, pkt, intf, peer):
+ ether = pkt[Ether]
+ self.assertEqual(ether.dst, "ff:ff:ff:ff:ff:ff")
+ self.assertEqual(ether.src, intf.local_mac)
+
+ ip = pkt[IPv6]
+ self.assertEqual(in6_ptop(ip.dst), in6_ptop(peer))
+ self.assertEqual(in6_ptop(ip.src), in6_ptop(intf.local_ip6))
+
+ udp = pkt[UDP]
+ self.assertEqual(udp.dport, DHCP6_SERVER_PORT)
+ self.assertEqual(udp.sport, DHCP6_CLIENT_PORT)
+
+ # not sure why this is not decoding
+ # adv = pkt[DHCP6_Advertise]
+
+ def test_dhcp_proxy(self):
+ """ DHCPv4 Proxy """
+
+ #
+ # Verify no response to DHCP request without DHCP config
+ #
+ p_disc_vrf0 = (Ether(dst="ff:ff:ff:ff:ff:ff",
+ src=self.pg2.remote_mac) /
+ IP(src="0.0.0.0", dst="255.255.255.255") /
+ UDP(sport=DHCP4_CLIENT_PORT,
+ dport=DHCP4_SERVER_PORT) /
+ BOOTP(op=1) /
+ DHCP(options=[('message-type', 'discover'), ('end')]))
+ pkts_disc_vrf0 = [p_disc_vrf0]
+ p_disc_vrf1 = (Ether(dst="ff:ff:ff:ff:ff:ff",
+ src=self.pg3.remote_mac) /
+ IP(src="0.0.0.0", dst="255.255.255.255") /
+ UDP(sport=DHCP4_CLIENT_PORT,
+ dport=DHCP4_SERVER_PORT) /
+ BOOTP(op=1) /
+ DHCP(options=[('message-type', 'discover'), ('end')]))
+ pkts_disc_vrf1 = [p_disc_vrf0]
+
+ self.send_and_assert_no_replies(self.pg2, pkts_disc_vrf0,
+ "DHCP with no configuration")
+ self.send_and_assert_no_replies(self.pg3, pkts_disc_vrf1,
+ "DHCP with no configuration")
+
+ #
+ # Enable DHCP proxy in VRF 0
+ #
+ server_addr = self.pg0.remote_ip4n
+ src_addr = self.pg0.local_ip4n
+
+ self.vapi.dhcp_proxy_config(server_addr,
+ src_addr,
+ rx_table_id=0)
+
+ #
+ # Discover packets from the client are dropped because there is no
+ # IP address configured on the client facing interface
+ #
+ self.send_and_assert_no_replies(self.pg2, pkts_disc_vrf0,
+ "Discover DHCP no relay address")
+
+ #
+ # Inject a response from the server
+ # dropped, because there is no IP addrees on the
+ # client interfce to fill in the option.
+ #
+ p = (Ether(dst=self.pg0.local_mac, src=self.pg0.remote_mac) /
+ IP(src=self.pg0.remote_ip4, dst=self.pg0.local_ip4) /
+ UDP(sport=DHCP4_SERVER_PORT, dport=DHCP4_SERVER_PORT) /
+ BOOTP(op=1) /
+ DHCP(options=[('message-type', 'offer'), ('end')]))
+ pkts = [p]
+
+ self.send_and_assert_no_replies(self.pg2, pkts,
+ "Offer DHCP no relay address")
+
+ #
+ # configure an IP address on the client facing interface
+ #
+ self.pg2.config_ip4()
+
+ #
+ # Try again with a discover packet
+ # Rx'd packet should be to the server address and from the configured
+ # source address
+ # UDP source ports are unchanged
+ # we've no option 82 config so that should be absent
+ #
+ self.pg2.add_stream(pkts_disc_vrf0)
+ self.pg_enable_capture(self.pg_interfaces)
+ self.pg_start()
+
+ rx = self.pg0.get_capture(1)
+ rx = rx[0]
+
+ option_82 = self.verify_relayed_dhcp_discover(rx, self.pg0,
+ src_intf=self.pg2)
+
+ #
+ # Create an DHCP offer reply from the server with a correctly formatted
+ # option 82. i.e. send back what we just captured
+ # The offer, sent mcast to the client, still has option 82.
+ #
+ p = (Ether(dst=self.pg0.local_mac, src=self.pg0.remote_mac) /
+ IP(src=self.pg0.remote_ip4, dst=self.pg0.local_ip4) /
+ UDP(sport=DHCP4_SERVER_PORT, dport=DHCP4_SERVER_PORT) /
+ BOOTP(op=1) /
+ DHCP(options=[('message-type', 'offer'),
+ ('relay_agent_Information', option_82),
+ ('end')]))
+ pkts = [p]
+
+ self.pg0.add_stream(pkts)
+ self.pg_enable_capture(self.pg_interfaces)
+ self.pg_start()
+
+ rx = self.pg2.get_capture(1)
+ rx = rx[0]
+
+ self.verify_dhcp_offer(rx, self.pg2)
+
+ #
+ # Bogus Option 82:
+ #
+ # 1. not our IP address = not checked by VPP? so offer is replayed
+ # to client
+ bad_ip = option_82[0:8] + chr(33) + option_82[9:]
+
+ p = (Ether(dst=self.pg0.local_mac, src=self.pg0.remote_mac) /
+ IP(src=self.pg0.remote_ip4, dst=self.pg0.local_ip4) /
+ UDP(sport=DHCP4_SERVER_PORT, dport=DHCP4_SERVER_PORT) /
+ BOOTP(op=1) /
+ DHCP(options=[('message-type', 'offer'),
+ ('relay_agent_Information', bad_ip),
+ ('end')]))
+ pkts = [p]
+ self.send_and_assert_no_replies(self.pg0, pkts,
+ "DHCP offer option 82 bad address")
+
+ # 2. Not a sw_if_index VPP knows
+ bad_if_index = option_82[0:2] + chr(33) + option_82[3:]
+
+ p = (Ether(dst=self.pg0.local_mac, src=self.pg0.remote_mac) /
+ IP(src=self.pg0.remote_ip4, dst=self.pg0.local_ip4) /
+ UDP(sport=DHCP4_SERVER_PORT, dport=DHCP4_SERVER_PORT) /
+ BOOTP(op=1) /
+ DHCP(options=[('message-type', 'offer'),
+ ('relay_agent_Information', bad_if_index),
+ ('end')]))
+ pkts = [p]
+ self.send_and_assert_no_replies(self.pg0, pkts,
+ "DHCP offer option 82 bad if index")
+
+ #
+ # Send a DHCP request in VRF 1. should be dropped.
+ #
+ self.send_and_assert_no_replies(self.pg3, pkts_disc_vrf1,
+ "DHCP with no configuration VRF 1")
+
+ #
+ # Delete the DHCP config in VRF 0
+ # Should now drop requests.
+ #
+ self.vapi.dhcp_proxy_config(server_addr,
+ src_addr,
+ rx_table_id=0,
+ is_add=0)
+
+ self.send_and_assert_no_replies(self.pg2, pkts_disc_vrf0,
+ "DHCP config removed VRF 0")
+ self.send_and_assert_no_replies(self.pg3, pkts_disc_vrf1,
+ "DHCP config removed VRF 1")
+
+ #
+ # Add DHCP config for VRF 1
+ #
+ server_addr = self.pg1.remote_ip4n
+ src_addr = self.pg1.local_ip4n
+ self.vapi.dhcp_proxy_config(server_addr,
+ src_addr,
+ rx_table_id=1,
+ server_table_id=1)
+
+ #
+ # Confim DHCP requests ok in VRF 1.
+ # - dropped on IP config on client interface
+ #
+ self.send_and_assert_no_replies(self.pg3, pkts_disc_vrf1,
+ "DHCP config removed VRF 1")
+
+ #
+ # configure an IP address on the client facing interface
+ #
+ self.pg3.config_ip4()
+
+ self.pg3.add_stream(pkts_disc_vrf1)
+ self.pg_enable_capture(self.pg_interfaces)
+ self.pg_start()
+
+ rx = self.pg1.get_capture(1)
+ rx = rx[0]
+ self.verify_relayed_dhcp_discover(rx, self.pg1, src_intf=self.pg3)
+
+ #
+ # Add VSS config
+ # table=1, fib=id=1, oui=4
+ self.vapi.dhcp_proxy_set_vss(1, 1, 4)
+
+ self.pg3.add_stream(pkts_disc_vrf1)
+ self.pg_enable_capture(self.pg_interfaces)
+ self.pg_start()
+
+ rx = self.pg1.get_capture(1)
+ rx = rx[0]
+ self.verify_relayed_dhcp_discover(rx, self.pg1,
+ src_intf=self.pg3,
+ fib_id=1, oui=4)
+
+ #
+ # Add a second DHCP server in VRF 1
+ # expect clients messages to be relay to both configured servers
+ #
+ self.pg1.generate_remote_hosts(2)
+ server_addr2 = socket.inet_pton(AF_INET, self.pg1.remote_hosts[1].ip4)
+
+ self.vapi.dhcp_proxy_config(server_addr2,
+ src_addr,
+ rx_table_id=1,
+ server_table_id=1,
+ is_add=1)
+
+ #
+ # We'll need an ARP entry for the server to send it packets
+ #
+ arp_entry = VppNeighbor(self,
+ self.pg1.sw_if_index,
+ self.pg1.remote_hosts[1].mac,
+ self.pg1.remote_hosts[1].ip4)
+ arp_entry.add_vpp_config()
+
+ #
+ # Send a discover from the client. expect two relayed messages
+ # The frist packet is sent to the second server
+ # We're not enforcing that here, it's just the way it is.
+ #
+ self.pg3.add_stream(pkts_disc_vrf1)
+ self.pg_enable_capture(self.pg_interfaces)
+ self.pg_start()
+
+ rx = self.pg1.get_capture(2)
+
+ option_82 = self.verify_relayed_dhcp_discover(
+ rx[0], self.pg1,
+ src_intf=self.pg3,
+ dst_mac=self.pg1.remote_hosts[1].mac,
+ dst_ip=self.pg1.remote_hosts[1].ip4,
+ fib_id=1, oui=4)
+ self.verify_relayed_dhcp_discover(rx[1], self.pg1,
+ src_intf=self.pg3,
+ fib_id=1, oui=4)
+
+ #
+ # Send both packets back. Client gets both.
+ #
+ p1 = (Ether(dst=self.pg1.local_mac, src=self.pg1.remote_mac) /
+ IP(src=self.pg1.remote_ip4, dst=self.pg1.local_ip4) /
+ UDP(sport=DHCP4_SERVER_PORT, dport=DHCP4_SERVER_PORT) /
+ BOOTP(op=1) /
+ DHCP(options=[('message-type', 'offer'),
+ ('relay_agent_Information', option_82),
+ ('end')]))
+ p2 = (Ether(dst=self.pg1.local_mac, src=self.pg1.remote_mac) /
+ IP(src=self.pg1.remote_hosts[1].ip4, dst=self.pg1.local_ip4) /
+ UDP(sport=DHCP4_SERVER_PORT, dport=DHCP4_SERVER_PORT) /
+ BOOTP(op=1) /
+ DHCP(options=[('message-type', 'offer'),
+ ('relay_agent_Information', option_82),
+ ('end')]))
+ pkts = [p1, p2]
+
+ self.pg1.add_stream(pkts)
+ self.pg_enable_capture(self.pg_interfaces)
+ self.pg_start()
+
+ rx = self.pg3.get_capture(2)
+
+ self.verify_dhcp_offer(rx[0], self.pg3, fib_id=1, oui=4)
+ self.verify_dhcp_offer(rx[1], self.pg3, fib_id=1, oui=4)
+
+ #
+ # Ensure offers from non-servers are dropeed
+ #
+ p2 = (Ether(dst=self.pg1.local_mac, src=self.pg1.remote_mac) /
+ IP(src="8.8.8.8", dst=self.pg1.local_ip4) /
+ UDP(sport=DHCP4_SERVER_PORT, dport=DHCP4_SERVER_PORT) /
+ BOOTP(op=1) /
+ DHCP(options=[('message-type', 'offer'),
+ ('relay_agent_Information', option_82),
+ ('end')]))
+ self.send_and_assert_no_replies(self.pg1, p2,
+ "DHCP offer from non-server")
+
+ #
+ # Ensure only the discover is sent to multiple servers
+ #
+ p_req_vrf1 = (Ether(dst="ff:ff:ff:ff:ff:ff",
+ src=self.pg3.remote_mac) /
+ IP(src="0.0.0.0", dst="255.255.255.255") /
+ UDP(sport=DHCP4_CLIENT_PORT,
+ dport=DHCP4_SERVER_PORT) /
+ BOOTP(op=1) /
+ DHCP(options=[('message-type', 'request'),
+ ('end')]))
+
+ self.pg3.add_stream(p_req_vrf1)
+ self.pg_enable_capture(self.pg_interfaces)
+ self.pg_start()
+
+ rx = self.pg1.get_capture(1)
+
+ #
+ # Remove the second DHCP server
+ #
+ self.vapi.dhcp_proxy_config(server_addr2,
+ src_addr,
+ rx_table_id=1,
+ server_table_id=1,
+ is_add=0)
+
+ #
+ # Test we can still relay with the first
+ #
+ self.pg3.add_stream(pkts_disc_vrf1)
+ self.pg_enable_capture(self.pg_interfaces)
+ self.pg_start()
+
+ rx = self.pg1.get_capture(1)
+ rx = rx[0]
+ self.verify_relayed_dhcp_discover(rx, self.pg1,
+ src_intf=self.pg3,
+ fib_id=1, oui=4)
+
+ #
+ # Remove the VSS config
+ # relayed DHCP has default vlaues in the option.
+ #
+ self.vapi.dhcp_proxy_set_vss(1, 1, 4, is_add=0)
+
+ self.pg3.add_stream(pkts_disc_vrf1)
+ self.pg_enable_capture(self.pg_interfaces)
+ self.pg_start()
+
+ rx = self.pg1.get_capture(1)
+ rx = rx[0]
+ self.verify_relayed_dhcp_discover(rx, self.pg1, src_intf=self.pg3)
+
+ #
+ # remove DHCP config to cleanup
+ #
+ self.vapi.dhcp_proxy_config(server_addr,
+ src_addr,
+ rx_table_id=1,
+ server_table_id=1,
+ is_add=0)
+
+ self.send_and_assert_no_replies(self.pg2, pkts_disc_vrf0,
+ "DHCP cleanup VRF 0")
+ self.send_and_assert_no_replies(self.pg3, pkts_disc_vrf1,
+ "DHCP cleanup VRF 1")
+ self.pg2.unconfig_ip4()
+ self.pg3.unconfig_ip4()
+
+ def test_dhcp6_proxy(self):
+ """ DHCPv6 Proxy"""
+ #
+ # Verify no response to DHCP request without DHCP config
+ #
+ dhcp_solicit_dst = "ff02::1:2"
+ dhcp_solicit_src_vrf0 = mk_ll_addr(self.pg2.remote_mac)
+ dhcp_solicit_src_vrf1 = mk_ll_addr(self.pg3.remote_mac)
+ server_addr_vrf0 = self.pg0.remote_ip6n
+ src_addr_vrf0 = self.pg0.local_ip6n
+ server_addr_vrf1 = self.pg1.remote_ip6n
+ src_addr_vrf1 = self.pg1.local_ip6n
+
+ dmac = in6_getnsmac(inet_pton(socket.AF_INET6, dhcp_solicit_dst))
+ p_solicit_vrf0 = (Ether(dst=dmac, src=self.pg2.remote_mac) /
+ IPv6(src=dhcp_solicit_src_vrf0,
+ dst=dhcp_solicit_dst) /
+ UDP(sport=DHCP6_SERVER_PORT,
+ dport=DHCP6_CLIENT_PORT) /
+ DHCP6_Solicit())
+ p_solicit_vrf1 = (Ether(dst=dmac, src=self.pg3.remote_mac) /
+ IPv6(src=dhcp_solicit_src_vrf1,
+ dst=dhcp_solicit_dst) /
+ UDP(sport=DHCP6_SERVER_PORT,
+ dport=DHCP6_CLIENT_PORT) /
+ DHCP6_Solicit())
+
+ self.send_and_assert_no_replies(self.pg2, p_solicit_vrf0,
+ "DHCP with no configuration")
+ self.send_and_assert_no_replies(self.pg3, p_solicit_vrf1,
+ "DHCP with no configuration")
+
+ #
+ # DHCPv6 config in VRF 0.
+ # Packets still dropped because the client facing interface has no
+ # IPv6 config
+ #
+ self.vapi.dhcp_proxy_config(server_addr_vrf0,
+ src_addr_vrf0,
+ rx_table_id=0,
+ server_table_id=0,
+ is_ipv6=1)
+
+ self.send_and_assert_no_replies(self.pg2, p_solicit_vrf0,
+ "DHCP with no configuration")
+ self.send_and_assert_no_replies(self.pg3, p_solicit_vrf1,
+ "DHCP with no configuration")
+
+ #
+ # configure an IP address on the client facing interface
+ #
+ self.pg2.config_ip6()
+
+ #
+ # Now the DHCP requests are relayed to the server
+ #
+ self.pg2.add_stream(p_solicit_vrf0)
+ self.pg_enable_capture(self.pg_interfaces)
+ self.pg_start()
+
+ rx = self.pg0.get_capture(1)
+
+ self.verify_dhcp6_solicit(rx[0], self.pg0,
+ dhcp_solicit_src_vrf0,
+ self.pg2.remote_mac)
+
+ #
+ # Exception cases for rejected relay responses
+ #
+
+ # 1 - not a relay reply
+ p_adv_vrf0 = (Ether(dst=self.pg0.local_mac, src=self.pg0.remote_mac) /
+ IPv6(dst=self.pg0.local_ip6, src=self.pg0.remote_ip6) /
+ UDP(sport=DHCP6_SERVER_PORT, dport=DHCP6_SERVER_PORT) /
+ DHCP6_Advertise())
+ self.send_and_assert_no_replies(self.pg2, p_adv_vrf0,
+ "DHCP6 not a relay reply")
+
+ # 2 - no relay message option
+ p_adv_vrf0 = (Ether(dst=self.pg0.local_mac, src=self.pg0.remote_mac) /
+ IPv6(dst=self.pg0.local_ip6, src=self.pg0.remote_ip6) /
+ UDP(sport=DHCP6_SERVER_PORT, dport=DHCP6_SERVER_PORT) /
+ DHCP6_RelayReply() /
+ DHCP6_Advertise())
+ self.send_and_assert_no_replies(self.pg2, p_adv_vrf0,
+ "DHCP not a relay message")
+
+ # 3 - no circuit ID
+ p_adv_vrf0 = (Ether(dst=self.pg0.local_mac, src=self.pg0.remote_mac) /
+ IPv6(dst=self.pg0.local_ip6, src=self.pg0.remote_ip6) /
+ UDP(sport=DHCP6_SERVER_PORT, dport=DHCP6_SERVER_PORT) /
+ DHCP6_RelayReply() /
+ DHCP6OptRelayMsg(optlen=0) /
+ DHCP6_Advertise())
+ self.send_and_assert_no_replies(self.pg2, p_adv_vrf0,
+ "DHCP6 no circuit ID")
+ # 4 - wrong circuit ID
+ p_adv_vrf0 = (Ether(dst=self.pg0.local_mac, src=self.pg0.remote_mac) /
+ IPv6(dst=self.pg0.local_ip6, src=self.pg0.remote_ip6) /
+ UDP(sport=DHCP6_SERVER_PORT, dport=DHCP6_SERVER_PORT) /
+ DHCP6_RelayReply() /
+ DHCP6OptIfaceId(optlen=4, ifaceid='\x00\x00\x00\x05') /
+ DHCP6OptRelayMsg(optlen=0) /
+ DHCP6_Advertise())
+ self.send_and_assert_no_replies(self.pg2, p_adv_vrf0,
+ "DHCP6 wrong circuit ID")
+
+ #
+ # Send the relay response (the advertisement)
+ # - no peer address
+ p_adv_vrf0 = (Ether(dst=self.pg0.local_mac, src=self.pg0.remote_mac) /
+ IPv6(dst=self.pg0.local_ip6, src=self.pg0.remote_ip6) /
+ UDP(sport=DHCP6_SERVER_PORT, dport=DHCP6_SERVER_PORT) /
+ DHCP6_RelayReply() /
+ DHCP6OptIfaceId(optlen=4, ifaceid='\x00\x00\x00\x03') /
+ DHCP6OptRelayMsg(optlen=0) /
+ DHCP6_Advertise(trid=1) /
+ DHCP6OptStatusCode(statuscode=0))
+ pkts_adv_vrf0 = [p_adv_vrf0]
+
+ self.pg0.add_stream(pkts_adv_vrf0)
+ self.pg_enable_capture(self.pg_interfaces)
+ self.pg_start()
+
+ rx = self.pg2.get_capture(1)
+
+ self.verify_dhcp6_advert(rx[0], self.pg2, "::")
+
+ #
+ # Send the relay response (the advertisement)
+ # - with peer address
+ p_adv_vrf0 = (Ether(dst=self.pg0.local_mac, src=self.pg0.remote_mac) /
+ IPv6(dst=self.pg0.local_ip6, src=self.pg0.remote_ip6) /
+ UDP(sport=DHCP6_SERVER_PORT, dport=DHCP6_SERVER_PORT) /
+ DHCP6_RelayReply(peeraddr=dhcp_solicit_src_vrf0) /
+ DHCP6OptIfaceId(optlen=4, ifaceid='\x00\x00\x00\x03') /
+ DHCP6OptRelayMsg(optlen=0) /
+ DHCP6_Advertise(trid=1) /
+ DHCP6OptStatusCode(statuscode=0))
+ pkts_adv_vrf0 = [p_adv_vrf0]
+
+ self.pg0.add_stream(pkts_adv_vrf0)
+ self.pg_enable_capture(self.pg_interfaces)
+ self.pg_start()
+
+ rx = self.pg2.get_capture(1)
+
+ self.verify_dhcp6_advert(rx[0], self.pg2, dhcp_solicit_src_vrf0)
+
+ #
+ # Add all the config for VRF 1
+ #
+ self.vapi.dhcp_proxy_config(server_addr_vrf1,
+ src_addr_vrf1,
+ rx_table_id=1,
+ server_table_id=1,
+ is_ipv6=1)
+ self.pg3.config_ip6()
+
+ #
+ # VRF 1 solicit
+ #
+ self.pg3.add_stream(p_solicit_vrf1)
+ self.pg_enable_capture(self.pg_interfaces)
+ self.pg_start()
+
+ rx = self.pg1.get_capture(1)
+
+ self.verify_dhcp6_solicit(rx[0], self.pg1,
+ dhcp_solicit_src_vrf1,
+ self.pg3.remote_mac)
+
+ #
+ # VRF 1 Advert
+ #
+ p_adv_vrf1 = (Ether(dst=self.pg1.local_mac, src=self.pg1.remote_mac) /
+ IPv6(dst=self.pg1.local_ip6, src=self.pg1.remote_ip6) /
+ UDP(sport=DHCP6_SERVER_PORT, dport=DHCP6_SERVER_PORT) /
+ DHCP6_RelayReply(peeraddr=dhcp_solicit_src_vrf1) /
+ DHCP6OptIfaceId(optlen=4, ifaceid='\x00\x00\x00\x04') /
+ DHCP6OptRelayMsg(optlen=0) /
+ DHCP6_Advertise(trid=1) /
+ DHCP6OptStatusCode(statuscode=0))
+ pkts_adv_vrf1 = [p_adv_vrf1]
+
+ self.pg1.add_stream(pkts_adv_vrf1)
+ self.pg_enable_capture(self.pg_interfaces)
+ self.pg_start()
+
+ rx = self.pg3.get_capture(1)
+
+ self.verify_dhcp6_advert(rx[0], self.pg3, dhcp_solicit_src_vrf1)
+
+ #
+ # Add VSS config
+ # table=1, fib=id=1, oui=4
+ self.vapi.dhcp_proxy_set_vss(1, 1, 4, is_ip6=1)
+
+ self.pg3.add_stream(p_solicit_vrf1)
+ self.pg_enable_capture(self.pg_interfaces)
+ self.pg_start()
+
+ rx = self.pg1.get_capture(1)
+
+ self.verify_dhcp6_solicit(rx[0], self.pg1,
+ dhcp_solicit_src_vrf1,
+ self.pg3.remote_mac,
+ fib_id=1,
+ oui=4)
+
+ #
+ # Remove the VSS config
+ # relayed DHCP has default vlaues in the option.
+ #
+ self.vapi.dhcp_proxy_set_vss(1, 1, 4, is_ip6=1, is_add=0)
+
+ self.pg3.add_stream(p_solicit_vrf1)
+ self.pg_enable_capture(self.pg_interfaces)
+ self.pg_start()
+
+ rx = self.pg1.get_capture(1)
+
+ self.verify_dhcp6_solicit(rx[0], self.pg1,
+ dhcp_solicit_src_vrf1,
+ self.pg3.remote_mac)
+
+ #
+ # Add a second DHCP server in VRF 1
+ # expect clients messages to be relay to both configured servers
+ #
+ self.pg1.generate_remote_hosts(2)
+ server_addr2 = socket.inet_pton(AF_INET6, self.pg1.remote_hosts[1].ip6)
+
+ self.vapi.dhcp_proxy_config(server_addr2,
+ src_addr_vrf1,
+ rx_table_id=1,
+ server_table_id=1,
+ is_ipv6=1)
+
+ #
+ # We'll need an ND entry for the server to send it packets
+ #
+ nd_entry = VppNeighbor(self,
+ self.pg1.sw_if_index,
+ self.pg1.remote_hosts[1].mac,
+ self.pg1.remote_hosts[1].ip6,
+ af=AF_INET6)
+ nd_entry.add_vpp_config()
+
+ #
+ # Send a discover from the client. expect two relayed messages
+ # The frist packet is sent to the second server
+ # We're not enforcing that here, it's just the way it is.
+ #
+ self.pg3.add_stream(p_solicit_vrf1)
+ self.pg_enable_capture(self.pg_interfaces)
+ self.pg_start()
+
+ rx = self.pg1.get_capture(2)
+
+ self.verify_dhcp6_solicit(rx[0], self.pg1,
+ dhcp_solicit_src_vrf1,
+ self.pg3.remote_mac)
+ self.verify_dhcp6_solicit(rx[1], self.pg1,
+ dhcp_solicit_src_vrf1,
+ self.pg3.remote_mac,
+ dst_mac=self.pg1.remote_hosts[1].mac,
+ dst_ip=self.pg1.remote_hosts[1].ip6)
+
+ #
+ # Send both packets back. Client gets both.
+ #
+ p1 = (Ether(dst=self.pg1.local_mac, src=self.pg1.remote_mac) /
+ IPv6(dst=self.pg1.local_ip6, src=self.pg1.remote_ip6) /
+ UDP(sport=DHCP6_SERVER_PORT, dport=DHCP6_SERVER_PORT) /
+ DHCP6_RelayReply(peeraddr=dhcp_solicit_src_vrf1) /
+ DHCP6OptIfaceId(optlen=4, ifaceid='\x00\x00\x00\x04') /
+ DHCP6OptRelayMsg(optlen=0) /
+ DHCP6_Advertise(trid=1) /
+ DHCP6OptStatusCode(statuscode=0))
+ p2 = (Ether(dst=self.pg1.local_mac, src=self.pg1.remote_hosts[1].mac) /
+ IPv6(dst=self.pg1.local_ip6, src=self.pg1._remote_hosts[1].ip6) /
+ UDP(sport=DHCP6_SERVER_PORT, dport=DHCP6_SERVER_PORT) /
+ DHCP6_RelayReply(peeraddr=dhcp_solicit_src_vrf1) /
+ DHCP6OptIfaceId(optlen=4, ifaceid='\x00\x00\x00\x04') /
+ DHCP6OptRelayMsg(optlen=0) /
+ DHCP6_Advertise(trid=1) /
+ DHCP6OptStatusCode(statuscode=0))
+
+ pkts = [p1, p2]
+
+ self.pg1.add_stream(pkts)
+ self.pg_enable_capture(self.pg_interfaces)
+ self.pg_start()
+
+ rx = self.pg3.get_capture(2)
+
+ self.verify_dhcp6_advert(rx[0], self.pg3, dhcp_solicit_src_vrf1)
+ self.verify_dhcp6_advert(rx[1], self.pg3, dhcp_solicit_src_vrf1)
+
+ #
+ # Ensure only solicit messages are duplicated
+ #
+ p_request_vrf1 = (Ether(dst=dmac, src=self.pg3.remote_mac) /
+ IPv6(src=dhcp_solicit_src_vrf1,
+ dst=dhcp_solicit_dst) /
+ UDP(sport=DHCP6_SERVER_PORT,
+ dport=DHCP6_CLIENT_PORT) /
+ DHCP6_Request())
+
+ self.pg3.add_stream(p_request_vrf1)
+ self.pg_enable_capture(self.pg_interfaces)
+ self.pg_start()
+
+ rx = self.pg1.get_capture(1)
+
+ #
+ # Test we drop DHCP packets from addresses that are not configured as
+ # DHCP servers
+ #
+ p2 = (Ether(dst=self.pg1.local_mac, src=self.pg1.remote_hosts[1].mac) /
+ IPv6(dst=self.pg1.local_ip6, src="3001::1") /
+ UDP(sport=DHCP6_SERVER_PORT, dport=DHCP6_SERVER_PORT) /
+ DHCP6_RelayReply(peeraddr=dhcp_solicit_src_vrf1) /
+ DHCP6OptIfaceId(optlen=4, ifaceid='\x00\x00\x00\x04') /
+ DHCP6OptRelayMsg(optlen=0) /
+ DHCP6_Advertise(trid=1) /
+ DHCP6OptStatusCode(statuscode=0))
+ self.send_and_assert_no_replies(self.pg1, p2,
+ "DHCP6 not from server")
+
+ #
+ # Remove the second DHCP server
+ #
+ self.vapi.dhcp_proxy_config(server_addr2,
+ src_addr_vrf1,
+ rx_table_id=1,
+ server_table_id=1,
+ is_ipv6=1,
+ is_add=0)
+
+ #
+ # Test we can still relay with the first
+ #
+ self.pg3.add_stream(p_solicit_vrf1)
+ self.pg_enable_capture(self.pg_interfaces)
+ self.pg_start()
+
+ rx = self.pg1.get_capture(1)
+
+ self.verify_dhcp6_solicit(rx[0], self.pg1,
+ dhcp_solicit_src_vrf1,
+ self.pg3.remote_mac)
+
+ #
+ # Cleanup
+ #
+ self.vapi.dhcp_proxy_config(server_addr_vrf1,
+ src_addr_vrf1,
+ rx_table_id=1,
+ server_table_id=1,
+ is_ipv6=1,
+ is_add=0)
+ self.vapi.dhcp_proxy_config(server_addr_vrf0,
+ src_addr_vrf0,
+ rx_table_id=0,
+ server_table_id=0,
+ is_ipv6=1,
+ is_add=0)
+
+ # duplicate delete
+ self.vapi.dhcp_proxy_config(server_addr_vrf0,
+ src_addr_vrf0,
+ rx_table_id=0,
+ server_table_id=0,
+ is_ipv6=1,
+ is_add=0)
+ self.pg2.unconfig_ip6()
+ self.pg3.unconfig_ip6()
+
+ def test_dhcp_client(self):
+ """ DHCP Client"""
+
+ hostname = 'universal-dp'
+
+ self.pg_enable_capture(self.pg_interfaces)
+
+ #
+ # Configure DHCP client on PG2 and capture the discover sent
+ #
+ self.vapi.dhcp_client(self.pg2.sw_if_index, hostname)
+
+ rx = self.pg2.get_capture(1)
+
+ self.verify_orig_dhcp_discover(rx[0], self.pg2, hostname)
+
+ #
+ # Sned back on offer, expect the request
+ #
+ p_offer = (Ether(dst=self.pg2.local_mac, src=self.pg2.remote_mac) /
+ IP(src=self.pg2.remote_ip4, dst="255.255.255.255") /
+ UDP(sport=DHCP4_SERVER_PORT, dport=DHCP4_CLIENT_PORT) /
+ BOOTP(op=1, yiaddr=self.pg2.local_ip4) /
+ DHCP(options=[('message-type', 'offer'),
+ ('server_id', self.pg2.remote_ip4),
+ ('end')]))
+
+ self.pg2.add_stream(p_offer)
+ self.pg_enable_capture(self.pg_interfaces)
+ self.pg_start()
+
+ rx = self.pg2.get_capture(1)
+ self.verify_orig_dhcp_request(rx[0], self.pg2, hostname,
+ self.pg2.local_ip4)
+
+ #
+ # Send an acknowloedgement
+ #
+ p_ack = (Ether(dst=self.pg2.local_mac, src=self.pg2.remote_mac) /
+ IP(src=self.pg2.remote_ip4, dst="255.255.255.255") /
+ UDP(sport=DHCP4_SERVER_PORT, dport=DHCP4_CLIENT_PORT) /
+ BOOTP(op=1, yiaddr=self.pg2.local_ip4) /
+ DHCP(options=[('message-type', 'ack'),
+ ('subnet_mask', "255.255.255.0"),
+ ('router', self.pg2.remote_ip4),
+ ('server_id', self.pg2.remote_ip4),
+ ('lease_time', 43200),
+ ('end')]))
+
+ self.pg2.add_stream(p_ack)
+ self.pg_enable_capture(self.pg_interfaces)
+ self.pg_start()
+
+ #
+ # We'll get an ARP request for the router address
+ #
+ rx = self.pg2.get_capture(1)
+
+ self.assertEqual(rx[0][ARP].pdst, self.pg2.remote_ip4)
+ self.pg_enable_capture(self.pg_interfaces)
+
+ #
+ # At the end of this procedure there should be a connected route
+ # in the FIB
+ #
+ self.assertTrue(find_route(self, self.pg2.local_ip4, 24))
+ self.assertTrue(find_route(self, self.pg2.local_ip4, 32))
+
+ # remove the left over ARP entry
+ self.vapi.ip_neighbor_add_del(self.pg2.sw_if_index,
+ mactobinary(self.pg2.remote_mac),
+ self.pg2.remote_ip4,
+ is_add=0)
+ #
+ # remove the DHCP config
+ #
+ self.vapi.dhcp_client(self.pg2.sw_if_index, hostname, is_add=0)
+
+ #
+ # and now the route should be gone
+ #
+ self.assertFalse(find_route(self, self.pg2.local_ip4, 32))
+ self.assertFalse(find_route(self, self.pg2.local_ip4, 24))
+
+ #
+ # Start the procedure again. this time have VPP send the client-ID
+ #
+ self.pg2.admin_down()
+ self.sleep(1)
+ self.pg2.admin_up()
+ self.vapi.dhcp_client(self.pg2.sw_if_index, hostname,
+ client_id=self.pg2.local_mac)
+
+ rx = self.pg2.get_capture(1)
+
+ self.verify_orig_dhcp_discover(rx[0], self.pg2, hostname,
+ self.pg2.local_mac)
+
+ self.pg2.add_stream(p_offer)
+ self.pg_enable_capture(self.pg_interfaces)
+ self.pg_start()
+
+ rx = self.pg2.get_capture(1)
+ self.verify_orig_dhcp_request(rx[0], self.pg2, hostname,
+ self.pg2.local_ip4)
+
+ #
+ # unicast the ack to the offered address
+ #
+ p_ack = (Ether(dst=self.pg2.local_mac, src=self.pg2.remote_mac) /
+ IP(src=self.pg2.remote_ip4, dst=self.pg2.local_ip4) /
+ UDP(sport=DHCP4_SERVER_PORT, dport=DHCP4_CLIENT_PORT) /
+ BOOTP(op=1, yiaddr=self.pg2.local_ip4) /
+ DHCP(options=[('message-type', 'ack'),
+ ('subnet_mask', "255.255.255.0"),
+ ('router', self.pg2.remote_ip4),
+ ('server_id', self.pg2.remote_ip4),
+ ('lease_time', 43200),
+ ('end')]))
+
+ self.pg2.add_stream(p_ack)
+ self.pg_enable_capture(self.pg_interfaces)
+ self.pg_start()
+
+ #
+ # At the end of this procedure there should be a connected route
+ # in the FIB
+ #
+ self.assertTrue(find_route(self, self.pg2.local_ip4, 32))
+ self.assertTrue(find_route(self, self.pg2.local_ip4, 24))
+
+ #
+ # remove the DHCP config
+ #
+ self.vapi.dhcp_client(self.pg2.sw_if_index, hostname, is_add=0)
+
+ self.assertFalse(find_route(self, self.pg2.local_ip4, 32))
+ self.assertFalse(find_route(self, self.pg2.local_ip4, 24))
+
+
+if __name__ == '__main__':
+ unittest.main(testRunner=VppTestRunner)
diff --git a/test/test_fib.py b/test/test_fib.py
new file mode 100644
index 00000000..1e28e8f8
--- /dev/null
+++ b/test/test_fib.py
@@ -0,0 +1,30 @@
+#!/usr/bin/env python
+
+import unittest
+
+from framework import VppTestCase, VppTestRunner
+
+
+class TestFIB(VppTestCase):
+ """ FIB Test Case """
+
+ @classmethod
+ def setUpClass(cls):
+ super(TestFIB, cls).setUpClass()
+
+ def setUp(self):
+ super(TestFIB, self).setUp()
+
+ def tearDown(self):
+ super(TestFIB, self).tearDown()
+
+ def test_fib(self):
+ """ FIB Unit Tests """
+ error = self.vapi.cli("test fib")
+
+ if error:
+ self.logger.critical(error)
+ self.assertEqual(error.find("Failed"), -1)
+
+if __name__ == '__main__':
+ unittest.main(testRunner=VppTestRunner)
diff --git a/test/test_flowprobe.py b/test/test_flowprobe.py
new file mode 100644
index 00000000..df6b4230
--- /dev/null
+++ b/test/test_flowprobe.py
@@ -0,0 +1,1021 @@
+#!/usr/bin/env python
+import random
+import socket
+import unittest
+import time
+import re
+
+from scapy.packet import Raw
+from scapy.layers.l2 import Ether
+from scapy.layers.inet import IP, TCP, UDP
+from scapy.layers.inet6 import IPv6
+
+from framework import VppTestCase, VppTestRunner, running_extended_tests
+from vpp_object import VppObject
+from vpp_pg_interface import CaptureTimeoutError
+from util import ppp
+from ipfix import IPFIX, Set, Template, Data, IPFIXDecoder
+from vpp_ip_route import VppIpRoute, VppRoutePath
+
+
+class VppCFLOW(VppObject):
+ """CFLOW object for IPFIX exporter and Flowprobe feature"""
+
+ def __init__(self, test, intf='pg2', active=0, passive=0, timeout=100,
+ mtu=1024, datapath='l2', layer='l2 l3 l4'):
+ self._test = test
+ self._intf = intf
+ self._active = active
+ if passive == 0 or passive < active:
+ self._passive = active+1
+ else:
+ self._passive = passive
+ self._datapath = datapath # l2 ip4 ip6
+ self._collect = layer # l2 l3 l4
+ self._timeout = timeout
+ self._mtu = mtu
+ self._configured = False
+
+ def add_vpp_config(self):
+ self.enable_exporter()
+ self._test.vapi.ppcli("flowprobe params record %s active %s "
+ "passive %s" % (self._collect, self._active,
+ self._passive))
+ self.enable_flowprobe_feature()
+ self._test.vapi.cli("ipfix flush")
+ self._configured = True
+
+ def remove_vpp_config(self):
+ self.disable_exporter()
+ self.disable_flowprobe_feature()
+ self._test.vapi.cli("ipfix flush")
+ self._configured = False
+
+ def enable_exporter(self):
+ self._test.vapi.set_ipfix_exporter(
+ collector_address=self._test.pg0.remote_ip4n,
+ src_address=self._test.pg0.local_ip4n,
+ path_mtu=self._mtu,
+ template_interval=self._timeout)
+
+ def enable_flowprobe_feature(self):
+ self._test.vapi.ppcli("flowprobe feature add-del %s %s" %
+ (self._intf, self._datapath))
+
+ def disable_exporter(self):
+ self._test.vapi.cli("set ipfix exporter collector 0.0.0.0")
+
+ def disable_flowprobe_feature(self):
+ self._test.vapi.cli("flowprobe feature add-del %s %s disable" %
+ (self._intf, self._datapath))
+
+ def object_id(self):
+ return "ipfix-collector-%s" % (self._src, self.dst)
+
+ def query_vpp_config(self):
+ return self._configured
+
+ def verify_templates(self, decoder=None, timeout=1, count=3):
+ templates = []
+ p = self._test.wait_for_cflow_packet(self._test.collector, 2, timeout)
+ self._test.assertTrue(p.haslayer(IPFIX))
+ if decoder is not None and p.haslayer(Template):
+ templates.append(p[Template].templateID)
+ decoder.add_template(p.getlayer(Template))
+ if count > 1:
+ p = self._test.wait_for_cflow_packet(self._test.collector, 2)
+ self._test.assertTrue(p.haslayer(IPFIX))
+ if decoder is not None and p.haslayer(Template):
+ templates.append(p[Template].templateID)
+ decoder.add_template(p.getlayer(Template))
+ if count > 2:
+ p = self._test.wait_for_cflow_packet(self._test.collector, 2)
+ self._test.assertTrue(p.haslayer(IPFIX))
+ if decoder is not None and p.haslayer(Template):
+ templates.append(p[Template].templateID)
+ decoder.add_template(p.getlayer(Template))
+ return templates
+
+
+class MethodHolder(VppTestCase):
+ """ Flow-per-packet plugin: test L2, IP4, IP6 reporting """
+
+ # Test variables
+ debug_print = False
+ max_number_of_packets = 10
+ pkts = []
+
+ @classmethod
+ def setUpClass(cls):
+ """
+ Perform standard class setup (defined by class method setUpClass in
+ class VppTestCase) before running the test case, set test case related
+ variables and configure VPP.
+ """
+ super(MethodHolder, cls).setUpClass()
+ try:
+ # Create pg interfaces
+ cls.create_pg_interfaces(range(9))
+
+ # Packet sizes
+ cls.pg_if_packet_sizes = [64, 512, 1518, 9018]
+
+ # Create BD with MAC learning and unknown unicast flooding disabled
+ # and put interfaces to this BD
+ cls.vapi.bridge_domain_add_del(bd_id=1, uu_flood=1, learn=1)
+ cls.vapi.sw_interface_set_l2_bridge(cls.pg1._sw_if_index, bd_id=1)
+ cls.vapi.sw_interface_set_l2_bridge(cls.pg2._sw_if_index, bd_id=1)
+
+ # Set up all interfaces
+ for i in cls.pg_interfaces:
+ i.admin_up()
+
+ cls.pg0.config_ip4()
+ cls.pg0.configure_ipv4_neighbors()
+ cls.collector = cls.pg0
+
+ cls.pg1.config_ip4()
+ cls.pg1.resolve_arp()
+ cls.pg2.config_ip4()
+ cls.pg2.resolve_arp()
+ cls.pg3.config_ip4()
+ cls.pg3.resolve_arp()
+ cls.pg4.config_ip4()
+ cls.pg4.resolve_arp()
+ cls.pg7.config_ip4()
+ cls.pg8.config_ip4()
+ cls.pg8.configure_ipv4_neighbors()
+
+ cls.pg5.config_ip6()
+ cls.pg5.resolve_ndp()
+ cls.pg5.disable_ipv6_ra()
+ cls.pg6.config_ip6()
+ cls.pg6.resolve_ndp()
+ cls.pg6.disable_ipv6_ra()
+ except Exception:
+ super(MethodHolder, cls).tearDownClass()
+ raise
+
+ def create_stream(self, src_if=None, dst_if=None, packets=None,
+ size=None, ip_ver='v4'):
+ """Create a packet stream to tickle the plugin
+
+ :param VppInterface src_if: Source interface for packet stream
+ :param VppInterface src_if: Dst interface for packet stream
+ """
+ if src_if is None:
+ src_if = self.pg1
+ if dst_if is None:
+ dst_if = self.pg2
+ self.pkts = []
+ if packets is None:
+ packets = random.randint(1, self.max_number_of_packets)
+ pkt_size = size
+ for p in range(0, packets):
+ if size is None:
+ pkt_size = random.choice(self.pg_if_packet_sizes)
+ info = self.create_packet_info(src_if, dst_if)
+ payload = self.info_to_payload(info)
+ p = Ether(src=src_if.remote_mac, dst=src_if.local_mac)
+ if ip_ver == 'v4':
+ p /= IP(src=src_if.remote_ip4, dst=dst_if.remote_ip4)
+ else:
+ p /= IPv6(src=src_if.remote_ip6, dst=dst_if.remote_ip6)
+ p /= UDP(sport=1234, dport=4321)
+ p /= Raw(payload)
+ info.data = p.copy()
+ self.extend_packet(p, pkt_size)
+ self.pkts.append(p)
+
+ def verify_cflow_data(self, decoder, capture, cflow):
+ octets = 0
+ packets = 0
+ for p in capture:
+ octets += p[IP].len
+ packets += 1
+ if cflow.haslayer(Data):
+ data = decoder.decode_data_set(cflow.getlayer(Set))
+ for record in data:
+ self.assertEqual(int(record[1].encode('hex'), 16), octets)
+ self.assertEqual(int(record[2].encode('hex'), 16), packets)
+
+ def send_packets(self, src_if=None, dst_if=None):
+ if src_if is None:
+ src_if = self.pg1
+ if dst_if is None:
+ dst_if = self.pg2
+ self.pg_enable_capture([dst_if])
+ src_if.add_stream(self.pkts)
+ self.pg_start()
+ return dst_if.get_capture(len(self.pkts))
+
+ def verify_cflow_data_detail(self, decoder, capture, cflow,
+ data_set={1: 'octets', 2: 'packets'},
+ ip_ver='v4'):
+ if self.debug_print:
+ print capture[0].show()
+ if cflow.haslayer(Data):
+ data = decoder.decode_data_set(cflow.getlayer(Set))
+ if self.debug_print:
+ print data
+ if ip_ver == 'v4':
+ ip_layer = capture[0][IP]
+ else:
+ ip_layer = capture[0][IPv6]
+ if data_set is not None:
+ for record in data:
+ # skip flow if in/out gress interface is 0
+ if int(record[10].encode('hex'), 16) == 0:
+ continue
+ if int(record[14].encode('hex'), 16) == 0:
+ continue
+
+ for field in data_set:
+ if field not in record.keys():
+ continue
+ value = data_set[field]
+ if value == 'octets':
+ value = ip_layer.len
+ if ip_ver == 'v6':
+ value += 40 # ??? is this correct
+ elif value == 'packets':
+ value = 1
+ elif value == 'src_ip':
+ if ip_ver == 'v4':
+ ip = socket.inet_pton(socket.AF_INET,
+ ip_layer.src)
+ else:
+ ip = socket.inet_pton(socket.AF_INET6,
+ ip_layer.src)
+ value = int(ip.encode('hex'), 16)
+ elif value == 'dst_ip':
+ if ip_ver == 'v4':
+ ip = socket.inet_pton(socket.AF_INET,
+ ip_layer.dst)
+ else:
+ ip = socket.inet_pton(socket.AF_INET6,
+ ip_layer.dst)
+ value = int(ip.encode('hex'), 16)
+ elif value == 'sport':
+ value = int(capture[0][UDP].sport)
+ elif value == 'dport':
+ value = int(capture[0][UDP].dport)
+ self.assertEqual(int(record[field].encode('hex'), 16),
+ value)
+
+ def verify_cflow_data_notimer(self, decoder, capture, cflows):
+ idx = 0
+ for cflow in cflows:
+ if cflow.haslayer(Data):
+ data = decoder.decode_data_set(cflow.getlayer(Set))
+ else:
+ raise Exception("No CFLOW data")
+
+ for rec in data:
+ p = capture[idx]
+ idx += 1
+ self.assertEqual(p[IP].len, int(rec[1].encode('hex'), 16))
+ self.assertEqual(1, int(rec[2].encode('hex'), 16))
+ self.assertEqual(len(capture), idx)
+
+ def wait_for_cflow_packet(self, collector_intf, set_id=2, timeout=1,
+ expected=True):
+ """ wait for CFLOW packet and verify its correctness
+
+ :param timeout: how long to wait
+
+ :returns: tuple (packet, time spent waiting for packet)
+ """
+ self.logger.info("IPFIX: Waiting for CFLOW packet")
+ deadline = time.time() + timeout
+ counter = 0
+ # self.logger.debug(self.vapi.ppcli("show flow table"))
+ while True:
+ counter += 1
+ # sanity check
+ self.assert_in_range(counter, 0, 100, "number of packets ignored")
+ time_left = deadline - time.time()
+ try:
+ if time_left < 0 and expected:
+ # self.logger.debug(self.vapi.ppcli("show flow table"))
+ raise CaptureTimeoutError(
+ "Packet did not arrive within timeout")
+ p = collector_intf.wait_for_packet(timeout=time_left)
+ except CaptureTimeoutError:
+ if expected:
+ # self.logger.debug(self.vapi.ppcli("show flow table"))
+ raise CaptureTimeoutError(
+ "Packet did not arrive within timeout")
+ else:
+ return
+ if not expected:
+ raise CaptureTimeoutError("Packet arrived even not expected")
+ self.assertEqual(p[Set].setID, set_id)
+ # self.logger.debug(self.vapi.ppcli("show flow table"))
+ self.logger.debug(ppp("IPFIX: Got packet:", p))
+ break
+ return p
+
+
+class Flowprobe(MethodHolder):
+ """Template verification, timer tests"""
+
+ def test_0001(self):
+ """ timer less than template timeout"""
+ self.logger.info("FFP_TEST_START_0001")
+ self.pg_enable_capture(self.pg_interfaces)
+ self.pkts = []
+
+ ipfix = VppCFLOW(test=self, active=2)
+ ipfix.add_vpp_config()
+
+ ipfix_decoder = IPFIXDecoder()
+ # template packet should arrive immediately
+ templates = ipfix.verify_templates(ipfix_decoder)
+
+ self.create_stream(packets=1)
+ self.send_packets()
+ capture = self.pg2.get_capture(1)
+
+ # make sure the one packet we expect actually showed up
+ cflow = self.wait_for_cflow_packet(self.collector, templates[1], 15)
+ self.verify_cflow_data(ipfix_decoder, capture, cflow)
+
+ ipfix.remove_vpp_config()
+ self.logger.info("FFP_TEST_FINISH_0001")
+
+ def test_0002(self):
+ """ timer greater than template timeout"""
+ self.logger.info("FFP_TEST_START_0002")
+ self.pg_enable_capture(self.pg_interfaces)
+ self.pkts = []
+
+ ipfix = VppCFLOW(test=self, timeout=3, active=4)
+ ipfix.add_vpp_config()
+
+ ipfix_decoder = IPFIXDecoder()
+ # template packet should arrive immediately
+ ipfix.verify_templates()
+
+ self.create_stream(packets=2)
+ self.send_packets()
+ capture = self.pg2.get_capture(2)
+
+ # next set of template packet should arrive after 20 seconds
+ # template packet should arrive within 20 s
+ templates = ipfix.verify_templates(ipfix_decoder, timeout=5)
+
+ # make sure the one packet we expect actually showed up
+ cflow = self.wait_for_cflow_packet(self.collector, templates[1], 15)
+ self.verify_cflow_data(ipfix_decoder, capture, cflow)
+
+ ipfix.remove_vpp_config()
+ self.logger.info("FFP_TEST_FINISH_0002")
+
+ def test_cflow_packet(self):
+ """verify cflow packet fields"""
+ self.logger.info("FFP_TEST_START_0000")
+ self.pg_enable_capture(self.pg_interfaces)
+ self.pkts = []
+
+ ipfix = VppCFLOW(test=self, intf='pg8', datapath="ip4",
+ layer='l2 l3 l4', active=2)
+ ipfix.add_vpp_config()
+
+ route_9001 = VppIpRoute(self, "9.0.0.0", 24,
+ [VppRoutePath(self.pg8._remote_hosts[0].ip4,
+ self.pg8.sw_if_index)])
+ route_9001.add_vpp_config()
+
+ ipfix_decoder = IPFIXDecoder()
+ templates = ipfix.verify_templates(ipfix_decoder, count=1)
+
+ self.pkts = [(Ether(dst=self.pg7.local_mac,
+ src=self.pg7.remote_mac) /
+ IP(src=self.pg7.remote_ip4, dst="9.0.0.100") /
+ TCP(sport=1234, dport=4321, flags=80) /
+ Raw('\xa5' * 100))]
+
+ nowUTC = int(time.time())
+ nowUNIX = nowUTC+2208988800
+ self.send_packets(src_if=self.pg7, dst_if=self.pg8)
+
+ cflow = self.wait_for_cflow_packet(self.collector, templates[0], 10)
+ self.collector.get_capture(2)
+
+ if cflow[0].haslayer(IPFIX):
+ self.assertEqual(cflow[IPFIX].version, 10)
+ self.assertEqual(cflow[IPFIX].observationDomainID, 1)
+ self.assertEqual(cflow[IPFIX].sequenceNumber, 0)
+ self.assertAlmostEqual(cflow[IPFIX].exportTime, nowUTC, delta=5)
+ if cflow.haslayer(Data):
+ record = ipfix_decoder.decode_data_set(cflow[0].getlayer(Set))[0]
+ # ingress interface
+ self.assertEqual(int(record[10].encode('hex'), 16), 8)
+ # egress interface
+ self.assertEqual(int(record[14].encode('hex'), 16), 9)
+ # packets
+ self.assertEqual(int(record[2].encode('hex'), 16), 1)
+ # src mac
+ self.assertEqual(':'.join(re.findall('..', record[56].encode(
+ 'hex'))), self.pg8.local_mac)
+ # dst mac
+ self.assertEqual(':'.join(re.findall('..', record[80].encode(
+ 'hex'))), self.pg8.remote_mac)
+ flowTimestamp = int(record[156].encode('hex'), 16) >> 32
+ # flow start timestamp
+ self.assertAlmostEqual(flowTimestamp, nowUNIX, delta=1)
+ flowTimestamp = int(record[157].encode('hex'), 16) >> 32
+ # flow end timestamp
+ self.assertAlmostEqual(flowTimestamp, nowUNIX, delta=1)
+ # ethernet type
+ self.assertEqual(int(record[256].encode('hex'), 16), 8)
+ # src ip
+ self.assertEqual('.'.join(re.findall('..', record[8].encode(
+ 'hex'))),
+ '.'.join('{:02x}'.format(int(n)) for n in
+ self.pg7.remote_ip4.split('.')))
+ # dst ip
+ self.assertEqual('.'.join(re.findall('..', record[12].encode(
+ 'hex'))),
+ '.'.join('{:02x}'.format(int(n)) for n in
+ "9.0.0.100".split('.')))
+ # protocol (TCP)
+ self.assertEqual(int(record[4].encode('hex'), 16), 6)
+ # src port
+ self.assertEqual(int(record[7].encode('hex'), 16), 1234)
+ # dst port
+ self.assertEqual(int(record[11].encode('hex'), 16), 4321)
+ # tcp flags
+ self.assertEqual(int(record[6].encode('hex'), 16), 80)
+
+ ipfix.remove_vpp_config()
+ self.logger.info("FFP_TEST_FINISH_0000")
+
+
+class Datapath(MethodHolder):
+ """collect information on Ethernet, IP4 and IP6 datapath (no timers)"""
+
+ def test_templatesL2(self):
+ """ verify template on L2 datapath"""
+ self.logger.info("FFP_TEST_START_0000")
+ self.pg_enable_capture(self.pg_interfaces)
+
+ ipfix = VppCFLOW(test=self, layer='l2')
+ ipfix.add_vpp_config()
+
+ # template packet should arrive immediately
+ self.vapi.cli("ipfix flush")
+ ipfix.verify_templates(timeout=3, count=1)
+ self.collector.get_capture(1)
+
+ ipfix.remove_vpp_config()
+ self.logger.info("FFP_TEST_FINISH_0000")
+
+ def test_L2onL2(self):
+ """ L2 data on L2 datapath"""
+ self.logger.info("FFP_TEST_START_0001")
+ self.pg_enable_capture(self.pg_interfaces)
+ self.pkts = []
+
+ ipfix = VppCFLOW(test=self, layer='l2')
+ ipfix.add_vpp_config()
+
+ ipfix_decoder = IPFIXDecoder()
+ # template packet should arrive immediately
+ templates = ipfix.verify_templates(ipfix_decoder, count=1)
+
+ self.create_stream(packets=1)
+ capture = self.send_packets()
+
+ # make sure the one packet we expect actually showed up
+ self.vapi.cli("ipfix flush")
+ cflow = self.wait_for_cflow_packet(self.collector, templates[0])
+ self.verify_cflow_data_detail(ipfix_decoder, capture, cflow,
+ {2: 'packets', 256: 8})
+ self.collector.get_capture(2)
+
+ ipfix.remove_vpp_config()
+ self.logger.info("FFP_TEST_FINISH_0001")
+
+ def test_L3onL2(self):
+ """ L3 data on L2 datapath"""
+ self.logger.info("FFP_TEST_START_0002")
+ self.pg_enable_capture(self.pg_interfaces)
+ self.pkts = []
+
+ ipfix = VppCFLOW(test=self, layer='l3')
+ ipfix.add_vpp_config()
+
+ ipfix_decoder = IPFIXDecoder()
+ # template packet should arrive immediately
+ templates = ipfix.verify_templates(ipfix_decoder, count=2)
+
+ self.create_stream(packets=1)
+ capture = self.send_packets()
+
+ # make sure the one packet we expect actually showed up
+ self.vapi.cli("ipfix flush")
+ cflow = self.wait_for_cflow_packet(self.collector, templates[0])
+ self.verify_cflow_data_detail(ipfix_decoder, capture, cflow,
+ {2: 'packets', 4: 17,
+ 8: 'src_ip', 12: 'dst_ip'})
+
+ self.collector.get_capture(3)
+
+ ipfix.remove_vpp_config()
+ self.logger.info("FFP_TEST_FINISH_0002")
+
+ def test_L4onL2(self):
+ """ L4 data on L2 datapath"""
+ self.logger.info("FFP_TEST_START_0003")
+ self.pg_enable_capture(self.pg_interfaces)
+ self.pkts = []
+
+ ipfix = VppCFLOW(test=self, layer='l4')
+ ipfix.add_vpp_config()
+
+ ipfix_decoder = IPFIXDecoder()
+ # template packet should arrive immediately
+ templates = ipfix.verify_templates(ipfix_decoder, count=2)
+
+ self.create_stream(packets=1)
+ capture = self.send_packets()
+
+ # make sure the one packet we expect actually showed up
+ self.vapi.cli("ipfix flush")
+ cflow = self.wait_for_cflow_packet(self.collector, templates[0])
+ self.verify_cflow_data_detail(ipfix_decoder, capture, cflow,
+ {2: 'packets', 7: 'sport', 11: 'dport'})
+
+ self.collector.get_capture(3)
+
+ ipfix.remove_vpp_config()
+ self.logger.info("FFP_TEST_FINISH_0003")
+
+ def test_templatesIp4(self):
+ """ verify templates on IP4 datapath"""
+ self.logger.info("FFP_TEST_START_0000")
+
+ self.pg_enable_capture(self.pg_interfaces)
+
+ ipfix = VppCFLOW(test=self, datapath='ip4')
+ ipfix.add_vpp_config()
+
+ # template packet should arrive immediately
+ self.vapi.cli("ipfix flush")
+ ipfix.verify_templates(timeout=3, count=1)
+ self.collector.get_capture(1)
+
+ ipfix.remove_vpp_config()
+
+ self.logger.info("FFP_TEST_FINISH_0000")
+
+ def test_L2onIP4(self):
+ """ L2 data on IP4 datapath"""
+ self.logger.info("FFP_TEST_START_0001")
+ self.pg_enable_capture(self.pg_interfaces)
+ self.pkts = []
+
+ ipfix = VppCFLOW(test=self, intf='pg4', layer='l2', datapath='ip4')
+ ipfix.add_vpp_config()
+
+ ipfix_decoder = IPFIXDecoder()
+ # template packet should arrive immediately
+ templates = ipfix.verify_templates(ipfix_decoder, count=1)
+
+ self.create_stream(src_if=self.pg3, dst_if=self.pg4, packets=1)
+ capture = self.send_packets(src_if=self.pg3, dst_if=self.pg4)
+
+ # make sure the one packet we expect actually showed up
+ self.vapi.cli("ipfix flush")
+ cflow = self.wait_for_cflow_packet(self.collector, templates[0])
+ self.verify_cflow_data_detail(ipfix_decoder, capture, cflow,
+ {2: 'packets', 256: 8})
+
+ # expected two templates and one cflow packet
+ self.collector.get_capture(2)
+
+ ipfix.remove_vpp_config()
+ self.logger.info("FFP_TEST_FINISH_0001")
+
+ def test_L3onIP4(self):
+ """ L3 data on IP4 datapath"""
+ self.logger.info("FFP_TEST_START_0002")
+ self.pg_enable_capture(self.pg_interfaces)
+ self.pkts = []
+
+ ipfix = VppCFLOW(test=self, intf='pg4', layer='l3', datapath='ip4')
+ ipfix.add_vpp_config()
+
+ ipfix_decoder = IPFIXDecoder()
+ # template packet should arrive immediately
+ templates = ipfix.verify_templates(ipfix_decoder, count=1)
+
+ self.create_stream(src_if=self.pg3, dst_if=self.pg4, packets=1)
+ capture = self.send_packets(src_if=self.pg3, dst_if=self.pg4)
+
+ # make sure the one packet we expect actually showed up
+ self.vapi.cli("ipfix flush")
+ cflow = self.wait_for_cflow_packet(self.collector, templates[0])
+ self.verify_cflow_data_detail(ipfix_decoder, capture, cflow,
+ {1: 'octets', 2: 'packets',
+ 8: 'src_ip', 12: 'dst_ip'})
+
+ # expected two templates and one cflow packet
+ self.collector.get_capture(2)
+
+ ipfix.remove_vpp_config()
+ self.logger.info("FFP_TEST_FINISH_0002")
+
+ def test_L4onIP4(self):
+ """ L4 data on IP4 datapath"""
+ self.logger.info("FFP_TEST_START_0003")
+ self.pg_enable_capture(self.pg_interfaces)
+ self.pkts = []
+
+ ipfix = VppCFLOW(test=self, intf='pg4', layer='l4', datapath='ip4')
+ ipfix.add_vpp_config()
+
+ ipfix_decoder = IPFIXDecoder()
+ # template packet should arrive immediately
+ templates = ipfix.verify_templates(ipfix_decoder, count=1)
+
+ self.create_stream(src_if=self.pg3, dst_if=self.pg4, packets=1)
+ capture = self.send_packets(src_if=self.pg3, dst_if=self.pg4)
+
+ # make sure the one packet we expect actually showed up
+ self.vapi.cli("ipfix flush")
+ cflow = self.wait_for_cflow_packet(self.collector, templates[0])
+ self.verify_cflow_data_detail(ipfix_decoder, capture, cflow,
+ {2: 'packets', 7: 'sport', 11: 'dport'})
+
+ # expected two templates and one cflow packet
+ self.collector.get_capture(2)
+
+ ipfix.remove_vpp_config()
+ self.logger.info("FFP_TEST_FINISH_0003")
+
+ def test_templatesIP6(self):
+ """ verify templates on IP6 datapath"""
+ self.logger.info("FFP_TEST_START_0000")
+ self.pg_enable_capture(self.pg_interfaces)
+
+ ipfix = VppCFLOW(test=self, datapath='ip6')
+ ipfix.add_vpp_config()
+
+ # template packet should arrive immediately
+ ipfix.verify_templates(count=1)
+ self.collector.get_capture(1)
+
+ ipfix.remove_vpp_config()
+
+ self.logger.info("FFP_TEST_FINISH_0000")
+
+ def test_L2onIP6(self):
+ """ L2 data on IP6 datapath"""
+ self.logger.info("FFP_TEST_START_0001")
+ self.pg_enable_capture(self.pg_interfaces)
+ self.pkts = []
+
+ ipfix = VppCFLOW(test=self, intf='pg6', layer='l2', datapath='ip6')
+ ipfix.add_vpp_config()
+
+ ipfix_decoder = IPFIXDecoder()
+ # template packet should arrive immediately
+ templates = ipfix.verify_templates(ipfix_decoder, count=1)
+
+ self.create_stream(src_if=self.pg5, dst_if=self.pg6, packets=1,
+ ip_ver='IPv6')
+ capture = self.send_packets(src_if=self.pg5, dst_if=self.pg6)
+
+ # make sure the one packet we expect actually showed up
+ self.vapi.cli("ipfix flush")
+ cflow = self.wait_for_cflow_packet(self.collector, templates[0])
+ self.verify_cflow_data_detail(ipfix_decoder, capture, cflow,
+ {2: 'packets', 256: 56710},
+ ip_ver='v6')
+
+ # expected two templates and one cflow packet
+ self.collector.get_capture(2)
+
+ ipfix.remove_vpp_config()
+ self.logger.info("FFP_TEST_FINISH_0001")
+
+ def test_L3onIP6(self):
+ """ L3 data on IP6 datapath"""
+ self.logger.info("FFP_TEST_START_0002")
+ self.pg_enable_capture(self.pg_interfaces)
+ self.pkts = []
+
+ ipfix = VppCFLOW(test=self, intf='pg6', layer='l3', datapath='ip6')
+ ipfix.add_vpp_config()
+
+ ipfix_decoder = IPFIXDecoder()
+ # template packet should arrive immediately
+ templates = ipfix.verify_templates(ipfix_decoder, count=1)
+
+ self.create_stream(src_if=self.pg5, dst_if=self.pg6, packets=1,
+ ip_ver='IPv6')
+ capture = self.send_packets(src_if=self.pg5, dst_if=self.pg6)
+
+ # make sure the one packet we expect actually showed up
+ self.vapi.cli("ipfix flush")
+ cflow = self.wait_for_cflow_packet(self.collector, templates[0])
+ self.verify_cflow_data_detail(ipfix_decoder, capture, cflow,
+ {2: 'packets',
+ 27: 'src_ip', 28: 'dst_ip'},
+ ip_ver='v6')
+
+ # expected two templates and one cflow packet
+ self.collector.get_capture(2)
+
+ ipfix.remove_vpp_config()
+ self.logger.info("FFP_TEST_FINISH_0002")
+
+ def test_L4onIP6(self):
+ """ L4 data on IP6 datapath"""
+ self.logger.info("FFP_TEST_START_0003")
+ self.pg_enable_capture(self.pg_interfaces)
+ self.pkts = []
+
+ ipfix = VppCFLOW(test=self, intf='pg6', layer='l4', datapath='ip6')
+ ipfix.add_vpp_config()
+
+ ipfix_decoder = IPFIXDecoder()
+ # template packet should arrive immediately
+ templates = ipfix.verify_templates(ipfix_decoder, count=1)
+
+ self.create_stream(src_if=self.pg5, dst_if=self.pg6, packets=1,
+ ip_ver='IPv6')
+ capture = self.send_packets(src_if=self.pg5, dst_if=self.pg6)
+
+ # make sure the one packet we expect actually showed up
+ self.vapi.cli("ipfix flush")
+ cflow = self.wait_for_cflow_packet(self.collector, templates[0])
+ self.verify_cflow_data_detail(ipfix_decoder, capture, cflow,
+ {2: 'packets', 7: 'sport', 11: 'dport'},
+ ip_ver='v6')
+
+ # expected two templates and one cflow packet
+ self.collector.get_capture(2)
+
+ ipfix.remove_vpp_config()
+ self.logger.info("FFP_TEST_FINISH_0003")
+
+ def test_0001(self):
+ """ no timers, one CFLOW packet, 9 Flows inside"""
+ self.logger.info("FFP_TEST_START_0001")
+ self.pg_enable_capture(self.pg_interfaces)
+ self.pkts = []
+
+ ipfix = VppCFLOW(test=self)
+ ipfix.add_vpp_config()
+
+ ipfix_decoder = IPFIXDecoder()
+ # template packet should arrive immediately
+ templates = ipfix.verify_templates(ipfix_decoder)
+
+ self.create_stream(packets=9)
+ capture = self.send_packets()
+
+ # make sure the one packet we expect actually showed up
+ self.vapi.cli("ipfix flush")
+ cflow = self.wait_for_cflow_packet(self.collector, templates[1])
+ self.verify_cflow_data_notimer(ipfix_decoder, capture, [cflow])
+ self.collector.get_capture(4)
+
+ ipfix.remove_vpp_config()
+ self.logger.info("FFP_TEST_FINISH_0001")
+
+ def test_0002(self):
+ """ no timers, two CFLOW packets (mtu=256), 3 Flows in each"""
+ self.logger.info("FFP_TEST_START_0002")
+ self.pg_enable_capture(self.pg_interfaces)
+ self.pkts = []
+
+ ipfix = VppCFLOW(test=self, mtu=256)
+ ipfix.add_vpp_config()
+
+ ipfix_decoder = IPFIXDecoder()
+ # template packet should arrive immediately
+ self.vapi.cli("ipfix flush")
+ templates = ipfix.verify_templates(ipfix_decoder)
+
+ self.create_stream(packets=6)
+ capture = self.send_packets()
+
+ # make sure the one packet we expect actually showed up
+ cflows = []
+ self.vapi.cli("ipfix flush")
+ cflows.append(self.wait_for_cflow_packet(self.collector,
+ templates[1]))
+ cflows.append(self.wait_for_cflow_packet(self.collector,
+ templates[1]))
+ self.verify_cflow_data_notimer(ipfix_decoder, capture, cflows)
+ self.collector.get_capture(5)
+
+ ipfix.remove_vpp_config()
+ self.logger.info("FFP_TEST_FINISH_0002")
+
+
+@unittest.skipUnless(running_extended_tests(), "part of extended tests")
+class DisableIPFIX(MethodHolder):
+ """Disable IPFIX"""
+
+ def test_0001(self):
+ """ disable IPFIX after first packets"""
+ self.logger.info("FFP_TEST_START_0001")
+ self.pg_enable_capture(self.pg_interfaces)
+ self.pkts = []
+
+ ipfix = VppCFLOW(test=self)
+ ipfix.add_vpp_config()
+
+ ipfix_decoder = IPFIXDecoder()
+ # template packet should arrive immediately
+ templates = ipfix.verify_templates(ipfix_decoder)
+
+ self.create_stream()
+ self.send_packets()
+
+ # make sure the one packet we expect actually showed up
+ self.vapi.cli("ipfix flush")
+ self.wait_for_cflow_packet(self.collector, templates[1])
+ self.collector.get_capture(4)
+
+ # disble IPFIX
+ ipfix.disable_exporter()
+ self.pg_enable_capture([self.collector])
+
+ self.send_packets()
+
+ # make sure no one packet arrived in 1 minute
+ self.vapi.cli("ipfix flush")
+ self.wait_for_cflow_packet(self.collector, templates[1],
+ expected=False)
+ self.collector.get_capture(0)
+
+ ipfix.remove_vpp_config()
+ self.logger.info("FFP_TEST_FINISH_0001")
+
+
+@unittest.skipUnless(running_extended_tests(), "part of extended tests")
+class ReenableIPFIX(MethodHolder):
+ """Re-enable IPFIX"""
+
+ def test_0011(self):
+ """ disable IPFIX after first packets and re-enable after few packets
+ """
+ self.logger.info("FFP_TEST_START_0001")
+ self.pg_enable_capture(self.pg_interfaces)
+ self.pkts = []
+
+ ipfix = VppCFLOW(test=self)
+ ipfix.add_vpp_config()
+
+ ipfix_decoder = IPFIXDecoder()
+ # template packet should arrive immediately
+ templates = ipfix.verify_templates(ipfix_decoder)
+
+ self.create_stream(packets=5)
+ self.send_packets()
+
+ # make sure the one packet we expect actually showed up
+ self.vapi.cli("ipfix flush")
+ self.wait_for_cflow_packet(self.collector, templates[1])
+ self.collector.get_capture(4)
+
+ # disble IPFIX
+ ipfix.disable_exporter()
+ self.vapi.cli("ipfix flush")
+ self.pg_enable_capture([self.collector])
+
+ self.send_packets()
+
+ # make sure no one packet arrived in active timer span
+ self.vapi.cli("ipfix flush")
+ self.wait_for_cflow_packet(self.collector, templates[1],
+ expected=False)
+ self.collector.get_capture(0)
+ self.pg2.get_capture(5)
+
+ # enable IPFIX
+ ipfix.enable_exporter()
+
+ capture = self.collector.get_capture(4)
+ nr_templates = 0
+ nr_data = 0
+ for p in capture:
+ self.assertTrue(p.haslayer(IPFIX))
+ if p.haslayer(Template):
+ nr_templates += 1
+ self.assertTrue(nr_templates, 3)
+ for p in capture:
+ self.assertTrue(p.haslayer(IPFIX))
+ if p.haslayer(Data):
+ nr_data += 1
+ self.assertTrue(nr_templates, 1)
+
+ ipfix.remove_vpp_config()
+ self.logger.info("FFP_TEST_FINISH_0001")
+
+
+@unittest.skipUnless(running_extended_tests(), "part of extended tests")
+class DisableFP(MethodHolder):
+ """Disable Flowprobe feature"""
+
+ def test_0001(self):
+ """ disable flowprobe feature after first packets"""
+ self.logger.info("FFP_TEST_START_0001")
+ self.pg_enable_capture(self.pg_interfaces)
+ self.pkts = []
+ ipfix = VppCFLOW(test=self)
+ ipfix.add_vpp_config()
+
+ ipfix_decoder = IPFIXDecoder()
+ # template packet should arrive immediately
+ templates = ipfix.verify_templates(ipfix_decoder)
+
+ self.create_stream()
+ self.send_packets()
+
+ # make sure the one packet we expect actually showed up
+ self.vapi.cli("ipfix flush")
+ self.wait_for_cflow_packet(self.collector, templates[1])
+ self.collector.get_capture(4)
+
+ # disble IPFIX
+ ipfix.disable_flowprobe_feature()
+ self.pg_enable_capture([self.collector])
+
+ self.send_packets()
+
+ # make sure no one packet arrived in active timer span
+ self.vapi.cli("ipfix flush")
+ self.wait_for_cflow_packet(self.collector, templates[1],
+ expected=False)
+ self.collector.get_capture(0)
+
+ ipfix.remove_vpp_config()
+ self.logger.info("FFP_TEST_FINISH_0001")
+
+
+@unittest.skipUnless(running_extended_tests(), "part of extended tests")
+class ReenableFP(MethodHolder):
+ """Re-enable Flowprobe feature"""
+
+ def test_0001(self):
+ """ disable flowprobe feature after first packets and re-enable
+ after few packets """
+ self.logger.info("FFP_TEST_START_0001")
+ self.pg_enable_capture(self.pg_interfaces)
+ self.pkts = []
+
+ ipfix = VppCFLOW(test=self)
+ ipfix.add_vpp_config()
+
+ ipfix_decoder = IPFIXDecoder()
+ # template packet should arrive immediately
+ self.vapi.cli("ipfix flush")
+ templates = ipfix.verify_templates(ipfix_decoder, timeout=3)
+
+ self.create_stream()
+ self.send_packets()
+
+ # make sure the one packet we expect actually showed up
+ self.vapi.cli("ipfix flush")
+ self.wait_for_cflow_packet(self.collector, templates[1], 5)
+ self.collector.get_capture(4)
+
+ # disble FPP feature
+ ipfix.disable_flowprobe_feature()
+ self.pg_enable_capture([self.collector])
+
+ self.send_packets()
+
+ # make sure no one packet arrived in active timer span
+ self.vapi.cli("ipfix flush")
+ self.wait_for_cflow_packet(self.collector, templates[1], 5,
+ expected=False)
+ self.collector.get_capture(0)
+
+ # enable FPP feature
+ ipfix.enable_flowprobe_feature()
+ self.vapi.cli("ipfix flush")
+ templates = ipfix.verify_templates(ipfix_decoder, timeout=3)
+
+ self.send_packets()
+
+ # make sure the next packets (templates and data) we expect actually
+ # showed up
+ self.vapi.cli("ipfix flush")
+ self.wait_for_cflow_packet(self.collector, templates[1], 5)
+ self.collector.get_capture(4)
+
+ ipfix.remove_vpp_config()
+ self.logger.info("FFP_TEST_FINISH_0001")
+
+
+if __name__ == '__main__':
+ unittest.main(testRunner=VppTestRunner)
diff --git a/test/test_gre.py b/test/test_gre.py
new file mode 100644
index 00000000..9046b05f
--- /dev/null
+++ b/test/test_gre.py
@@ -0,0 +1,814 @@
+#!/usr/bin/env python
+
+import unittest
+from logging import *
+
+from framework import VppTestCase, VppTestRunner
+from vpp_sub_interface import VppDot1QSubint
+from vpp_gre_interface import VppGreInterface, VppGre6Interface
+from vpp_ip_route import VppIpRoute, VppRoutePath, DpoProto, VppIpTable
+from vpp_papi_provider import L2_VTR_OP
+
+from scapy.packet import Raw
+from scapy.layers.l2 import Ether, Dot1Q, GRE
+from scapy.layers.inet import IP, UDP
+from scapy.layers.inet6 import IPv6
+from scapy.volatile import RandMAC, RandIP
+
+from util import ppp, ppc
+
+
+class TestGRE(VppTestCase):
+ """ GRE Test Case """
+
+ @classmethod
+ def setUpClass(cls):
+ super(TestGRE, cls).setUpClass()
+
+ def setUp(self):
+ super(TestGRE, self).setUp()
+
+ # create 3 pg interfaces - set one in a non-default table.
+ self.create_pg_interfaces(range(3))
+
+ self.tbl = VppIpTable(self, 1)
+ self.tbl.add_vpp_config()
+ self.pg1.set_table_ip4(1)
+
+ for i in self.pg_interfaces:
+ i.admin_up()
+
+ self.pg0.config_ip4()
+ self.pg0.resolve_arp()
+ self.pg1.config_ip4()
+ self.pg1.resolve_arp()
+ self.pg2.config_ip6()
+ self.pg2.resolve_ndp()
+
+ def tearDown(self):
+ for i in self.pg_interfaces:
+ i.unconfig_ip4()
+ i.unconfig_ip6()
+ i.admin_down()
+ self.pg1.set_table_ip4(0)
+ super(TestGRE, self).tearDown()
+
+ def create_stream_ip4(self, src_if, src_ip, dst_ip):
+ pkts = []
+ for i in range(0, 257):
+ info = self.create_packet_info(src_if, src_if)
+ payload = self.info_to_payload(info)
+ p = (Ether(dst=src_if.local_mac, src=src_if.remote_mac) /
+ IP(src=src_ip, dst=dst_ip) /
+ UDP(sport=1234, dport=1234) /
+ Raw(payload))
+ info.data = p.copy()
+ pkts.append(p)
+ return pkts
+
+ def create_stream_ip6(self, src_if, src_ip, dst_ip):
+ pkts = []
+ for i in range(0, 257):
+ info = self.create_packet_info(src_if, src_if)
+ payload = self.info_to_payload(info)
+ p = (Ether(dst=src_if.local_mac, src=src_if.remote_mac) /
+ IPv6(src=src_ip, dst=dst_ip) /
+ UDP(sport=1234, dport=1234) /
+ Raw(payload))
+ info.data = p.copy()
+ pkts.append(p)
+ return pkts
+
+ def create_tunnel_stream_4o4(self, src_if,
+ tunnel_src, tunnel_dst,
+ src_ip, dst_ip):
+ pkts = []
+ for i in range(0, 257):
+ info = self.create_packet_info(src_if, src_if)
+ payload = self.info_to_payload(info)
+ p = (Ether(dst=src_if.local_mac, src=src_if.remote_mac) /
+ IP(src=tunnel_src, dst=tunnel_dst) /
+ GRE() /
+ IP(src=src_ip, dst=dst_ip) /
+ UDP(sport=1234, dport=1234) /
+ Raw(payload))
+ info.data = p.copy()
+ pkts.append(p)
+ return pkts
+
+ def create_tunnel_stream_6o4(self, src_if,
+ tunnel_src, tunnel_dst,
+ src_ip, dst_ip):
+ pkts = []
+ for i in range(0, 257):
+ info = self.create_packet_info(src_if, src_if)
+ payload = self.info_to_payload(info)
+ p = (Ether(dst=src_if.local_mac, src=src_if.remote_mac) /
+ IP(src=tunnel_src, dst=tunnel_dst) /
+ GRE() /
+ IPv6(src=src_ip, dst=dst_ip) /
+ UDP(sport=1234, dport=1234) /
+ Raw(payload))
+ info.data = p.copy()
+ pkts.append(p)
+ return pkts
+
+ def create_tunnel_stream_6o6(self, src_if,
+ tunnel_src, tunnel_dst,
+ src_ip, dst_ip):
+ pkts = []
+ for i in range(0, 257):
+ info = self.create_packet_info(src_if, src_if)
+ payload = self.info_to_payload(info)
+ p = (Ether(dst=src_if.local_mac, src=src_if.remote_mac) /
+ IPv6(src=tunnel_src, dst=tunnel_dst) /
+ GRE() /
+ IPv6(src=src_ip, dst=dst_ip) /
+ UDP(sport=1234, dport=1234) /
+ Raw(payload))
+ info.data = p.copy()
+ pkts.append(p)
+ return pkts
+
+ def create_tunnel_stream_l2o4(self, src_if,
+ tunnel_src, tunnel_dst):
+ pkts = []
+ for i in range(0, 257):
+ info = self.create_packet_info(src_if, src_if)
+ payload = self.info_to_payload(info)
+ p = (Ether(dst=src_if.local_mac, src=src_if.remote_mac) /
+ IP(src=tunnel_src, dst=tunnel_dst) /
+ GRE() /
+ Ether(dst=RandMAC('*:*:*:*:*:*'),
+ src=RandMAC('*:*:*:*:*:*')) /
+ IP(src=str(RandIP()), dst=str(RandIP())) /
+ UDP(sport=1234, dport=1234) /
+ Raw(payload))
+ info.data = p.copy()
+ pkts.append(p)
+ return pkts
+
+ def create_tunnel_stream_vlano4(self, src_if,
+ tunnel_src, tunnel_dst, vlan):
+ pkts = []
+ for i in range(0, 257):
+ info = self.create_packet_info(src_if, src_if)
+ payload = self.info_to_payload(info)
+ p = (Ether(dst=src_if.local_mac, src=src_if.remote_mac) /
+ IP(src=tunnel_src, dst=tunnel_dst) /
+ GRE() /
+ Ether(dst=RandMAC('*:*:*:*:*:*'),
+ src=RandMAC('*:*:*:*:*:*')) /
+ Dot1Q(vlan=vlan) /
+ IP(src=str(RandIP()), dst=str(RandIP())) /
+ UDP(sport=1234, dport=1234) /
+ Raw(payload))
+ info.data = p.copy()
+ pkts.append(p)
+ return pkts
+
+ def verify_tunneled_4o4(self, src_if, capture, sent,
+ tunnel_src, tunnel_dst):
+
+ self.assertEqual(len(capture), len(sent))
+
+ for i in range(len(capture)):
+ try:
+ tx = sent[i]
+ rx = capture[i]
+
+ tx_ip = tx[IP]
+ rx_ip = rx[IP]
+
+ self.assertEqual(rx_ip.src, tunnel_src)
+ self.assertEqual(rx_ip.dst, tunnel_dst)
+
+ rx_gre = rx[GRE]
+ rx_ip = rx_gre[IP]
+
+ self.assertEqual(rx_ip.src, tx_ip.src)
+ self.assertEqual(rx_ip.dst, tx_ip.dst)
+ # IP processing post pop has decremented the TTL
+ self.assertEqual(rx_ip.ttl + 1, tx_ip.ttl)
+
+ except:
+ self.logger.error(ppp("Rx:", rx))
+ self.logger.error(ppp("Tx:", tx))
+ raise
+
+ def verify_tunneled_6o6(self, src_if, capture, sent,
+ tunnel_src, tunnel_dst):
+
+ self.assertEqual(len(capture), len(sent))
+
+ for i in range(len(capture)):
+ try:
+ tx = sent[i]
+ rx = capture[i]
+
+ tx_ip = tx[IPv6]
+ rx_ip = rx[IPv6]
+
+ self.assertEqual(rx_ip.src, tunnel_src)
+ self.assertEqual(rx_ip.dst, tunnel_dst)
+
+ rx_gre = GRE(str(rx_ip[IPv6].payload))
+ rx_ip = rx_gre[IPv6]
+
+ self.assertEqual(rx_ip.src, tx_ip.src)
+ self.assertEqual(rx_ip.dst, tx_ip.dst)
+
+ except:
+ self.logger.error(ppp("Rx:", rx))
+ self.logger.error(ppp("Tx:", tx))
+ raise
+
+ def verify_tunneled_l2o4(self, src_if, capture, sent,
+ tunnel_src, tunnel_dst):
+ self.assertEqual(len(capture), len(sent))
+
+ for i in range(len(capture)):
+ try:
+ tx = sent[i]
+ rx = capture[i]
+
+ tx_ip = tx[IP]
+ rx_ip = rx[IP]
+
+ self.assertEqual(rx_ip.src, tunnel_src)
+ self.assertEqual(rx_ip.dst, tunnel_dst)
+
+ rx_gre = rx[GRE]
+ rx_l2 = rx_gre[Ether]
+ rx_ip = rx_l2[IP]
+ tx_gre = tx[GRE]
+ tx_l2 = tx_gre[Ether]
+ tx_ip = tx_l2[IP]
+
+ self.assertEqual(rx_ip.src, tx_ip.src)
+ self.assertEqual(rx_ip.dst, tx_ip.dst)
+ # bridged, not L3 forwarded, so no TTL decrement
+ self.assertEqual(rx_ip.ttl, tx_ip.ttl)
+
+ except:
+ self.logger.error(ppp("Rx:", rx))
+ self.logger.error(ppp("Tx:", tx))
+ raise
+
+ def verify_tunneled_vlano4(self, src_if, capture, sent,
+ tunnel_src, tunnel_dst, vlan):
+ try:
+ self.assertEqual(len(capture), len(sent))
+ except:
+ ppc("Unexpected packets captured:", capture)
+ raise
+
+ for i in range(len(capture)):
+ try:
+ tx = sent[i]
+ rx = capture[i]
+
+ tx_ip = tx[IP]
+ rx_ip = rx[IP]
+
+ self.assertEqual(rx_ip.src, tunnel_src)
+ self.assertEqual(rx_ip.dst, tunnel_dst)
+
+ rx_gre = rx[GRE]
+ rx_l2 = rx_gre[Ether]
+ rx_vlan = rx_l2[Dot1Q]
+ rx_ip = rx_l2[IP]
+
+ self.assertEqual(rx_vlan.vlan, vlan)
+
+ tx_gre = tx[GRE]
+ tx_l2 = tx_gre[Ether]
+ tx_ip = tx_l2[IP]
+
+ self.assertEqual(rx_ip.src, tx_ip.src)
+ self.assertEqual(rx_ip.dst, tx_ip.dst)
+ # bridged, not L3 forwarded, so no TTL decrement
+ self.assertEqual(rx_ip.ttl, tx_ip.ttl)
+
+ except:
+ self.logger.error(ppp("Rx:", rx))
+ self.logger.error(ppp("Tx:", tx))
+ raise
+
+ def verify_decapped_4o4(self, src_if, capture, sent):
+ self.assertEqual(len(capture), len(sent))
+
+ for i in range(len(capture)):
+ try:
+ tx = sent[i]
+ rx = capture[i]
+
+ tx_ip = tx[IP]
+ rx_ip = rx[IP]
+ tx_gre = tx[GRE]
+ tx_ip = tx_gre[IP]
+
+ self.assertEqual(rx_ip.src, tx_ip.src)
+ self.assertEqual(rx_ip.dst, tx_ip.dst)
+ # IP processing post pop has decremented the TTL
+ self.assertEqual(rx_ip.ttl + 1, tx_ip.ttl)
+
+ except:
+ self.logger.error(ppp("Rx:", rx))
+ self.logger.error(ppp("Tx:", tx))
+ raise
+
+ def verify_decapped_6o4(self, src_if, capture, sent):
+ self.assertEqual(len(capture), len(sent))
+
+ for i in range(len(capture)):
+ try:
+ tx = sent[i]
+ rx = capture[i]
+
+ tx_ip = tx[IP]
+ rx_ip = rx[IPv6]
+ tx_gre = tx[GRE]
+ tx_ip = tx_gre[IPv6]
+
+ self.assertEqual(rx_ip.src, tx_ip.src)
+ self.assertEqual(rx_ip.dst, tx_ip.dst)
+ self.assertEqual(rx_ip.hlim + 1, tx_ip.hlim)
+
+ except:
+ self.logger.error(ppp("Rx:", rx))
+ self.logger.error(ppp("Tx:", tx))
+ raise
+
+ def test_gre(self):
+ """ GRE IPv4 tunnel Tests """
+
+ #
+ # Create an L3 GRE tunnel.
+ # - set it admin up
+ # - assign an IP Addres
+ # - Add a route via the tunnel
+ #
+ gre_if = VppGreInterface(self,
+ self.pg0.local_ip4,
+ "1.1.1.2")
+ gre_if.add_vpp_config()
+
+ #
+ # The double create (create the same tunnel twice) should fail,
+ # and we should still be able to use the original
+ #
+ try:
+ gre_if.add_vpp_config()
+ except Exception:
+ pass
+ else:
+ self.fail("Double GRE tunnel add does not fail")
+
+ gre_if.admin_up()
+ gre_if.config_ip4()
+
+ route_via_tun = VppIpRoute(self, "4.4.4.4", 32,
+ [VppRoutePath("0.0.0.0",
+ gre_if.sw_if_index)])
+
+ route_via_tun.add_vpp_config()
+
+ #
+ # Send a packet stream that is routed into the tunnel
+ # - they are all dropped since the tunnel's desintation IP
+ # is unresolved - or resolves via the default route - which
+ # which is a drop.
+ #
+ tx = self.create_stream_ip4(self.pg0, "5.5.5.5", "4.4.4.4")
+ self.pg0.add_stream(tx)
+
+ self.pg_enable_capture(self.pg_interfaces)
+ self.pg_start()
+
+ self.pg0.assert_nothing_captured(
+ remark="GRE packets forwarded without DIP resolved")
+
+ #
+ # Add a route that resolves the tunnel's destination
+ #
+ route_tun_dst = VppIpRoute(self, "1.1.1.2", 32,
+ [VppRoutePath(self.pg0.remote_ip4,
+ self.pg0.sw_if_index)])
+ route_tun_dst.add_vpp_config()
+
+ #
+ # Send a packet stream that is routed into the tunnel
+ # - packets are GRE encapped
+ #
+ self.vapi.cli("clear trace")
+ tx = self.create_stream_ip4(self.pg0, "5.5.5.5", "4.4.4.4")
+ self.pg0.add_stream(tx)
+
+ self.pg_enable_capture(self.pg_interfaces)
+ self.pg_start()
+
+ rx = self.pg0.get_capture(len(tx))
+ self.verify_tunneled_4o4(self.pg0, rx, tx,
+ self.pg0.local_ip4, "1.1.1.2")
+
+ #
+ # Send tunneled packets that match the created tunnel and
+ # are decapped and forwarded
+ #
+ self.vapi.cli("clear trace")
+ tx = self.create_tunnel_stream_4o4(self.pg0,
+ "1.1.1.2",
+ self.pg0.local_ip4,
+ self.pg0.local_ip4,
+ self.pg0.remote_ip4)
+ self.pg0.add_stream(tx)
+
+ self.pg_enable_capture(self.pg_interfaces)
+ self.pg_start()
+
+ rx = self.pg0.get_capture(len(tx))
+ self.verify_decapped_4o4(self.pg0, rx, tx)
+
+ #
+ # Send tunneled packets that do not match the tunnel's src
+ #
+ self.vapi.cli("clear trace")
+ tx = self.create_tunnel_stream_4o4(self.pg0,
+ "1.1.1.3",
+ self.pg0.local_ip4,
+ self.pg0.local_ip4,
+ self.pg0.remote_ip4)
+ self.pg0.add_stream(tx)
+
+ self.pg_enable_capture(self.pg_interfaces)
+ self.pg_start()
+
+ self.pg0.assert_nothing_captured(
+ remark="GRE packets forwarded despite no SRC address match")
+
+ #
+ # Configure IPv6 on the PG interface so we can route IPv6
+ # packets
+ #
+ self.pg0.config_ip6()
+ self.pg0.resolve_ndp()
+
+ #
+ # Send IPv6 tunnel encapslated packets
+ # - dropped since IPv6 is not enabled on the tunnel
+ #
+ self.vapi.cli("clear trace")
+ tx = self.create_tunnel_stream_6o4(self.pg0,
+ "1.1.1.2",
+ self.pg0.local_ip4,
+ self.pg0.local_ip6,
+ self.pg0.remote_ip6)
+ self.pg0.add_stream(tx)
+
+ self.pg_enable_capture(self.pg_interfaces)
+ self.pg_start()
+
+ self.pg0.assert_nothing_captured(remark="IPv6 GRE packets forwarded "
+ "despite IPv6 not enabled on tunnel")
+
+ #
+ # Enable IPv6 on the tunnel
+ #
+ gre_if.config_ip6()
+
+ #
+ # Send IPv6 tunnel encapslated packets
+ # - forwarded since IPv6 is enabled on the tunnel
+ #
+ self.vapi.cli("clear trace")
+ tx = self.create_tunnel_stream_6o4(self.pg0,
+ "1.1.1.2",
+ self.pg0.local_ip4,
+ self.pg0.local_ip6,
+ self.pg0.remote_ip6)
+ self.pg0.add_stream(tx)
+
+ self.pg_enable_capture(self.pg_interfaces)
+ self.pg_start()
+
+ rx = self.pg0.get_capture(len(tx))
+ self.verify_decapped_6o4(self.pg0, rx, tx)
+
+ #
+ # test case cleanup
+ #
+ route_tun_dst.remove_vpp_config()
+ route_via_tun.remove_vpp_config()
+ gre_if.remove_vpp_config()
+
+ self.pg0.unconfig_ip6()
+
+ def test_gre6(self):
+ """ GRE IPv6 tunnel Tests """
+
+ #
+ # Create an L3 GRE tunnel.
+ # - set it admin up
+ # - assign an IP Address
+ # - Add a route via the tunnel
+ #
+ gre_if = VppGre6Interface(self,
+ self.pg2.local_ip6,
+ "1002::1")
+ gre_if.add_vpp_config()
+ gre_if.admin_up()
+ gre_if.config_ip6()
+
+ route_via_tun = VppIpRoute(
+ self, "4004::1", 128,
+ [VppRoutePath("0::0",
+ gre_if.sw_if_index,
+ proto=DpoProto.DPO_PROTO_IP6)],
+ is_ip6=1)
+
+ route_via_tun.add_vpp_config()
+
+ #
+ # Send a packet stream that is routed into the tunnel
+ # - they are all dropped since the tunnel's desintation IP
+ # is unresolved - or resolves via the default route - which
+ # which is a drop.
+ #
+ tx = self.create_stream_ip6(self.pg2, "5005::1", "4004::1")
+ self.pg2.add_stream(tx)
+
+ self.pg_enable_capture(self.pg_interfaces)
+ self.pg_start()
+
+ self.pg2.assert_nothing_captured(
+ remark="GRE packets forwarded without DIP resolved")
+
+ #
+ # Add a route that resolves the tunnel's destination
+ #
+ route_tun_dst = VppIpRoute(
+ self, "1002::1", 128,
+ [VppRoutePath(self.pg2.remote_ip6,
+ self.pg2.sw_if_index,
+ proto=DpoProto.DPO_PROTO_IP6)],
+ is_ip6=1)
+ route_tun_dst.add_vpp_config()
+
+ #
+ # Send a packet stream that is routed into the tunnel
+ # - packets are GRE encapped
+ #
+ self.vapi.cli("clear trace")
+ tx = self.create_stream_ip6(self.pg2, "5005::1", "4004::1")
+ self.pg2.add_stream(tx)
+
+ self.pg_enable_capture(self.pg_interfaces)
+ self.pg_start()
+
+ rx = self.pg2.get_capture(len(tx))
+ self.verify_tunneled_6o6(self.pg2, rx, tx,
+ self.pg2.local_ip6, "1002::1")
+
+ #
+ # test case cleanup
+ #
+ route_tun_dst.remove_vpp_config()
+ route_via_tun.remove_vpp_config()
+ gre_if.remove_vpp_config()
+
+ self.pg2.unconfig_ip6()
+
+ def test_gre_vrf(self):
+ """ GRE tunnel VRF Tests """
+
+ #
+ # Create an L3 GRE tunnel whose destination is in the non-default
+ # table. The underlay is thus non-default - the overlay is still
+ # the default.
+ # - set it admin up
+ # - assign an IP Addres
+ #
+ gre_if = VppGreInterface(self, self.pg1.local_ip4,
+ "2.2.2.2",
+ outer_fib_id=1)
+ gre_if.add_vpp_config()
+ gre_if.admin_up()
+ gre_if.config_ip4()
+
+ #
+ # Add a route via the tunnel - in the overlay
+ #
+ route_via_tun = VppIpRoute(self, "9.9.9.9", 32,
+ [VppRoutePath("0.0.0.0",
+ gre_if.sw_if_index)])
+ route_via_tun.add_vpp_config()
+
+ #
+ # Add a route that resolves the tunnel's destination - in the
+ # underlay table
+ #
+ route_tun_dst = VppIpRoute(self, "2.2.2.2", 32, table_id=1,
+ paths=[VppRoutePath(self.pg1.remote_ip4,
+ self.pg1.sw_if_index)])
+ route_tun_dst.add_vpp_config()
+
+ #
+ # Send a packet stream that is routed into the tunnel
+ # packets are sent in on pg0 which is in the default table
+ # - packets are GRE encapped
+ #
+ self.vapi.cli("clear trace")
+ tx = self.create_stream_ip4(self.pg0, "5.5.5.5", "9.9.9.9")
+ self.pg0.add_stream(tx)
+
+ self.pg_enable_capture(self.pg_interfaces)
+ self.pg_start()
+
+ rx = self.pg1.get_capture(len(tx))
+ self.verify_tunneled_4o4(self.pg1, rx, tx,
+ self.pg1.local_ip4, "2.2.2.2")
+
+ #
+ # Send tunneled packets that match the created tunnel and
+ # are decapped and forwarded. This tests the decap lookup
+ # does not happen in the encap table
+ #
+ self.vapi.cli("clear trace")
+ tx = self.create_tunnel_stream_4o4(self.pg1,
+ "2.2.2.2",
+ self.pg1.local_ip4,
+ self.pg0.local_ip4,
+ self.pg0.remote_ip4)
+ self.pg1.add_stream(tx)
+
+ self.pg_enable_capture(self.pg_interfaces)
+ self.pg_start()
+
+ rx = self.pg0.get_capture(len(tx))
+ self.verify_decapped_4o4(self.pg0, rx, tx)
+
+ #
+ # test case cleanup
+ #
+ route_tun_dst.remove_vpp_config()
+ route_via_tun.remove_vpp_config()
+ gre_if.remove_vpp_config()
+
+ def test_gre_l2(self):
+ """ GRE tunnel L2 Tests """
+
+ #
+ # Add routes to resolve the tunnel destinations
+ #
+ route_tun1_dst = VppIpRoute(self, "2.2.2.2", 32,
+ [VppRoutePath(self.pg0.remote_ip4,
+ self.pg0.sw_if_index)])
+ route_tun2_dst = VppIpRoute(self, "2.2.2.3", 32,
+ [VppRoutePath(self.pg0.remote_ip4,
+ self.pg0.sw_if_index)])
+
+ route_tun1_dst.add_vpp_config()
+ route_tun2_dst.add_vpp_config()
+
+ #
+ # Create 2 L2 GRE tunnels and x-connect them
+ #
+ gre_if1 = VppGreInterface(self, self.pg0.local_ip4,
+ "2.2.2.2",
+ is_teb=1)
+ gre_if2 = VppGreInterface(self, self.pg0.local_ip4,
+ "2.2.2.3",
+ is_teb=1)
+ gre_if1.add_vpp_config()
+ gre_if2.add_vpp_config()
+
+ gre_if1.admin_up()
+ gre_if2.admin_up()
+
+ self.vapi.sw_interface_set_l2_xconnect(gre_if1.sw_if_index,
+ gre_if2.sw_if_index,
+ enable=1)
+ self.vapi.sw_interface_set_l2_xconnect(gre_if2.sw_if_index,
+ gre_if1.sw_if_index,
+ enable=1)
+
+ #
+ # Send in tunnel encapped L2. expect out tunnel encapped L2
+ # in both directions
+ #
+ self.vapi.cli("clear trace")
+ tx = self.create_tunnel_stream_l2o4(self.pg0,
+ "2.2.2.2",
+ self.pg0.local_ip4)
+ self.pg0.add_stream(tx)
+
+ self.pg_enable_capture(self.pg_interfaces)
+ self.pg_start()
+
+ rx = self.pg0.get_capture(len(tx))
+ self.verify_tunneled_l2o4(self.pg0, rx, tx,
+ self.pg0.local_ip4,
+ "2.2.2.3")
+
+ self.vapi.cli("clear trace")
+ tx = self.create_tunnel_stream_l2o4(self.pg0,
+ "2.2.2.3",
+ self.pg0.local_ip4)
+ self.pg0.add_stream(tx)
+
+ self.pg_enable_capture(self.pg_interfaces)
+ self.pg_start()
+
+ rx = self.pg0.get_capture(len(tx))
+ self.verify_tunneled_l2o4(self.pg0, rx, tx,
+ self.pg0.local_ip4,
+ "2.2.2.2")
+
+ self.vapi.sw_interface_set_l2_xconnect(gre_if1.sw_if_index,
+ gre_if2.sw_if_index,
+ enable=0)
+ self.vapi.sw_interface_set_l2_xconnect(gre_if2.sw_if_index,
+ gre_if1.sw_if_index,
+ enable=0)
+
+ #
+ # Create a VLAN sub-interfaces on the GRE TEB interfaces
+ # then x-connect them
+ #
+ gre_if_11 = VppDot1QSubint(self, gre_if1, 11)
+ gre_if_12 = VppDot1QSubint(self, gre_if2, 12)
+
+ # gre_if_11.add_vpp_config()
+ # gre_if_12.add_vpp_config()
+
+ gre_if_11.admin_up()
+ gre_if_12.admin_up()
+
+ self.vapi.sw_interface_set_l2_xconnect(gre_if_11.sw_if_index,
+ gre_if_12.sw_if_index,
+ enable=1)
+ self.vapi.sw_interface_set_l2_xconnect(gre_if_12.sw_if_index,
+ gre_if_11.sw_if_index,
+ enable=1)
+
+ #
+ # Configure both to pop thier respective VLAN tags,
+ # so that during the x-coonect they will subsequently push
+ #
+ self.vapi.sw_interface_set_l2_tag_rewrite(gre_if_12.sw_if_index,
+ L2_VTR_OP.L2_POP_1,
+ 12)
+ self.vapi.sw_interface_set_l2_tag_rewrite(gre_if_11.sw_if_index,
+ L2_VTR_OP.L2_POP_1,
+ 11)
+
+ #
+ # Send traffic in both directiond - expect the VLAN tags to
+ # be swapped.
+ #
+ self.vapi.cli("clear trace")
+ tx = self.create_tunnel_stream_vlano4(self.pg0,
+ "2.2.2.2",
+ self.pg0.local_ip4,
+ 11)
+ self.pg0.add_stream(tx)
+
+ self.pg_enable_capture(self.pg_interfaces)
+ self.pg_start()
+
+ rx = self.pg0.get_capture(len(tx))
+ self.verify_tunneled_vlano4(self.pg0, rx, tx,
+ self.pg0.local_ip4,
+ "2.2.2.3",
+ 12)
+
+ self.vapi.cli("clear trace")
+ tx = self.create_tunnel_stream_vlano4(self.pg0,
+ "2.2.2.3",
+ self.pg0.local_ip4,
+ 12)
+ self.pg0.add_stream(tx)
+
+ self.pg_enable_capture(self.pg_interfaces)
+ self.pg_start()
+
+ rx = self.pg0.get_capture(len(tx))
+ self.verify_tunneled_vlano4(self.pg0, rx, tx,
+ self.pg0.local_ip4,
+ "2.2.2.2",
+ 11)
+
+ #
+ # Cleanup Test resources
+ #
+ gre_if_11.remove_vpp_config()
+ gre_if_12.remove_vpp_config()
+ gre_if1.remove_vpp_config()
+ gre_if2.remove_vpp_config()
+ route_tun1_dst.add_vpp_config()
+ route_tun2_dst.add_vpp_config()
+
+
+if __name__ == '__main__':
+ unittest.main(testRunner=VppTestRunner)
diff --git a/test/test_gtpu.py b/test/test_gtpu.py
new file mode 100644
index 00000000..9411fffc
--- /dev/null
+++ b/test/test_gtpu.py
@@ -0,0 +1,289 @@
+#!/usr/bin/env python
+
+import socket
+from util import ip4n_range
+import unittest
+from framework import VppTestCase, VppTestRunner
+from template_bd import BridgeDomain
+
+from scapy.layers.l2 import Ether, Raw
+from scapy.layers.inet import IP, UDP
+from scapy.contrib.gtp import GTP_U_Header
+from scapy.utils import atol
+
+
+class TestGtpu(BridgeDomain, VppTestCase):
+ """ GTPU Test Case """
+
+ def __init__(self, *args):
+ BridgeDomain.__init__(self)
+ VppTestCase.__init__(self, *args)
+
+ def encapsulate(self, pkt, vni):
+ """
+ Encapsulate the original payload frame by adding GTPU header with its
+ UDP, IP and Ethernet fields
+ """
+ return (Ether(src=self.pg0.remote_mac, dst=self.pg0.local_mac) /
+ IP(src=self.pg0.remote_ip4, dst=self.pg0.local_ip4) /
+ UDP(sport=self.dport, dport=self.dport, chksum=0) /
+ GTP_U_Header(TEID=vni, gtp_type=self.gtp_type, length=150) /
+ pkt)
+
+ def encap_mcast(self, pkt, src_ip, src_mac, vni):
+ """
+ Encapsulate the original payload frame by adding GTPU header with its
+ UDP, IP and Ethernet fields
+ """
+ return (Ether(src=src_mac, dst=self.mcast_mac) /
+ IP(src=src_ip, dst=self.mcast_ip4) /
+ UDP(sport=self.dport, dport=self.dport, chksum=0) /
+ GTP_U_Header(TEID=vni, gtp_type=self.gtp_type, length=150) /
+ pkt)
+
+ def decapsulate(self, pkt):
+ """
+ Decapsulate the original payload frame by removing GTPU header
+ """
+ return pkt[GTP_U_Header].payload
+
+ # Method for checking GTPU encapsulation.
+ #
+ def check_encapsulation(self, pkt, vni, local_only=False, mcast_pkt=False):
+ # Verify source MAC is VPP_MAC and destination MAC is MY_MAC resolved
+ # by VPP using ARP.
+ self.assertEqual(pkt[Ether].src, self.pg0.local_mac)
+ if not local_only:
+ if not mcast_pkt:
+ self.assertEqual(pkt[Ether].dst, self.pg0.remote_mac)
+ else:
+ self.assertEqual(pkt[Ether].dst, type(self).mcast_mac)
+ # Verify GTPU tunnel source IP is VPP_IP and destination IP is MY_IP.
+ self.assertEqual(pkt[IP].src, self.pg0.local_ip4)
+ if not local_only:
+ if not mcast_pkt:
+ self.assertEqual(pkt[IP].dst, self.pg0.remote_ip4)
+ else:
+ self.assertEqual(pkt[IP].dst, type(self).mcast_ip4)
+ # Verify UDP destination port is GTPU 2152, source UDP port could be
+ # arbitrary.
+ self.assertEqual(pkt[UDP].dport, type(self).dport)
+ # Verify TEID
+ self.assertEqual(pkt[GTP_U_Header].TEID, vni)
+
+ def test_encap(self):
+ """ Encapsulation test
+ Send frames from pg1
+ Verify receipt of encapsulated frames on pg0
+ """
+ self.pg1.add_stream([self.frame_reply])
+
+ self.pg0.enable_capture()
+
+ self.pg_start()
+
+ # Pick first received frame and check if it's corectly encapsulated.
+ out = self.pg0.get_capture(1)
+ pkt = out[0]
+ self.check_encapsulation(pkt, self.single_tunnel_bd)
+
+ # payload = self.decapsulate(pkt)
+ # self.assert_eq_pkts(payload, self.frame_reply)
+
+ def test_ucast_flood(self):
+ """ Unicast flood test
+ Send frames from pg3
+ Verify receipt of encapsulated frames on pg0
+ """
+ self.pg3.add_stream([self.frame_reply])
+
+ self.pg0.enable_capture()
+
+ self.pg_start()
+
+ # Get packet from each tunnel and assert it's corectly encapsulated.
+ out = self.pg0.get_capture(self.n_ucast_tunnels)
+ for pkt in out:
+ self.check_encapsulation(pkt, self.ucast_flood_bd, True)
+ # payload = self.decapsulate(pkt)
+ # self.assert_eq_pkts(payload, self.frame_reply)
+
+ def test_mcast_flood(self):
+ """ Multicast flood test
+ Send frames from pg2
+ Verify receipt of encapsulated frames on pg0
+ """
+ self.pg2.add_stream([self.frame_reply])
+
+ self.pg0.enable_capture()
+
+ self.pg_start()
+
+ # Pick first received frame and check if it's corectly encapsulated.
+ out = self.pg0.get_capture(1)
+ pkt = out[0]
+ self.check_encapsulation(pkt, self.mcast_flood_bd,
+ local_only=False, mcast_pkt=True)
+
+ # payload = self.decapsulate(pkt)
+ # self.assert_eq_pkts(payload, self.frame_reply)
+
+ @classmethod
+ def create_gtpu_flood_test_bd(cls, teid, n_ucast_tunnels):
+ # Create 10 ucast gtpu tunnels under bd
+ ip_range_start = 10
+ ip_range_end = ip_range_start + n_ucast_tunnels
+ next_hop_address = cls.pg0.remote_ip4n
+ for dest_ip4n in ip4n_range(next_hop_address, ip_range_start,
+ ip_range_end):
+ # add host route so dest_ip4n will not be resolved
+ cls.vapi.ip_add_del_route(dest_ip4n, 32, next_hop_address)
+ r = cls.vapi.gtpu_add_del_tunnel(
+ src_addr=cls.pg0.local_ip4n,
+ dst_addr=dest_ip4n,
+ teid=teid)
+ cls.vapi.sw_interface_set_l2_bridge(r.sw_if_index, bd_id=teid)
+
+ @classmethod
+ def add_del_shared_mcast_dst_load(cls, is_add):
+ """
+ add or del tunnels sharing the same mcast dst
+ to test gtpu ref_count mechanism
+ """
+ n_shared_dst_tunnels = 20
+ teid_start = 1000
+ teid_end = teid_start + n_shared_dst_tunnels
+ for teid in range(teid_start, teid_end):
+ r = cls.vapi.gtpu_add_del_tunnel(
+ src_addr=cls.pg0.local_ip4n,
+ dst_addr=cls.mcast_ip4n,
+ mcast_sw_if_index=1,
+ teid=teid,
+ is_add=is_add)
+ if r.sw_if_index == 0xffffffff:
+ raise "bad sw_if_index"
+
+ @classmethod
+ def add_shared_mcast_dst_load(cls):
+ cls.add_del_shared_mcast_dst_load(is_add=1)
+
+ @classmethod
+ def del_shared_mcast_dst_load(cls):
+ cls.add_del_shared_mcast_dst_load(is_add=0)
+
+ @classmethod
+ def add_del_mcast_tunnels_load(cls, is_add):
+ """
+ add or del tunnels to test gtpu stability
+ """
+ n_distinct_dst_tunnels = 20
+ ip_range_start = 10
+ ip_range_end = ip_range_start + n_distinct_dst_tunnels
+ for dest_ip4n in ip4n_range(cls.mcast_ip4n, ip_range_start,
+ ip_range_end):
+ teid = bytearray(dest_ip4n)[3]
+ cls.vapi.gtpu_add_del_tunnel(
+ src_addr=cls.pg0.local_ip4n,
+ dst_addr=dest_ip4n,
+ mcast_sw_if_index=1,
+ teid=teid,
+ is_add=is_add)
+
+ @classmethod
+ def add_mcast_tunnels_load(cls):
+ cls.add_del_mcast_tunnels_load(is_add=1)
+
+ @classmethod
+ def del_mcast_tunnels_load(cls):
+ cls.add_del_mcast_tunnels_load(is_add=0)
+
+ # Class method to start the GTPU test case.
+ # Overrides setUpClass method in VppTestCase class.
+ # Python try..except statement is used to ensure that the tear down of
+ # the class will be executed even if exception is raised.
+ # @param cls The class pointer.
+ @classmethod
+ def setUpClass(cls):
+ super(TestGtpu, cls).setUpClass()
+
+ try:
+ cls.dport = 2152
+ cls.gtp_type = 0xff
+
+ # Create 2 pg interfaces.
+ cls.create_pg_interfaces(range(4))
+ for pg in cls.pg_interfaces:
+ pg.admin_up()
+
+ # Configure IPv4 addresses on VPP pg0.
+ cls.pg0.config_ip4()
+
+ # Resolve MAC address for VPP's IP address on pg0.
+ cls.pg0.resolve_arp()
+
+ # Our Multicast address
+ cls.mcast_ip4 = '239.1.1.1'
+ cls.mcast_ip4n = socket.inet_pton(socket.AF_INET, cls.mcast_ip4)
+ iplong = atol(cls.mcast_ip4)
+ cls.mcast_mac = "01:00:5e:%02x:%02x:%02x" % (
+ (iplong >> 16) & 0x7F, (iplong >> 8) & 0xFF, iplong & 0xFF)
+
+ # Create GTPU VTEP on VPP pg0, and put gtpu_tunnel0 and pg1
+ # into BD.
+ cls.single_tunnel_bd = 11
+ r = cls.vapi.gtpu_add_del_tunnel(
+ src_addr=cls.pg0.local_ip4n,
+ dst_addr=cls.pg0.remote_ip4n,
+ teid=cls.single_tunnel_bd)
+ cls.vapi.sw_interface_set_l2_bridge(r.sw_if_index,
+ bd_id=cls.single_tunnel_bd)
+ cls.vapi.sw_interface_set_l2_bridge(cls.pg1.sw_if_index,
+ bd_id=cls.single_tunnel_bd)
+
+ # Setup teid 2 to test multicast flooding
+ cls.n_ucast_tunnels = 10
+ cls.mcast_flood_bd = 12
+ cls.create_gtpu_flood_test_bd(cls.mcast_flood_bd,
+ cls.n_ucast_tunnels)
+ r = cls.vapi.gtpu_add_del_tunnel(
+ src_addr=cls.pg0.local_ip4n,
+ dst_addr=cls.mcast_ip4n,
+ mcast_sw_if_index=1,
+ teid=cls.mcast_flood_bd)
+ cls.vapi.sw_interface_set_l2_bridge(r.sw_if_index,
+ bd_id=cls.mcast_flood_bd)
+ cls.vapi.sw_interface_set_l2_bridge(cls.pg2.sw_if_index,
+ bd_id=cls.mcast_flood_bd)
+
+ # Add and delete mcast tunnels to check stability
+ cls.add_shared_mcast_dst_load()
+ cls.add_mcast_tunnels_load()
+ cls.del_shared_mcast_dst_load()
+ cls.del_mcast_tunnels_load()
+
+ # Setup teid 3 to test unicast flooding
+ cls.ucast_flood_bd = 13
+ cls.create_gtpu_flood_test_bd(cls.ucast_flood_bd,
+ cls.n_ucast_tunnels)
+ cls.vapi.sw_interface_set_l2_bridge(cls.pg3.sw_if_index,
+ bd_id=cls.ucast_flood_bd)
+ except Exception:
+ super(TestGtpu, cls).tearDownClass()
+ raise
+
+ # Method to define VPP actions before tear down of the test case.
+ # Overrides tearDown method in VppTestCase class.
+ # @param self The object pointer.
+ def tearDown(self):
+ super(TestGtpu, self).tearDown()
+ if not self.vpp_dead:
+ self.logger.info(self.vapi.cli("show bridge-domain 11 detail"))
+ self.logger.info(self.vapi.cli("show bridge-domain 12 detail"))
+ self.logger.info(self.vapi.cli("show bridge-domain 13 detail"))
+ self.logger.info(self.vapi.cli("show int"))
+ self.logger.info(self.vapi.cli("show gtpu tunnel"))
+ self.logger.info(self.vapi.cli("show trace"))
+
+
+if __name__ == '__main__':
+ unittest.main(testRunner=VppTestRunner)
diff --git a/test/test_interface_crud.py b/test/test_interface_crud.py
new file mode 100644
index 00000000..63917047
--- /dev/null
+++ b/test/test_interface_crud.py
@@ -0,0 +1,151 @@
+#!/usr/bin/env python
+"""CRUD tests of APIs (Create, Read, Update, Delete) HLD:
+
+- interface up/down/add/delete - interface type:
+ - pg (TBD)
+ - loopback
+ - vhostuser (TBD)
+ - af_packet (TBD)
+ - netmap (TBD)
+ - tuntap (root privileges needed)
+ - vxlan (TBD)
+"""
+
+import unittest
+
+from scapy.layers.inet import IP, ICMP
+from scapy.layers.l2 import Ether
+
+from framework import VppTestCase, VppTestRunner
+
+
+class TestLoopbackInterfaceCRUD(VppTestCase):
+ """CRUD Loopback
+
+ """
+
+ @classmethod
+ def setUpClass(cls):
+ super(TestLoopbackInterfaceCRUD, cls).setUpClass()
+ try:
+ cls.create_pg_interfaces(range(1))
+ for i in cls.pg_interfaces:
+ i.config_ip4()
+ i.resolve_arp()
+ except:
+ cls.tearDownClass()
+ raise
+
+ @staticmethod
+ def create_icmp_stream(src_if, dst_ifs):
+ """
+
+ :param VppInterface src_if: Packets are send to this interface,
+ using this interfaces remote host.
+ :param list dst_ifs: IPv4 ICMP requests are send to interfaces
+ addresses.
+ :return: List of generated packets.
+ """
+ pkts = []
+ for i in dst_ifs:
+ p = (Ether(dst=src_if.local_mac, src=src_if.remote_mac) /
+ IP(src=src_if.remote_ip4, dst=i.local_ip4) /
+ ICMP(id=i.sw_if_index, type='echo-request'))
+ pkts.append(p)
+ return pkts
+
+ def verify_icmp(self, capture, request_src_if, dst_ifs):
+ """
+
+ :param capture: Capture to verify.
+ :param VppInterface request_src_if: Interface where was send packets.
+ :param list dst_ifs: Interfaces where was generated IPv4 ICMP requests.
+ """
+ rcvd_icmp_pkts = []
+ for pkt in capture:
+ try:
+ ip = pkt[IP]
+ icmp = pkt[ICMP]
+ except IndexError:
+ pass
+ else:
+ info = (ip.src, ip.dst, icmp.type, icmp.id)
+ rcvd_icmp_pkts.append(info)
+
+ for i in dst_ifs:
+ # 0 - icmp echo response
+ info = (i.local_ip4, request_src_if.remote_ip4, 0, i.sw_if_index)
+ self.assertIn(info, rcvd_icmp_pkts)
+
+ def test_crud(self):
+ # create
+ loopbacks = self.create_loopback_interfaces(range(20))
+ for i in loopbacks:
+ i.local_ip4_prefix_len = 32
+ i.config_ip4()
+ i.admin_up()
+
+ # read (check sw if dump, ip4 fib, ip6 fib)
+ if_dump = self.vapi.sw_interface_dump()
+ fib4_dump = self.vapi.ip_fib_dump()
+ for i in loopbacks:
+ self.assertTrue(i.is_interface_config_in_dump(if_dump))
+ self.assertTrue(i.is_ip4_entry_in_fib_dump(fib4_dump))
+
+ # check ping
+ stream = self.create_icmp_stream(self.pg0, loopbacks)
+ self.pg0.add_stream(stream)
+ self.pg_enable_capture(self.pg_interfaces)
+ self.pg_start()
+ capture = self.pg0.get_capture(expected_count=len(stream))
+
+ self.verify_icmp(capture, self.pg0, loopbacks)
+
+ # delete
+ for i in loopbacks:
+ i.remove_vpp_config()
+
+ # read (check not in sw if dump, ip4 fib, ip6 fib)
+ if_dump = self.vapi.sw_interface_dump()
+ fib4_dump = self.vapi.ip_fib_dump()
+ for i in loopbacks:
+ self.assertFalse(i.is_interface_config_in_dump(if_dump))
+ self.assertFalse(i.is_ip4_entry_in_fib_dump(fib4_dump))
+
+ # check not ping
+ stream = self.create_icmp_stream(self.pg0, loopbacks)
+ self.pg0.add_stream(stream)
+ self.pg_enable_capture(self.pg_interfaces)
+ self.pg_start()
+ self.pg0.assert_nothing_captured()
+
+ def test_down(self):
+ # create
+ loopbacks = self.create_loopback_interfaces(range(20))
+ for i in loopbacks:
+ i.local_ip4_prefix_len = 32
+ i.config_ip4()
+ i.admin_up()
+
+ # disable
+ for i in loopbacks:
+ i.admin_down()
+ i.unconfig_ip4()
+
+ # read (check not in sw if dump, ip4 fib, ip6 fib)
+ if_dump = self.vapi.sw_interface_dump()
+ fib4_dump = self.vapi.ip_fib_dump()
+ for i in loopbacks:
+ self.assertTrue(i.is_interface_config_in_dump(if_dump))
+ self.assertFalse(i.is_ip4_entry_in_fib_dump(fib4_dump))
+
+ # check not ping
+ stream = self.create_icmp_stream(self.pg0, loopbacks)
+ self.pg0.add_stream(stream)
+ self.pg_enable_capture(self.pg_interfaces)
+ self.pg_start()
+ self.pg0.assert_nothing_captured()
+
+
+if __name__ == '__main__':
+ unittest.main(testRunner=VppTestRunner)
diff --git a/test/test_ip4.py b/test/test_ip4.py
new file mode 100644
index 00000000..55d16735
--- /dev/null
+++ b/test/test_ip4.py
@@ -0,0 +1,1013 @@
+#!/usr/bin/env python
+import random
+import socket
+import unittest
+
+from framework import VppTestCase, VppTestRunner
+from vpp_sub_interface import VppSubInterface, VppDot1QSubint, VppDot1ADSubint
+from vpp_ip_route import VppIpRoute, VppRoutePath, VppIpMRoute, \
+ VppMRoutePath, MRouteItfFlags, MRouteEntryFlags, VppMplsIpBind, \
+ VppMplsTable
+
+from scapy.packet import Raw
+from scapy.layers.l2 import Ether, Dot1Q, ARP
+from scapy.layers.inet import IP, UDP, ICMP, icmptypes, icmpcodes
+from util import ppp
+from scapy.contrib.mpls import MPLS
+
+
+class TestIPv4(VppTestCase):
+ """ IPv4 Test Case """
+
+ def setUp(self):
+ """
+ Perform test setup before test case.
+
+ **Config:**
+ - create 3 pg interfaces
+ - untagged pg0 interface
+ - Dot1Q subinterface on pg1
+ - Dot1AD subinterface on pg2
+ - setup interfaces:
+ - put it into UP state
+ - set IPv4 addresses
+ - resolve neighbor address using ARP
+ - configure 200 fib entries
+
+ :ivar list interfaces: pg interfaces and subinterfaces.
+ :ivar dict flows: IPv4 packet flows in test.
+ :ivar list pg_if_packet_sizes: packet sizes in test.
+ """
+ super(TestIPv4, self).setUp()
+
+ # create 3 pg interfaces
+ self.create_pg_interfaces(range(3))
+
+ # create 2 subinterfaces for pg1 and pg2
+ self.sub_interfaces = [
+ VppDot1QSubint(self, self.pg1, 100),
+ VppDot1ADSubint(self, self.pg2, 200, 300, 400)]
+
+ # packet flows mapping pg0 -> pg1.sub, pg2.sub, etc.
+ self.flows = dict()
+ self.flows[self.pg0] = [self.pg1.sub_if, self.pg2.sub_if]
+ self.flows[self.pg1.sub_if] = [self.pg0, self.pg2.sub_if]
+ self.flows[self.pg2.sub_if] = [self.pg0, self.pg1.sub_if]
+
+ # packet sizes
+ self.pg_if_packet_sizes = [64, 512, 1518, 9018]
+ self.sub_if_packet_sizes = [64, 512, 1518 + 4, 9018 + 4]
+
+ self.interfaces = list(self.pg_interfaces)
+ self.interfaces.extend(self.sub_interfaces)
+
+ # setup all interfaces
+ for i in self.interfaces:
+ i.admin_up()
+ i.config_ip4()
+ i.resolve_arp()
+
+ # config 2M FIB entries
+ self.config_fib_entries(200)
+
+ def tearDown(self):
+ """Run standard test teardown and log ``show ip arp``."""
+ super(TestIPv4, self).tearDown()
+ if not self.vpp_dead:
+ self.logger.info(self.vapi.cli("show ip arp"))
+ # info(self.vapi.cli("show ip fib")) # many entries
+
+ def config_fib_entries(self, count):
+ """For each interface add to the FIB table *count* routes to
+ "10.0.0.1/32" destination with interface's local address as next-hop
+ address.
+
+ :param int count: Number of FIB entries.
+
+ - *TODO:* check if the next-hop address shouldn't be remote address
+ instead of local address.
+ """
+ n_int = len(self.interfaces)
+ percent = 0
+ counter = 0.0
+ dest_addr = socket.inet_pton(socket.AF_INET, "10.0.0.1")
+ dest_addr_len = 32
+ for i in self.interfaces:
+ next_hop_address = i.local_ip4n
+ for j in range(count / n_int):
+ self.vapi.ip_add_del_route(
+ dest_addr, dest_addr_len, next_hop_address)
+ counter += 1
+ if counter / count * 100 > percent:
+ self.logger.info("Configure %d FIB entries .. %d%% done" %
+ (count, percent))
+ percent += 1
+
+ def create_stream(self, src_if, packet_sizes):
+ """Create input packet stream for defined interface.
+
+ :param VppInterface src_if: Interface to create packet stream for.
+ :param list packet_sizes: Required packet sizes.
+ """
+ pkts = []
+ for i in range(0, 257):
+ dst_if = self.flows[src_if][i % 2]
+ info = self.create_packet_info(src_if, dst_if)
+ payload = self.info_to_payload(info)
+ p = (Ether(dst=src_if.local_mac, src=src_if.remote_mac) /
+ IP(src=src_if.remote_ip4, dst=dst_if.remote_ip4) /
+ UDP(sport=1234, dport=1234) /
+ Raw(payload))
+ info.data = p.copy()
+ if isinstance(src_if, VppSubInterface):
+ p = src_if.add_dot1_layer(p)
+ size = packet_sizes[(i // 2) % len(packet_sizes)]
+ self.extend_packet(p, size)
+ pkts.append(p)
+ return pkts
+
+ def verify_capture(self, dst_if, capture):
+ """Verify captured input packet stream for defined interface.
+
+ :param VppInterface dst_if: Interface to verify captured packet stream
+ for.
+ :param list capture: Captured packet stream.
+ """
+ self.logger.info("Verifying capture on interface %s" % dst_if.name)
+ last_info = dict()
+ for i in self.interfaces:
+ last_info[i.sw_if_index] = None
+ is_sub_if = False
+ dst_sw_if_index = dst_if.sw_if_index
+ if hasattr(dst_if, 'parent'):
+ is_sub_if = True
+ for packet in capture:
+ if is_sub_if:
+ # Check VLAN tags and Ethernet header
+ packet = dst_if.remove_dot1_layer(packet)
+ self.assertTrue(Dot1Q not in packet)
+ try:
+ ip = packet[IP]
+ udp = packet[UDP]
+ payload_info = self.payload_to_info(str(packet[Raw]))
+ packet_index = payload_info.index
+ self.assertEqual(payload_info.dst, dst_sw_if_index)
+ self.logger.debug(
+ "Got packet on port %s: src=%u (id=%u)" %
+ (dst_if.name, payload_info.src, packet_index))
+ next_info = self.get_next_packet_info_for_interface2(
+ payload_info.src, dst_sw_if_index,
+ last_info[payload_info.src])
+ last_info[payload_info.src] = next_info
+ self.assertTrue(next_info is not None)
+ self.assertEqual(packet_index, next_info.index)
+ saved_packet = next_info.data
+ # Check standard fields
+ self.assertEqual(ip.src, saved_packet[IP].src)
+ self.assertEqual(ip.dst, saved_packet[IP].dst)
+ self.assertEqual(udp.sport, saved_packet[UDP].sport)
+ self.assertEqual(udp.dport, saved_packet[UDP].dport)
+ except:
+ self.logger.error(ppp("Unexpected or invalid packet:", packet))
+ raise
+ for i in self.interfaces:
+ remaining_packet = self.get_next_packet_info_for_interface2(
+ i.sw_if_index, dst_sw_if_index, last_info[i.sw_if_index])
+ self.assertTrue(remaining_packet is None,
+ "Interface %s: Packet expected from interface %s "
+ "didn't arrive" % (dst_if.name, i.name))
+
+ def test_fib(self):
+ """ IPv4 FIB test
+
+ Test scenario:
+
+ - Create IPv4 stream for pg0 interface
+ - Create IPv4 tagged streams for pg1's and pg2's subinterface.
+ - Send and verify received packets on each interface.
+ """
+
+ pkts = self.create_stream(self.pg0, self.pg_if_packet_sizes)
+ self.pg0.add_stream(pkts)
+
+ for i in self.sub_interfaces:
+ pkts = self.create_stream(i, self.sub_if_packet_sizes)
+ i.parent.add_stream(pkts)
+
+ self.pg_enable_capture(self.pg_interfaces)
+ self.pg_start()
+
+ pkts = self.pg0.get_capture()
+ self.verify_capture(self.pg0, pkts)
+
+ for i in self.sub_interfaces:
+ pkts = i.parent.get_capture()
+ self.verify_capture(i, pkts)
+
+
+class TestIPv4FibCrud(VppTestCase):
+ """ FIB - add/update/delete - ip4 routes
+
+ Test scenario:
+ - add 1k,
+ - del 100,
+ - add new 1k,
+ - del 1.5k
+
+ ..note:: Python API is too slow to add many routes, needs replacement.
+ """
+
+ def config_fib_many_to_one(self, start_dest_addr, next_hop_addr, count):
+ """
+
+ :param start_dest_addr:
+ :param next_hop_addr:
+ :param count:
+ :return list: added ips with 32 prefix
+ """
+ added_ips = []
+ dest_addr = int(socket.inet_pton(socket.AF_INET,
+ start_dest_addr).encode('hex'),
+ 16)
+ dest_addr_len = 32
+ n_next_hop_addr = socket.inet_pton(socket.AF_INET, next_hop_addr)
+ for _ in range(count):
+ n_dest_addr = '{:08x}'.format(dest_addr).decode('hex')
+ self.vapi.ip_add_del_route(n_dest_addr, dest_addr_len,
+ n_next_hop_addr)
+ added_ips.append(socket.inet_ntoa(n_dest_addr))
+ dest_addr += 1
+ return added_ips
+
+ def unconfig_fib_many_to_one(self, start_dest_addr, next_hop_addr, count):
+
+ removed_ips = []
+ dest_addr = int(socket.inet_pton(socket.AF_INET,
+ start_dest_addr).encode('hex'),
+ 16)
+ dest_addr_len = 32
+ n_next_hop_addr = socket.inet_pton(socket.AF_INET, next_hop_addr)
+ for _ in range(count):
+ n_dest_addr = '{:08x}'.format(dest_addr).decode('hex')
+ self.vapi.ip_add_del_route(n_dest_addr, dest_addr_len,
+ n_next_hop_addr, is_add=0)
+ removed_ips.append(socket.inet_ntoa(n_dest_addr))
+ dest_addr += 1
+ return removed_ips
+
+ def create_stream(self, src_if, dst_if, dst_ips, count):
+ pkts = []
+
+ for _ in range(count):
+ dst_addr = random.choice(dst_ips)
+ info = self.create_packet_info(src_if, dst_if)
+ payload = self.info_to_payload(info)
+ p = (Ether(dst=src_if.local_mac, src=src_if.remote_mac) /
+ IP(src=src_if.remote_ip4, dst=dst_addr) /
+ UDP(sport=1234, dport=1234) /
+ Raw(payload))
+ info.data = p.copy()
+ self.extend_packet(p, random.choice(self.pg_if_packet_sizes))
+ pkts.append(p)
+
+ return pkts
+
+ def _find_ip_match(self, find_in, pkt):
+ for p in find_in:
+ if self.payload_to_info(str(p[Raw])) == \
+ self.payload_to_info(str(pkt[Raw])):
+ if p[IP].src != pkt[IP].src:
+ break
+ if p[IP].dst != pkt[IP].dst:
+ break
+ if p[UDP].sport != pkt[UDP].sport:
+ break
+ if p[UDP].dport != pkt[UDP].dport:
+ break
+ return p
+ return None
+
+ @staticmethod
+ def _match_route_detail(route_detail, ip, address_length=32, table_id=0):
+ if route_detail.address == socket.inet_pton(socket.AF_INET, ip):
+ if route_detail.table_id != table_id:
+ return False
+ elif route_detail.address_length != address_length:
+ return False
+ else:
+ return True
+ else:
+ return False
+
+ def verify_capture(self, dst_interface, received_pkts, expected_pkts):
+ self.assertEqual(len(received_pkts), len(expected_pkts))
+ to_verify = list(expected_pkts)
+ for p in received_pkts:
+ self.assertEqual(p.src, dst_interface.local_mac)
+ self.assertEqual(p.dst, dst_interface.remote_mac)
+ x = self._find_ip_match(to_verify, p)
+ to_verify.remove(x)
+ self.assertListEqual(to_verify, [])
+
+ def verify_route_dump(self, fib_dump, ips):
+
+ def _ip_in_route_dump(ip, fib_dump):
+ return next((route for route in fib_dump
+ if self._match_route_detail(route, ip)),
+ False)
+
+ for ip in ips:
+ self.assertTrue(_ip_in_route_dump(ip, fib_dump),
+ 'IP {} is not in fib dump.'.format(ip))
+
+ def verify_not_in_route_dump(self, fib_dump, ips):
+
+ def _ip_in_route_dump(ip, fib_dump):
+ return next((route for route in fib_dump
+ if self._match_route_detail(route, ip)),
+ False)
+
+ for ip in ips:
+ self.assertFalse(_ip_in_route_dump(ip, fib_dump),
+ 'IP {} is in fib dump.'.format(ip))
+
+ @classmethod
+ def setUpClass(cls):
+ """
+ #. Create and initialize 3 pg interfaces.
+ #. initialize class attributes configured_routes and deleted_routes
+ to store information between tests.
+ """
+ super(TestIPv4FibCrud, cls).setUpClass()
+
+ try:
+ # create 3 pg interfaces
+ cls.create_pg_interfaces(range(3))
+
+ cls.interfaces = list(cls.pg_interfaces)
+
+ # setup all interfaces
+ for i in cls.interfaces:
+ i.admin_up()
+ i.config_ip4()
+ i.resolve_arp()
+
+ cls.configured_routes = []
+ cls.deleted_routes = []
+ cls.pg_if_packet_sizes = [64, 512, 1518, 9018]
+
+ except Exception:
+ super(TestIPv4FibCrud, cls).tearDownClass()
+ raise
+
+ def setUp(self):
+ super(TestIPv4FibCrud, self).setUp()
+ self.reset_packet_infos()
+
+ def test_1_add_routes(self):
+ """ Add 1k routes
+
+ - add 100 routes check with traffic script.
+ """
+ # config 1M FIB entries
+ self.configured_routes.extend(self.config_fib_many_to_one(
+ "10.0.0.0", self.pg0.remote_ip4, 100))
+
+ fib_dump = self.vapi.ip_fib_dump()
+ self.verify_route_dump(fib_dump, self.configured_routes)
+
+ self.stream_1 = self.create_stream(
+ self.pg1, self.pg0, self.configured_routes, 100)
+ self.stream_2 = self.create_stream(
+ self.pg2, self.pg0, self.configured_routes, 100)
+ self.pg1.add_stream(self.stream_1)
+ self.pg2.add_stream(self.stream_2)
+
+ self.pg_enable_capture(self.pg_interfaces)
+ self.pg_start()
+
+ pkts = self.pg0.get_capture(len(self.stream_1) + len(self.stream_2))
+ self.verify_capture(self.pg0, pkts, self.stream_1 + self.stream_2)
+
+ def test_2_del_routes(self):
+ """ Delete 100 routes
+
+ - delete 10 routes check with traffic script.
+ """
+ self.deleted_routes.extend(self.unconfig_fib_many_to_one(
+ "10.0.0.10", self.pg0.remote_ip4, 10))
+ for x in self.deleted_routes:
+ self.configured_routes.remove(x)
+
+ fib_dump = self.vapi.ip_fib_dump()
+ self.verify_route_dump(fib_dump, self.configured_routes)
+
+ self.stream_1 = self.create_stream(
+ self.pg1, self.pg0, self.configured_routes, 100)
+ self.stream_2 = self.create_stream(
+ self.pg2, self.pg0, self.configured_routes, 100)
+ self.stream_3 = self.create_stream(
+ self.pg1, self.pg0, self.deleted_routes, 100)
+ self.stream_4 = self.create_stream(
+ self.pg2, self.pg0, self.deleted_routes, 100)
+ self.pg1.add_stream(self.stream_1 + self.stream_3)
+ self.pg2.add_stream(self.stream_2 + self.stream_4)
+ self.pg_enable_capture(self.pg_interfaces)
+ self.pg_start()
+
+ pkts = self.pg0.get_capture(len(self.stream_1) + len(self.stream_2))
+ self.verify_capture(self.pg0, pkts, self.stream_1 + self.stream_2)
+
+ def test_3_add_new_routes(self):
+ """ Add 1k routes
+
+ - re-add 5 routes check with traffic script.
+ - add 100 routes check with traffic script.
+ """
+ tmp = self.config_fib_many_to_one(
+ "10.0.0.10", self.pg0.remote_ip4, 5)
+ self.configured_routes.extend(tmp)
+ for x in tmp:
+ self.deleted_routes.remove(x)
+
+ self.configured_routes.extend(self.config_fib_many_to_one(
+ "10.0.1.0", self.pg0.remote_ip4, 100))
+
+ fib_dump = self.vapi.ip_fib_dump()
+ self.verify_route_dump(fib_dump, self.configured_routes)
+
+ self.stream_1 = self.create_stream(
+ self.pg1, self.pg0, self.configured_routes, 300)
+ self.stream_2 = self.create_stream(
+ self.pg2, self.pg0, self.configured_routes, 300)
+ self.stream_3 = self.create_stream(
+ self.pg1, self.pg0, self.deleted_routes, 100)
+ self.stream_4 = self.create_stream(
+ self.pg2, self.pg0, self.deleted_routes, 100)
+
+ self.pg1.add_stream(self.stream_1 + self.stream_3)
+ self.pg2.add_stream(self.stream_2 + self.stream_4)
+ self.pg_enable_capture(self.pg_interfaces)
+ self.pg_start()
+
+ pkts = self.pg0.get_capture(len(self.stream_1) + len(self.stream_2))
+ self.verify_capture(self.pg0, pkts, self.stream_1 + self.stream_2)
+
+ def test_4_del_routes(self):
+ """ Delete 1.5k routes
+
+ - delete 5 routes check with traffic script.
+ - add 100 routes check with traffic script.
+ """
+ self.deleted_routes.extend(self.unconfig_fib_many_to_one(
+ "10.0.0.0", self.pg0.remote_ip4, 15))
+ self.deleted_routes.extend(self.unconfig_fib_many_to_one(
+ "10.0.0.20", self.pg0.remote_ip4, 85))
+ self.deleted_routes.extend(self.unconfig_fib_many_to_one(
+ "10.0.1.0", self.pg0.remote_ip4, 100))
+ fib_dump = self.vapi.ip_fib_dump()
+ self.verify_not_in_route_dump(fib_dump, self.deleted_routes)
+
+
+class TestIPNull(VppTestCase):
+ """ IPv4 routes via NULL """
+
+ def setUp(self):
+ super(TestIPNull, self).setUp()
+
+ # create 2 pg interfaces
+ self.create_pg_interfaces(range(1))
+
+ for i in self.pg_interfaces:
+ i.admin_up()
+ i.config_ip4()
+ i.resolve_arp()
+
+ def tearDown(self):
+ super(TestIPNull, self).tearDown()
+ for i in self.pg_interfaces:
+ i.unconfig_ip4()
+ i.admin_down()
+
+ def test_ip_null(self):
+ """ IP NULL route """
+
+ #
+ # A route via IP NULL that will reply with ICMP unreachables
+ #
+ ip_unreach = VppIpRoute(self, "10.0.0.1", 32, [], is_unreach=1)
+ ip_unreach.add_vpp_config()
+
+ p_unreach = (Ether(src=self.pg0.remote_mac,
+ dst=self.pg0.local_mac) /
+ IP(src=self.pg0.remote_ip4, dst="10.0.0.1") /
+ UDP(sport=1234, dport=1234) /
+ Raw('\xa5' * 100))
+
+ self.pg0.add_stream(p_unreach)
+ self.pg_enable_capture(self.pg_interfaces)
+ self.pg_start()
+
+ rx = self.pg0.get_capture(1)
+ rx = rx[0]
+ icmp = rx[ICMP]
+
+ self.assertEqual(icmptypes[icmp.type], "dest-unreach")
+ self.assertEqual(icmpcodes[icmp.type][icmp.code], "host-unreachable")
+ self.assertEqual(icmp.src, self.pg0.remote_ip4)
+ self.assertEqual(icmp.dst, "10.0.0.1")
+
+ #
+ # ICMP replies are rate limited. so sit and spin.
+ #
+ self.sleep(1)
+
+ #
+ # A route via IP NULL that will reply with ICMP prohibited
+ #
+ ip_prohibit = VppIpRoute(self, "10.0.0.2", 32, [], is_prohibit=1)
+ ip_prohibit.add_vpp_config()
+
+ p_prohibit = (Ether(src=self.pg0.remote_mac,
+ dst=self.pg0.local_mac) /
+ IP(src=self.pg0.remote_ip4, dst="10.0.0.2") /
+ UDP(sport=1234, dport=1234) /
+ Raw('\xa5' * 100))
+
+ self.pg0.add_stream(p_prohibit)
+ self.pg_enable_capture(self.pg_interfaces)
+ self.pg_start()
+
+ rx = self.pg0.get_capture(1)
+
+ rx = rx[0]
+ icmp = rx[ICMP]
+
+ self.assertEqual(icmptypes[icmp.type], "dest-unreach")
+ self.assertEqual(icmpcodes[icmp.type][icmp.code], "host-prohibited")
+ self.assertEqual(icmp.src, self.pg0.remote_ip4)
+ self.assertEqual(icmp.dst, "10.0.0.2")
+
+
+class TestIPDisabled(VppTestCase):
+ """ IPv4 disabled """
+
+ def setUp(self):
+ super(TestIPDisabled, self).setUp()
+
+ # create 2 pg interfaces
+ self.create_pg_interfaces(range(2))
+
+ # PG0 is IP enalbed
+ self.pg0.admin_up()
+ self.pg0.config_ip4()
+ self.pg0.resolve_arp()
+
+ # PG 1 is not IP enabled
+ self.pg1.admin_up()
+
+ def tearDown(self):
+ super(TestIPDisabled, self).tearDown()
+ for i in self.pg_interfaces:
+ i.unconfig_ip4()
+ i.admin_down()
+
+ def send_and_assert_no_replies(self, intf, pkts, remark):
+ intf.add_stream(pkts)
+ self.pg_enable_capture(self.pg_interfaces)
+ self.pg_start()
+ for i in self.pg_interfaces:
+ i.get_capture(0)
+ i.assert_nothing_captured(remark=remark)
+
+ def test_ip_disabled(self):
+ """ IP Disabled """
+
+ #
+ # An (S,G).
+ # one accepting interface, pg0, 2 forwarding interfaces
+ #
+ route_232_1_1_1 = VppIpMRoute(
+ self,
+ "0.0.0.0",
+ "232.1.1.1", 32,
+ MRouteEntryFlags.MFIB_ENTRY_FLAG_NONE,
+ [VppMRoutePath(self.pg1.sw_if_index,
+ MRouteItfFlags.MFIB_ITF_FLAG_ACCEPT),
+ VppMRoutePath(self.pg0.sw_if_index,
+ MRouteItfFlags.MFIB_ITF_FLAG_FORWARD)])
+ route_232_1_1_1.add_vpp_config()
+
+ pu = (Ether(src=self.pg1.remote_mac,
+ dst=self.pg1.local_mac) /
+ IP(src="10.10.10.10", dst=self.pg0.remote_ip4) /
+ UDP(sport=1234, dport=1234) /
+ Raw('\xa5' * 100))
+ pm = (Ether(src=self.pg1.remote_mac,
+ dst=self.pg1.local_mac) /
+ IP(src="10.10.10.10", dst="232.1.1.1") /
+ UDP(sport=1234, dport=1234) /
+ Raw('\xa5' * 100))
+
+ #
+ # PG1 does not forward IP traffic
+ #
+ self.send_and_assert_no_replies(self.pg1, pu, "IP disabled")
+ self.send_and_assert_no_replies(self.pg1, pm, "IP disabled")
+
+ #
+ # IP enable PG1
+ #
+ self.pg1.config_ip4()
+
+ #
+ # Now we get packets through
+ #
+ self.pg1.add_stream(pu)
+ self.pg_enable_capture(self.pg_interfaces)
+ self.pg_start()
+ rx = self.pg0.get_capture(1)
+
+ self.pg1.add_stream(pm)
+ self.pg_enable_capture(self.pg_interfaces)
+ self.pg_start()
+ rx = self.pg0.get_capture(1)
+
+ #
+ # Disable PG1
+ #
+ self.pg1.unconfig_ip4()
+
+ #
+ # PG1 does not forward IP traffic
+ #
+ self.send_and_assert_no_replies(self.pg1, pu, "IP disabled")
+ self.send_and_assert_no_replies(self.pg1, pm, "IP disabled")
+
+
+class TestIPSubNets(VppTestCase):
+ """ IPv4 Subnets """
+
+ def setUp(self):
+ super(TestIPSubNets, self).setUp()
+
+ # create a 2 pg interfaces
+ self.create_pg_interfaces(range(2))
+
+ # pg0 we will use to experiemnt
+ self.pg0.admin_up()
+
+ # pg1 is setup normally
+ self.pg1.admin_up()
+ self.pg1.config_ip4()
+ self.pg1.resolve_arp()
+
+ def tearDown(self):
+ super(TestIPSubNets, self).tearDown()
+ for i in self.pg_interfaces:
+ i.admin_down()
+
+ def send_and_assert_no_replies(self, intf, pkts, remark):
+ intf.add_stream(pkts)
+ self.pg_enable_capture(self.pg_interfaces)
+ self.pg_start()
+ for i in self.pg_interfaces:
+ i.get_capture(0)
+ i.assert_nothing_captured(remark=remark)
+
+ def test_ip_sub_nets(self):
+ """ IP Sub Nets """
+
+ #
+ # Configure a covering route to forward so we know
+ # when we are dropping
+ #
+ cover_route = VppIpRoute(self, "10.0.0.0", 8,
+ [VppRoutePath(self.pg1.remote_ip4,
+ self.pg1.sw_if_index)])
+ cover_route.add_vpp_config()
+
+ p = (Ether(src=self.pg1.remote_mac,
+ dst=self.pg1.local_mac) /
+ IP(dst="10.10.10.10", src=self.pg0.local_ip4) /
+ UDP(sport=1234, dport=1234) /
+ Raw('\xa5' * 100))
+
+ self.pg1.add_stream(p)
+ self.pg_enable_capture(self.pg_interfaces)
+ self.pg_start()
+ rx = self.pg1.get_capture(1)
+
+ #
+ # Configure some non-/24 subnets on an IP interface
+ #
+ ip_addr_n = socket.inet_pton(socket.AF_INET, "10.10.10.10")
+
+ self.vapi.sw_interface_add_del_address(self.pg0.sw_if_index,
+ ip_addr_n,
+ 16)
+
+ pn = (Ether(src=self.pg1.remote_mac,
+ dst=self.pg1.local_mac) /
+ IP(dst="10.10.0.0", src=self.pg0.local_ip4) /
+ UDP(sport=1234, dport=1234) /
+ Raw('\xa5' * 100))
+ pb = (Ether(src=self.pg1.remote_mac,
+ dst=self.pg1.local_mac) /
+ IP(dst="10.10.255.255", src=self.pg0.local_ip4) /
+ UDP(sport=1234, dport=1234) /
+ Raw('\xa5' * 100))
+
+ self.send_and_assert_no_replies(self.pg1, pn, "IP Network address")
+ self.send_and_assert_no_replies(self.pg1, pb, "IP Broadcast address")
+
+ # remove the sub-net and we are forwarding via the cover again
+ self.vapi.sw_interface_add_del_address(self.pg0.sw_if_index,
+ ip_addr_n,
+ 16,
+ is_add=0)
+ self.pg1.add_stream(pn)
+ self.pg_enable_capture(self.pg_interfaces)
+ self.pg_start()
+ rx = self.pg1.get_capture(1)
+ self.pg1.add_stream(pb)
+ self.pg_enable_capture(self.pg_interfaces)
+ self.pg_start()
+ rx = self.pg1.get_capture(1)
+
+ #
+ # A /31 is a special case where the 'other-side' is an attached host
+ # packets to that peer generate ARP requests
+ #
+ ip_addr_n = socket.inet_pton(socket.AF_INET, "10.10.10.10")
+
+ self.vapi.sw_interface_add_del_address(self.pg0.sw_if_index,
+ ip_addr_n,
+ 31)
+
+ pn = (Ether(src=self.pg1.remote_mac,
+ dst=self.pg1.local_mac) /
+ IP(dst="10.10.10.11", src=self.pg0.local_ip4) /
+ UDP(sport=1234, dport=1234) /
+ Raw('\xa5' * 100))
+
+ self.pg1.add_stream(pn)
+ self.pg_enable_capture(self.pg_interfaces)
+ self.pg_start()
+ rx = self.pg0.get_capture(1)
+ rx[ARP]
+
+ # remove the sub-net and we are forwarding via the cover again
+ self.vapi.sw_interface_add_del_address(self.pg0.sw_if_index,
+ ip_addr_n,
+ 31,
+ is_add=0)
+ self.pg1.add_stream(pn)
+ self.pg_enable_capture(self.pg_interfaces)
+ self.pg_start()
+ rx = self.pg1.get_capture(1)
+
+
+class TestIPLoadBalance(VppTestCase):
+ """ IPv4 Load-Balancing """
+
+ def setUp(self):
+ super(TestIPLoadBalance, self).setUp()
+
+ self.create_pg_interfaces(range(5))
+ mpls_tbl = VppMplsTable(self, 0)
+ mpls_tbl.add_vpp_config()
+
+ for i in self.pg_interfaces:
+ i.admin_up()
+ i.config_ip4()
+ i.resolve_arp()
+ i.enable_mpls()
+
+ def tearDown(self):
+ for i in self.pg_interfaces:
+ i.disable_mpls()
+ i.unconfig_ip4()
+ i.admin_down()
+ super(TestIPLoadBalance, self).tearDown()
+
+ def send_and_expect_load_balancing(self, input, pkts, outputs):
+ input.add_stream(pkts)
+ self.pg_enable_capture(self.pg_interfaces)
+ self.pg_start()
+ for oo in outputs:
+ rx = oo._get_capture(1)
+ self.assertNotEqual(0, len(rx))
+
+ def send_and_expect_one_itf(self, input, pkts, itf):
+ input.add_stream(pkts)
+ self.pg_enable_capture(self.pg_interfaces)
+ self.pg_start()
+ rx = itf.get_capture(len(pkts))
+
+ def test_ip_load_balance(self):
+ """ IP Load-Balancing """
+
+ #
+ # An array of packets that differ only in the destination port
+ #
+ port_ip_pkts = []
+ port_mpls_pkts = []
+
+ #
+ # An array of packets that differ only in the source address
+ #
+ src_ip_pkts = []
+ src_mpls_pkts = []
+
+ for ii in range(65):
+ port_ip_hdr = (IP(dst="10.0.0.1", src="20.0.0.1") /
+ UDP(sport=1234, dport=1234 + ii) /
+ Raw('\xa5' * 100))
+ port_ip_pkts.append((Ether(src=self.pg0.remote_mac,
+ dst=self.pg0.local_mac) /
+ port_ip_hdr))
+ port_mpls_pkts.append((Ether(src=self.pg0.remote_mac,
+ dst=self.pg0.local_mac) /
+ MPLS(label=66, ttl=2) /
+ port_ip_hdr))
+
+ src_ip_hdr = (IP(dst="10.0.0.1", src="20.0.0.%d" % ii) /
+ UDP(sport=1234, dport=1234) /
+ Raw('\xa5' * 100))
+ src_ip_pkts.append((Ether(src=self.pg0.remote_mac,
+ dst=self.pg0.local_mac) /
+ src_ip_hdr))
+ src_mpls_pkts.append((Ether(src=self.pg0.remote_mac,
+ dst=self.pg0.local_mac) /
+ MPLS(label=66, ttl=2) /
+ src_ip_hdr))
+
+ route_10_0_0_1 = VppIpRoute(self, "10.0.0.1", 32,
+ [VppRoutePath(self.pg1.remote_ip4,
+ self.pg1.sw_if_index),
+ VppRoutePath(self.pg2.remote_ip4,
+ self.pg2.sw_if_index)])
+ route_10_0_0_1.add_vpp_config()
+
+ binding = VppMplsIpBind(self, 66, "10.0.0.1", 32)
+ binding.add_vpp_config()
+
+ #
+ # inject the packet on pg0 - expect load-balancing across the 2 paths
+ # - since the default hash config is to use IP src,dst and port
+ # src,dst
+ # We are not going to ensure equal amounts of packets across each link,
+ # since the hash algorithm is statistical and therefore this can never
+ # be guaranteed. But wuth 64 different packets we do expect some
+ # balancing. So instead just ensure there is traffic on each link.
+ #
+ self.send_and_expect_load_balancing(self.pg0, port_ip_pkts,
+ [self.pg1, self.pg2])
+ self.send_and_expect_load_balancing(self.pg0, src_ip_pkts,
+ [self.pg1, self.pg2])
+ self.send_and_expect_load_balancing(self.pg0, port_mpls_pkts,
+ [self.pg1, self.pg2])
+ self.send_and_expect_load_balancing(self.pg0, src_mpls_pkts,
+ [self.pg1, self.pg2])
+
+ #
+ # change the flow hash config so it's only IP src,dst
+ # - now only the stream with differing source address will
+ # load-balance
+ #
+ self.vapi.set_ip_flow_hash(0, src=1, dst=1, sport=0, dport=0)
+
+ self.send_and_expect_load_balancing(self.pg0, src_ip_pkts,
+ [self.pg1, self.pg2])
+ self.send_and_expect_load_balancing(self.pg0, src_mpls_pkts,
+ [self.pg1, self.pg2])
+
+ self.send_and_expect_one_itf(self.pg0, port_ip_pkts, self.pg2)
+
+ #
+ # change the flow hash config back to defaults
+ #
+ self.vapi.set_ip_flow_hash(0, src=1, dst=1, sport=1, dport=1)
+
+ #
+ # Recursive prefixes
+ # - testing that 2 stages of load-balancing occurs and there is no
+ # polarisation (i.e. only 2 of 4 paths are used)
+ #
+ port_pkts = []
+ src_pkts = []
+
+ for ii in range(257):
+ port_pkts.append((Ether(src=self.pg0.remote_mac,
+ dst=self.pg0.local_mac) /
+ IP(dst="1.1.1.1", src="20.0.0.1") /
+ UDP(sport=1234, dport=1234 + ii) /
+ Raw('\xa5' * 100)))
+ src_pkts.append((Ether(src=self.pg0.remote_mac,
+ dst=self.pg0.local_mac) /
+ IP(dst="1.1.1.1", src="20.0.0.%d" % ii) /
+ UDP(sport=1234, dport=1234) /
+ Raw('\xa5' * 100)))
+
+ route_10_0_0_2 = VppIpRoute(self, "10.0.0.2", 32,
+ [VppRoutePath(self.pg3.remote_ip4,
+ self.pg3.sw_if_index),
+ VppRoutePath(self.pg4.remote_ip4,
+ self.pg4.sw_if_index)])
+ route_10_0_0_2.add_vpp_config()
+
+ route_1_1_1_1 = VppIpRoute(self, "1.1.1.1", 32,
+ [VppRoutePath("10.0.0.2", 0xffffffff),
+ VppRoutePath("10.0.0.1", 0xffffffff)])
+ route_1_1_1_1.add_vpp_config()
+
+ #
+ # inject the packet on pg0 - expect load-balancing across all 4 paths
+ #
+ self.vapi.cli("clear trace")
+ self.send_and_expect_load_balancing(self.pg0, port_pkts,
+ [self.pg1, self.pg2,
+ self.pg3, self.pg4])
+ self.send_and_expect_load_balancing(self.pg0, src_pkts,
+ [self.pg1, self.pg2,
+ self.pg3, self.pg4])
+
+ #
+ # Recursive prefixes
+ # - testing that 2 stages of load-balancing, no choices
+ #
+ port_pkts = []
+
+ for ii in range(257):
+ port_pkts.append((Ether(src=self.pg0.remote_mac,
+ dst=self.pg0.local_mac) /
+ IP(dst="1.1.1.2", src="20.0.0.2") /
+ UDP(sport=1234, dport=1234 + ii) /
+ Raw('\xa5' * 100)))
+
+ route_10_0_0_3 = VppIpRoute(self, "10.0.0.3", 32,
+ [VppRoutePath(self.pg3.remote_ip4,
+ self.pg3.sw_if_index)])
+ route_10_0_0_3.add_vpp_config()
+
+ route_1_1_1_2 = VppIpRoute(self, "1.1.1.2", 32,
+ [VppRoutePath("10.0.0.3", 0xffffffff)])
+ route_1_1_1_2.add_vpp_config()
+
+ #
+ # inject the packet on pg0 - expect load-balancing across all 4 paths
+ #
+ self.vapi.cli("clear trace")
+ self.send_and_expect_one_itf(self.pg0, port_pkts, self.pg3)
+
+
+class TestIPVlan0(VppTestCase):
+ """ IPv4 VLAN-0 """
+
+ def setUp(self):
+ super(TestIPVlan0, self).setUp()
+
+ self.create_pg_interfaces(range(2))
+ mpls_tbl = VppMplsTable(self, 0)
+ mpls_tbl.add_vpp_config()
+
+ for i in self.pg_interfaces:
+ i.admin_up()
+ i.config_ip4()
+ i.resolve_arp()
+ i.enable_mpls()
+
+ def tearDown(self):
+ for i in self.pg_interfaces:
+ i.disable_mpls()
+ i.unconfig_ip4()
+ i.admin_down()
+ super(TestIPVlan0, self).tearDown()
+
+ def send_and_expect(self, input, pkts, output):
+ input.add_stream(pkts)
+ self.pg_enable_capture(self.pg_interfaces)
+ self.pg_start()
+ rx = output.get_capture(len(pkts))
+
+ def test_ip_vlan_0(self):
+ """ IP VLAN-0 """
+
+ pkts = (Ether(src=self.pg0.remote_mac,
+ dst=self.pg0.local_mac) /
+ Dot1Q(vlan=0) /
+ IP(dst=self.pg1.remote_ip4,
+ src=self.pg0.remote_ip4) /
+ UDP(sport=1234, dport=1234) /
+ Raw('\xa5' * 100)) * 65
+
+ #
+ # Expect that packets sent on VLAN-0 are forwarded on the
+ # main interface.
+ #
+ self.send_and_expect(self.pg0, pkts, self.pg1)
+
+
+if __name__ == '__main__':
+ unittest.main(testRunner=VppTestRunner)
diff --git a/test/test_ip4_irb.py b/test/test_ip4_irb.py
new file mode 100644
index 00000000..bbec7ca7
--- /dev/null
+++ b/test/test_ip4_irb.py
@@ -0,0 +1,263 @@
+#!/usr/bin/env python
+"""IRB Test Case HLD:
+
+**config**
+ - L2 MAC learning enabled in l2bd
+ - 2 routed interfaces untagged, bvi (Bridge Virtual Interface)
+ - 2 bridged interfaces in l2bd with bvi
+
+**test**
+ - sending ip4 eth pkts between routed interfaces
+ - 2 routed interfaces
+ - 2 bridged interfaces
+
+ - 64B, 512B, 1518B, 9200B (ether_size)
+
+ - burst of pkts per interface
+ - 257pkts per burst
+ - routed pkts hitting different FIB entries
+ - bridged pkts hitting different MAC entries
+
+**verify**
+ - all packets received correctly
+
+"""
+
+import unittest
+from random import choice
+
+from scapy.packet import Raw
+from scapy.layers.l2 import Ether
+from scapy.layers.inet import IP, UDP
+
+from framework import VppTestCase, VppTestRunner
+
+
+class TestIpIrb(VppTestCase):
+ """IRB Test Case"""
+
+ @classmethod
+ def setUpClass(cls):
+ """
+ #. Create BD with MAC learning enabled and put interfaces to this BD.
+ #. Configure IPv4 addresses on loopback interface and routed interface.
+ #. Configure MAC address binding to IPv4 neighbors on loop0.
+ #. Configure MAC address on pg2.
+ #. Loopback BVI interface has remote hosts, one half of hosts are
+ behind pg0 second behind pg1.
+ """
+ super(TestIpIrb, cls).setUpClass()
+
+ cls.pg_if_packet_sizes = [64, 512, 1518, 9018] # packet sizes
+ cls.bd_id = 10
+ cls.remote_hosts_count = 250
+
+ # create 3 pg interfaces, 1 loopback interface
+ cls.create_pg_interfaces(range(3))
+ cls.create_loopback_interfaces(range(1))
+
+ cls.interfaces = list(cls.pg_interfaces)
+ cls.interfaces.extend(cls.lo_interfaces)
+
+ for i in cls.interfaces:
+ i.admin_up()
+
+ # Create BD with MAC learning enabled and put interfaces to this BD
+ cls.vapi.sw_interface_set_l2_bridge(
+ cls.loop0.sw_if_index, bd_id=cls.bd_id, bvi=1)
+ cls.vapi.sw_interface_set_l2_bridge(
+ cls.pg0.sw_if_index, bd_id=cls.bd_id)
+ cls.vapi.sw_interface_set_l2_bridge(
+ cls.pg1.sw_if_index, bd_id=cls.bd_id)
+
+ # Configure IPv4 addresses on loopback interface and routed interface
+ cls.loop0.config_ip4()
+ cls.pg2.config_ip4()
+
+ # Configure MAC address binding to IPv4 neighbors on loop0
+ cls.loop0.generate_remote_hosts(cls.remote_hosts_count)
+ cls.loop0.configure_ipv4_neighbors()
+ # configure MAC address on pg2
+ cls.pg2.resolve_arp()
+
+ # Loopback BVI interface has remote hosts, one half of hosts are behind
+ # pg0 second behind pg1
+ half = cls.remote_hosts_count // 2
+ cls.pg0.remote_hosts = cls.loop0.remote_hosts[:half]
+ cls.pg1.remote_hosts = cls.loop0.remote_hosts[half:]
+
+ def tearDown(self):
+ """Run standard test teardown and log ``show l2patch``,
+ ``show l2fib verbose``,``show bridge-domain <bd_id> detail``,
+ ``show ip arp``.
+ """
+ super(TestIpIrb, self).tearDown()
+ if not self.vpp_dead:
+ self.logger.info(self.vapi.cli("show l2patch"))
+ self.logger.info(self.vapi.cli("show l2fib verbose"))
+ self.logger.info(self.vapi.cli("show bridge-domain %s detail" %
+ self.bd_id))
+ self.logger.info(self.vapi.cli("show ip arp"))
+
+ def create_stream(self, src_ip_if, dst_ip_if, packet_sizes):
+ pkts = []
+ for i in range(0, 257):
+ remote_dst_host = choice(dst_ip_if.remote_hosts)
+ info = self.create_packet_info(src_ip_if, dst_ip_if)
+ payload = self.info_to_payload(info)
+ p = (Ether(dst=src_ip_if.local_mac, src=src_ip_if.remote_mac) /
+ IP(src=src_ip_if.remote_ip4,
+ dst=remote_dst_host.ip4) /
+ UDP(sport=1234, dport=1234) /
+ Raw(payload))
+ info.data = p.copy()
+ size = packet_sizes[(i // 2) % len(packet_sizes)]
+ self.extend_packet(p, size)
+ pkts.append(p)
+ return pkts
+
+ def create_stream_l2_to_ip(self, src_l2_if, src_ip_if, dst_ip_if,
+ packet_sizes):
+ pkts = []
+ for i in range(0, 257):
+ info = self.create_packet_info(src_ip_if, dst_ip_if)
+ payload = self.info_to_payload(info)
+
+ host = choice(src_l2_if.remote_hosts)
+
+ p = (Ether(src=host.mac,
+ dst=src_ip_if.local_mac) /
+ IP(src=host.ip4,
+ dst=dst_ip_if.remote_ip4) /
+ UDP(sport=1234, dport=1234) /
+ Raw(payload))
+
+ info.data = p.copy()
+ size = packet_sizes[(i // 2) % len(packet_sizes)]
+ self.extend_packet(p, size)
+
+ pkts.append(p)
+ return pkts
+
+ def verify_capture_l2_to_ip(self, dst_ip_if, src_ip_if, capture):
+ last_info = dict()
+ for i in self.interfaces:
+ last_info[i.sw_if_index] = None
+
+ dst_ip_sw_if_index = dst_ip_if.sw_if_index
+
+ for packet in capture:
+ ip = packet[IP]
+ udp = packet[IP][UDP]
+ payload_info = self.payload_to_info(str(packet[IP][UDP][Raw]))
+
+ self.assertEqual(payload_info.dst, dst_ip_sw_if_index)
+
+ next_info = self.get_next_packet_info_for_interface2(
+ payload_info.src, dst_ip_sw_if_index,
+ last_info[payload_info.src])
+ last_info[payload_info.src] = next_info
+ self.assertTrue(next_info is not None)
+ saved_packet = next_info.data
+ self.assertTrue(next_info is not None)
+
+ # MAC: src, dst
+ self.assertEqual(packet.src, dst_ip_if.local_mac)
+ self.assertEqual(packet.dst, dst_ip_if.remote_mac)
+
+ # IP: src, dst
+ host = src_ip_if.host_by_ip4(ip.src)
+ self.assertIsNotNone(host)
+ self.assertEqual(ip.dst, saved_packet[IP].dst)
+ self.assertEqual(ip.dst, dst_ip_if.remote_ip4)
+
+ # UDP:
+ self.assertEqual(udp.sport, saved_packet[UDP].sport)
+ self.assertEqual(udp.dport, saved_packet[UDP].dport)
+
+ def verify_capture(self, dst_ip_if, src_ip_if, capture):
+ last_info = dict()
+ for i in self.interfaces:
+ last_info[i.sw_if_index] = None
+
+ dst_ip_sw_if_index = dst_ip_if.sw_if_index
+
+ for packet in capture:
+ ip = packet[IP]
+ udp = packet[IP][UDP]
+ payload_info = self.payload_to_info(str(packet[IP][UDP][Raw]))
+ packet_index = payload_info.index
+
+ self.assertEqual(payload_info.dst, dst_ip_sw_if_index)
+
+ next_info = self.get_next_packet_info_for_interface2(
+ payload_info.src, dst_ip_sw_if_index,
+ last_info[payload_info.src])
+ last_info[payload_info.src] = next_info
+ self.assertTrue(next_info is not None)
+ self.assertEqual(packet_index, next_info.index)
+ saved_packet = next_info.data
+ self.assertTrue(next_info is not None)
+
+ # MAC: src, dst
+ self.assertEqual(packet.src, dst_ip_if.local_mac)
+ host = dst_ip_if.host_by_mac(packet.dst)
+
+ # IP: src, dst
+ self.assertEqual(ip.src, src_ip_if.remote_ip4)
+ self.assertEqual(ip.dst, saved_packet[IP].dst)
+ self.assertEqual(ip.dst, host.ip4)
+
+ # UDP:
+ self.assertEqual(udp.sport, saved_packet[UDP].sport)
+ self.assertEqual(udp.dport, saved_packet[UDP].dport)
+
+ def test_ip4_irb_1(self):
+ """ IPv4 IRB test 1
+
+ Test scenario:
+ - ip traffic from pg2 interface must ends in both pg0 and pg1
+ - arp entry present in loop0 interface for destination IP
+ - no l2 entree configured, pg0 and pg1 are same
+ """
+
+ stream = self.create_stream(
+ self.pg2, self.loop0, self.pg_if_packet_sizes)
+ self.pg2.add_stream(stream)
+
+ self.pg_enable_capture(self.pg_interfaces)
+ self.pg_start()
+
+ packet_count = self.get_packet_count_for_if_idx(self.loop0.sw_if_index)
+
+ rcvd1 = self.pg0.get_capture(packet_count)
+ rcvd2 = self.pg1.get_capture(packet_count)
+
+ self.verify_capture(self.loop0, self.pg2, rcvd1)
+ self.verify_capture(self.loop0, self.pg2, rcvd2)
+
+ self.assertListEqual(rcvd1.res, rcvd2.res)
+
+ def test_ip4_irb_2(self):
+ """ IPv4 IRB test 2
+
+ Test scenario:
+ - ip traffic from pg0 and pg1 ends on pg2
+ """
+
+ stream1 = self.create_stream_l2_to_ip(
+ self.pg0, self.loop0, self.pg2, self.pg_if_packet_sizes)
+ stream2 = self.create_stream_l2_to_ip(
+ self.pg1, self.loop0, self.pg2, self.pg_if_packet_sizes)
+ self.pg0.add_stream(stream1)
+ self.pg1.add_stream(stream2)
+
+ self.pg_enable_capture(self.pg_interfaces)
+ self.pg_start()
+
+ rcvd = self.pg2.get_capture()
+ self.verify_capture_l2_to_ip(self.pg2, self.loop0, rcvd)
+
+
+if __name__ == '__main__':
+ unittest.main(testRunner=VppTestRunner)
diff --git a/test/test_ip4_vrf_multi_instance.py b/test/test_ip4_vrf_multi_instance.py
new file mode 100644
index 00000000..5a8d6760
--- /dev/null
+++ b/test/test_ip4_vrf_multi_instance.py
@@ -0,0 +1,414 @@
+#!/usr/bin/env python
+"""IP4 VRF Multi-instance Test Case HLD:
+
+**NOTES:**
+ - higher number of pg-ip4 interfaces causes problems => only 15 pg-ip4 \
+ interfaces in 5 VRFs are tested
+ - jumbo packets in configuration with 15 pg-ip4 interfaces leads to \
+ problems too
+ - Reset of FIB table / VRF does not remove routes from IP FIB (see Jira \
+ ticket https://jira.fd.io/browse/VPP-560) so checks of reset VRF tables \
+ are skipped in tests 2, 3 and 4
+
+**config 1**
+ - add 15 pg-ip4 interfaces
+ - configure 5 hosts per pg-ip4 interface
+ - configure 4 VRFs
+ - add 3 pg-ip4 interfaces per VRF
+
+**test 1**
+ - send IP4 packets between all pg-ip4 interfaces in all VRF groups
+
+**verify 1**
+ - check VRF data by parsing output of ip_fib_dump API command
+ - all packets received correctly in case of pg-ip4 interfaces in VRF
+ - no packet received in case of pg-ip4 interfaces not in VRF
+
+**config 2**
+ - delete 2 VRFs
+
+**test 2**
+ - send IP4 packets between all pg-ip4 interfaces in all VRF groups
+
+**verify 2**
+ - check VRF data by parsing output of ip_fib_dump API command
+ - all packets received correctly in case of pg-ip4 interfaces in VRF
+ - no packet received in case of pg-ip4 interfaces not in VRF
+
+**config 3**
+ - add 1 of deleted VRFs and 1 new VRF
+
+**test 3**
+ - send IP4 packets between all pg-ip4 interfaces in all VRF groups
+
+**verify 3**
+ - check VRF data by parsing output of ip_fib_dump API command
+ - all packets received correctly in case of pg-ip4 interfaces in VRF
+ - no packet received in case of pg-ip4 interfaces not in VRF
+
+**config 4**
+ - delete all VRFs (i.e. no VRF except VRF=0 created)
+
+**test 4**
+ - send IP4 packets between all pg-ip4 interfaces in all VRF groups
+
+**verify 4**
+ - check VRF data by parsing output of ip_fib_dump API command
+ - all packets received correctly in case of pg-ip4 interfaces in VRF
+ - no packet received in case of pg-ip4 interfaces not in VRF
+"""
+
+import unittest
+import random
+
+from scapy.packet import Raw
+from scapy.layers.l2 import Ether
+from scapy.layers.inet import IP, UDP, ARP
+
+from framework import VppTestCase, VppTestRunner
+from util import ppp
+
+
+def is_ipv4_misc(p):
+ """ Is packet one of uninteresting IPv4 broadcasts? """
+ if p.haslayer(ARP):
+ return True
+ return False
+
+
+class TestIp4VrfMultiInst(VppTestCase):
+ """ IP4 VRF Multi-instance Test Case """
+
+ @classmethod
+ def setUpClass(cls):
+ """
+ Perform standard class setup (defined by class method setUpClass in
+ class VppTestCase) before running the test case, set test case related
+ variables and configure VPP.
+ """
+ super(TestIp4VrfMultiInst, cls).setUpClass()
+
+ # Test variables
+ cls.hosts_per_pg = 5
+ cls.nr_of_vrfs = 5
+ cls.pg_ifs_per_vrf = 3
+
+ try:
+ # Create pg interfaces
+ cls.create_pg_interfaces(
+ range(cls.nr_of_vrfs * cls.pg_ifs_per_vrf))
+
+ # Packet flows mapping pg0 -> pg1, pg2 etc.
+ cls.flows = dict()
+ for i in range(len(cls.pg_interfaces)):
+ multiplicand = i / cls.pg_ifs_per_vrf
+ pg_list = [
+ cls.pg_interfaces[multiplicand * cls.pg_ifs_per_vrf + j]
+ for j in range(cls.pg_ifs_per_vrf)
+ if (multiplicand * cls.pg_ifs_per_vrf + j) != i]
+ cls.flows[cls.pg_interfaces[i]] = pg_list
+
+ # Packet sizes - jumbo packet (9018 bytes) skipped
+ cls.pg_if_packet_sizes = [64, 512, 1518]
+
+ # Set up all interfaces
+ for pg_if in cls.pg_interfaces:
+ pg_if.admin_up()
+ pg_if.generate_remote_hosts(cls.hosts_per_pg)
+
+ # Create list of VRFs
+ cls.vrf_list = list()
+
+ # Create list of deleted VRFs
+ cls.vrf_deleted_list = list()
+
+ # Create list of pg_interfaces in VRFs
+ cls.pg_in_vrf = list()
+
+ # Create list of pg_interfaces not in BDs
+ cls.pg_not_in_vrf = [pg_if for pg_if in cls.pg_interfaces]
+
+ # Create mapping of pg_interfaces to VRF IDs
+ cls.pg_if_by_vrf_id = dict()
+ for i in range(cls.nr_of_vrfs):
+ vrf_id = i + 1
+ pg_list = [
+ cls.pg_interfaces[i * cls.pg_ifs_per_vrf + j]
+ for j in range(cls.pg_ifs_per_vrf)]
+ cls.pg_if_by_vrf_id[vrf_id] = pg_list
+
+ except Exception:
+ super(TestIp4VrfMultiInst, cls).tearDownClass()
+ raise
+
+ def setUp(self):
+ """
+ Clear trace and packet infos before running each test.
+ """
+ super(TestIp4VrfMultiInst, self).setUp()
+ self.reset_packet_infos()
+
+ def tearDown(self):
+ """
+ Show various debug prints after each test.
+ """
+ super(TestIp4VrfMultiInst, self).tearDown()
+ if not self.vpp_dead:
+ self.logger.info(self.vapi.ppcli("show ip fib"))
+ self.logger.info(self.vapi.ppcli("show ip arp"))
+
+ def create_vrf_and_assign_interfaces(self, count, start=1):
+ """
+ Create required number of FIB tables / VRFs, put 3 l2-pg interfaces
+ to every FIB table / VRF.
+
+ :param int count: Number of FIB tables / VRFs to be created.
+ :param int start: Starting number of the FIB table / VRF ID. \
+ (Default value = 1)
+ """
+
+ for i in range(count):
+ vrf_id = i + start
+ pg_if = self.pg_if_by_vrf_id[vrf_id][0]
+ dest_addr = pg_if.remote_hosts[0].ip4n
+ dest_addr_len = 24
+ self.vapi.ip_table_add_del(vrf_id, is_add=1)
+ self.vapi.ip_add_del_route(
+ dest_addr, dest_addr_len, pg_if.local_ip4n,
+ table_id=vrf_id, is_multipath=1)
+ self.logger.info("IPv4 VRF ID %d created" % vrf_id)
+ if vrf_id not in self.vrf_list:
+ self.vrf_list.append(vrf_id)
+ if vrf_id in self.vrf_deleted_list:
+ self.vrf_deleted_list.remove(vrf_id)
+ for j in range(self.pg_ifs_per_vrf):
+ pg_if = self.pg_if_by_vrf_id[vrf_id][j]
+ pg_if.set_table_ip4(vrf_id)
+ self.logger.info("pg-interface %s added to IPv4 VRF ID %d"
+ % (pg_if.name, vrf_id))
+ if pg_if not in self.pg_in_vrf:
+ self.pg_in_vrf.append(pg_if)
+ if pg_if in self.pg_not_in_vrf:
+ self.pg_not_in_vrf.remove(pg_if)
+ pg_if.config_ip4()
+ pg_if.configure_ipv4_neighbors()
+ self.logger.debug(self.vapi.ppcli("show ip fib"))
+ self.logger.debug(self.vapi.ppcli("show ip arp"))
+
+ def delete_vrf(self, vrf_id):
+ """
+ Delete required FIB table / VRF.
+
+ :param int vrf_id: The FIB table / VRF ID to be deleted.
+ """
+ # self.vapi.reset_vrf(vrf_id, is_ipv6=0)
+ self.vapi.reset_fib(vrf_id, is_ipv6=0)
+ if vrf_id in self.vrf_list:
+ self.vrf_list.remove(vrf_id)
+ if vrf_id not in self.vrf_deleted_list:
+ self.vrf_deleted_list.append(vrf_id)
+ for j in range(self.pg_ifs_per_vrf):
+ pg_if = self.pg_if_by_vrf_id[vrf_id][j]
+ pg_if.unconfig_ip4()
+ if pg_if in self.pg_in_vrf:
+ self.pg_in_vrf.remove(pg_if)
+ if pg_if not in self.pg_not_in_vrf:
+ self.pg_not_in_vrf.append(pg_if)
+ self.logger.info("IPv4 VRF ID %d reset" % vrf_id)
+ self.logger.debug(self.vapi.ppcli("show ip fib"))
+ self.logger.debug(self.vapi.ppcli("show ip arp"))
+ self.vapi.ip_table_add_del(vrf_id, is_add=0)
+
+ def create_stream(self, src_if, packet_sizes):
+ """
+ Create input packet stream for defined interface using hosts list.
+
+ :param object src_if: Interface to create packet stream for.
+ :param list packet_sizes: List of required packet sizes.
+ :return: Stream of packets.
+ """
+ pkts = []
+ src_hosts = src_if.remote_hosts
+ for dst_if in self.flows[src_if]:
+ for dst_host in dst_if.remote_hosts:
+ src_host = random.choice(src_hosts)
+ pkt_info = self.create_packet_info(src_if, dst_if)
+ payload = self.info_to_payload(pkt_info)
+ p = (Ether(dst=src_if.local_mac, src=src_host.mac) /
+ IP(src=src_host.ip4, dst=dst_host.ip4) /
+ UDP(sport=1234, dport=1234) /
+ Raw(payload))
+ pkt_info.data = p.copy()
+ size = random.choice(packet_sizes)
+ self.extend_packet(p, size)
+ pkts.append(p)
+ self.logger.debug("Input stream created for port %s. Length: %u pkt(s)"
+ % (src_if.name, len(pkts)))
+ return pkts
+
+ def verify_capture(self, pg_if, capture):
+ """
+ Verify captured input packet stream for defined interface.
+
+ :param object pg_if: Interface to verify captured packet stream for.
+ :param list capture: Captured packet stream.
+ """
+ last_info = dict()
+ for i in self.pg_interfaces:
+ last_info[i.sw_if_index] = None
+ dst_sw_if_index = pg_if.sw_if_index
+ for packet in capture:
+ try:
+ ip = packet[IP]
+ udp = packet[UDP]
+ payload_info = self.payload_to_info(str(packet[Raw]))
+ packet_index = payload_info.index
+ self.assertEqual(payload_info.dst, dst_sw_if_index)
+ self.logger.debug("Got packet on port %s: src=%u (id=%u)" %
+ (pg_if.name, payload_info.src, packet_index))
+ next_info = self.get_next_packet_info_for_interface2(
+ payload_info.src, dst_sw_if_index,
+ last_info[payload_info.src])
+ last_info[payload_info.src] = next_info
+ self.assertIsNotNone(next_info)
+ self.assertEqual(packet_index, next_info.index)
+ saved_packet = next_info.data
+ # Check standard fields
+ self.assertEqual(ip.src, saved_packet[IP].src)
+ self.assertEqual(ip.dst, saved_packet[IP].dst)
+ self.assertEqual(udp.sport, saved_packet[UDP].sport)
+ self.assertEqual(udp.dport, saved_packet[UDP].dport)
+ except:
+ self.logger.error(ppp("Unexpected or invalid packet:", packet))
+ raise
+ for i in self.pg_interfaces:
+ remaining_packet = self.get_next_packet_info_for_interface2(
+ i, dst_sw_if_index, last_info[i.sw_if_index])
+ self.assertIsNone(
+ remaining_packet,
+ "Port %u: Packet expected from source %u didn't arrive" %
+ (dst_sw_if_index, i.sw_if_index))
+
+ def verify_vrf(self, vrf_id):
+ """
+ Check if the FIB table / VRF ID is configured.
+
+ :param int vrf_id: The FIB table / VRF ID to be verified.
+ :return: 1 if the FIB table / VRF ID is configured, otherwise return 0.
+ """
+ ip_fib_dump = self.vapi.ip_fib_dump()
+ vrf_count = 0
+ for ip_fib_details in ip_fib_dump:
+ if ip_fib_details[2] == vrf_id:
+ vrf_count += 1
+ if vrf_count == 0:
+ self.logger.info("IPv4 VRF ID %d is not configured" % vrf_id)
+ return 0
+ else:
+ self.logger.info("IPv4 VRF ID %d is configured" % vrf_id)
+ return 1
+
+ def run_verify_test(self):
+ """
+ Create packet streams for all configured l2-pg interfaces, send all \
+ prepared packet streams and verify that:
+ - all packets received correctly on all pg-l2 interfaces assigned
+ to bridge domains
+ - no packet received on all pg-l2 interfaces not assigned to bridge
+ domains
+
+ :raise RuntimeError: If no packet captured on l2-pg interface assigned
+ to the bridge domain or if any packet is captured on l2-pg
+ interface not assigned to the bridge domain.
+ """
+ # Test
+ # Create incoming packet streams for packet-generator interfaces
+ for pg_if in self.pg_interfaces:
+ pkts = self.create_stream(pg_if, self.pg_if_packet_sizes)
+ pg_if.add_stream(pkts)
+
+ # Enable packet capture and start packet sending
+ self.pg_enable_capture(self.pg_interfaces)
+ self.pg_start()
+
+ # Verify
+ # Verify outgoing packet streams per packet-generator interface
+ for pg_if in self.pg_interfaces:
+ if pg_if in self.pg_in_vrf:
+ capture = pg_if.get_capture(remark="interface is in VRF")
+ self.verify_capture(pg_if, capture)
+ elif pg_if in self.pg_not_in_vrf:
+ pg_if.assert_nothing_captured(remark="interface is not in VRF",
+ filter_out_fn=is_ipv4_misc)
+ self.logger.debug("No capture for interface %s" % pg_if.name)
+ else:
+ raise Exception("Unknown interface: %s" % pg_if.name)
+
+ def test_ip4_vrf_01(self):
+ """ IP4 VRF Multi-instance test 1 - create 5 BDs
+ """
+ # Config 1
+ # Create 4 VRFs
+ self.create_vrf_and_assign_interfaces(4)
+
+ # Verify 1
+ for vrf_id in self.vrf_list:
+ self.assertEqual(self.verify_vrf(vrf_id), 1)
+
+ # Test 1
+ self.run_verify_test()
+
+ def test_ip4_vrf_02(self):
+ """ IP4 VRF Multi-instance test 2 - delete 2 VRFs
+ """
+ # Config 2
+ # Delete 2 VRFs
+ self.delete_vrf(1)
+ self.delete_vrf(2)
+
+ # Verify 2
+ # for vrf_id in self.vrf_deleted_list:
+ # self.assertEqual(self.verify_vrf(vrf_id), 0)
+ for vrf_id in self.vrf_list:
+ self.assertEqual(self.verify_vrf(vrf_id), 1)
+
+ # Test 2
+ self.run_verify_test()
+
+ def test_ip4_vrf_03(self):
+ """ IP4 VRF Multi-instance 3 - add 2 VRFs
+ """
+ # Config 3
+ # Add 1 of deleted VRFs and 1 new VRF
+ self.create_vrf_and_assign_interfaces(1)
+ self.create_vrf_and_assign_interfaces(1, start=5)
+
+ # Verify 3
+ # for vrf_id in self.vrf_deleted_list:
+ # self.assertEqual(self.verify_vrf(vrf_id), 0)
+ for vrf_id in self.vrf_list:
+ self.assertEqual(self.verify_vrf(vrf_id), 1)
+
+ # Test 3
+ self.run_verify_test()
+
+ def test_ip4_vrf_04(self):
+ """ IP4 VRF Multi-instance test 4 - delete 4 VRFs
+ """
+ # Config 4
+ # Delete all VRFs (i.e. no VRF except VRF=0 created)
+ for i in range(len(self.vrf_list)):
+ self.delete_vrf(self.vrf_list[0])
+
+ # Verify 4
+ # for vrf_id in self.vrf_deleted_list:
+ # self.assertEqual(self.verify_vrf(vrf_id), 0)
+ for vrf_id in self.vrf_list:
+ self.assertEqual(self.verify_vrf(vrf_id), 1)
+
+ # Test 4
+ self.run_verify_test()
+
+
+if __name__ == '__main__':
+ unittest.main(testRunner=VppTestRunner)
diff --git a/test/test_ip6.py b/test/test_ip6.py
new file mode 100644
index 00000000..aad3713c
--- /dev/null
+++ b/test/test_ip6.py
@@ -0,0 +1,1510 @@
+#!/usr/bin/env python
+
+import unittest
+from socket import AF_INET6
+
+from framework import VppTestCase, VppTestRunner
+from vpp_sub_interface import VppSubInterface, VppDot1QSubint
+from vpp_pg_interface import is_ipv6_misc
+from vpp_ip_route import VppIpRoute, VppRoutePath, find_route, VppIpMRoute, \
+ VppMRoutePath, MRouteItfFlags, MRouteEntryFlags, VppMplsIpBind, \
+ VppMplsRoute, DpoProto, VppMplsTable
+from vpp_neighbor import find_nbr, VppNeighbor
+
+from scapy.packet import Raw
+from scapy.layers.l2 import Ether, Dot1Q
+from scapy.layers.inet6 import IPv6, UDP, ICMPv6ND_NS, ICMPv6ND_RS, \
+ ICMPv6ND_RA, ICMPv6NDOptSrcLLAddr, getmacbyip6, ICMPv6MRD_Solicitation, \
+ ICMPv6NDOptMTU, ICMPv6NDOptSrcLLAddr, ICMPv6NDOptPrefixInfo, \
+ ICMPv6ND_NA, ICMPv6NDOptDstLLAddr, ICMPv6DestUnreach, icmp6types
+
+from util import ppp
+from scapy.utils6 import in6_getnsma, in6_getnsmac, in6_ptop, in6_islladdr, \
+ in6_mactoifaceid, in6_ismaddr
+from scapy.utils import inet_pton, inet_ntop
+from scapy.contrib.mpls import MPLS
+
+
+def mk_ll_addr(mac):
+ euid = in6_mactoifaceid(mac)
+ addr = "fe80::" + euid
+ return addr
+
+
+class TestIPv6ND(VppTestCase):
+ def validate_ra(self, intf, rx, dst_ip=None):
+ if not dst_ip:
+ dst_ip = intf.remote_ip6
+
+ # unicasted packets must come to the unicast mac
+ self.assertEqual(rx[Ether].dst, intf.remote_mac)
+
+ # and from the router's MAC
+ self.assertEqual(rx[Ether].src, intf.local_mac)
+
+ # the rx'd RA should be addressed to the sender's source
+ self.assertTrue(rx.haslayer(ICMPv6ND_RA))
+ self.assertEqual(in6_ptop(rx[IPv6].dst),
+ in6_ptop(dst_ip))
+
+ # and come from the router's link local
+ self.assertTrue(in6_islladdr(rx[IPv6].src))
+ self.assertEqual(in6_ptop(rx[IPv6].src),
+ in6_ptop(mk_ll_addr(intf.local_mac)))
+
+ def validate_na(self, intf, rx, dst_ip=None, tgt_ip=None):
+ if not dst_ip:
+ dst_ip = intf.remote_ip6
+ if not tgt_ip:
+ dst_ip = intf.local_ip6
+
+ # unicasted packets must come to the unicast mac
+ self.assertEqual(rx[Ether].dst, intf.remote_mac)
+
+ # and from the router's MAC
+ self.assertEqual(rx[Ether].src, intf.local_mac)
+
+ # the rx'd NA should be addressed to the sender's source
+ self.assertTrue(rx.haslayer(ICMPv6ND_NA))
+ self.assertEqual(in6_ptop(rx[IPv6].dst),
+ in6_ptop(dst_ip))
+
+ # and come from the target address
+ self.assertEqual(in6_ptop(rx[IPv6].src), in6_ptop(tgt_ip))
+
+ # Dest link-layer options should have the router's MAC
+ dll = rx[ICMPv6NDOptDstLLAddr]
+ self.assertEqual(dll.lladdr, intf.local_mac)
+
+ def validate_ns(self, intf, rx, tgt_ip):
+ nsma = in6_getnsma(inet_pton(AF_INET6, tgt_ip))
+ dst_ip = inet_ntop(AF_INET6, nsma)
+
+ # NS is broadcast
+ self.assertEqual(rx[Ether].dst, "ff:ff:ff:ff:ff:ff")
+
+ # and from the router's MAC
+ self.assertEqual(rx[Ether].src, intf.local_mac)
+
+ # the rx'd NS should be addressed to an mcast address
+ # derived from the target address
+ self.assertEqual(in6_ptop(rx[IPv6].dst), in6_ptop(dst_ip))
+
+ # expect the tgt IP in the NS header
+ ns = rx[ICMPv6ND_NS]
+ self.assertEqual(in6_ptop(ns.tgt), in6_ptop(tgt_ip))
+
+ # packet is from the router's local address
+ self.assertEqual(in6_ptop(rx[IPv6].src), intf.local_ip6)
+
+ # Src link-layer options should have the router's MAC
+ sll = rx[ICMPv6NDOptSrcLLAddr]
+ self.assertEqual(sll.lladdr, intf.local_mac)
+
+ def send_and_expect_ra(self, intf, pkts, remark, dst_ip=None,
+ filter_out_fn=is_ipv6_misc):
+ intf.add_stream(pkts)
+ self.pg_enable_capture(self.pg_interfaces)
+ self.pg_start()
+ rx = intf.get_capture(1, filter_out_fn=filter_out_fn)
+
+ self.assertEqual(len(rx), 1)
+ rx = rx[0]
+ self.validate_ra(intf, rx, dst_ip)
+
+ def send_and_expect_na(self, intf, pkts, remark, dst_ip=None,
+ tgt_ip=None,
+ filter_out_fn=is_ipv6_misc):
+ intf.add_stream(pkts)
+ self.pg_enable_capture(self.pg_interfaces)
+ self.pg_start()
+ rx = intf.get_capture(1, filter_out_fn=filter_out_fn)
+
+ self.assertEqual(len(rx), 1)
+ rx = rx[0]
+ self.validate_na(intf, rx, dst_ip, tgt_ip)
+
+ def send_and_expect_ns(self, tx_intf, rx_intf, pkts, tgt_ip,
+ filter_out_fn=is_ipv6_misc):
+ tx_intf.add_stream(pkts)
+ self.pg_enable_capture(self.pg_interfaces)
+ self.pg_start()
+ rx = rx_intf.get_capture(1, filter_out_fn=filter_out_fn)
+
+ self.assertEqual(len(rx), 1)
+ rx = rx[0]
+ self.validate_ns(rx_intf, rx, tgt_ip)
+
+ def send_and_assert_no_replies(self, intf, pkts, remark):
+ intf.add_stream(pkts)
+ self.pg_enable_capture(self.pg_interfaces)
+ self.pg_start()
+ for i in self.pg_interfaces:
+ i.get_capture(0)
+ i.assert_nothing_captured(remark=remark)
+
+ def verify_ip(self, rx, smac, dmac, sip, dip):
+ ether = rx[Ether]
+ self.assertEqual(ether.dst, dmac)
+ self.assertEqual(ether.src, smac)
+
+ ip = rx[IPv6]
+ self.assertEqual(ip.src, sip)
+ self.assertEqual(ip.dst, dip)
+
+
+class TestIPv6(TestIPv6ND):
+ """ IPv6 Test Case """
+
+ @classmethod
+ def setUpClass(cls):
+ super(TestIPv6, cls).setUpClass()
+
+ def setUp(self):
+ """
+ Perform test setup before test case.
+
+ **Config:**
+ - create 3 pg interfaces
+ - untagged pg0 interface
+ - Dot1Q subinterface on pg1
+ - Dot1AD subinterface on pg2
+ - setup interfaces:
+ - put it into UP state
+ - set IPv6 addresses
+ - resolve neighbor address using NDP
+ - configure 200 fib entries
+
+ :ivar list interfaces: pg interfaces and subinterfaces.
+ :ivar dict flows: IPv4 packet flows in test.
+ :ivar list pg_if_packet_sizes: packet sizes in test.
+
+ *TODO:* Create AD sub interface
+ """
+ super(TestIPv6, self).setUp()
+
+ # create 3 pg interfaces
+ self.create_pg_interfaces(range(3))
+
+ # create 2 subinterfaces for p1 and pg2
+ self.sub_interfaces = [
+ VppDot1QSubint(self, self.pg1, 100),
+ VppDot1QSubint(self, self.pg2, 200)
+ # TODO: VppDot1ADSubint(self, self.pg2, 200, 300, 400)
+ ]
+
+ # packet flows mapping pg0 -> pg1.sub, pg2.sub, etc.
+ self.flows = dict()
+ self.flows[self.pg0] = [self.pg1.sub_if, self.pg2.sub_if]
+ self.flows[self.pg1.sub_if] = [self.pg0, self.pg2.sub_if]
+ self.flows[self.pg2.sub_if] = [self.pg0, self.pg1.sub_if]
+
+ # packet sizes
+ self.pg_if_packet_sizes = [64, 512, 1518, 9018]
+ self.sub_if_packet_sizes = [64, 512, 1518 + 4, 9018 + 4]
+
+ self.interfaces = list(self.pg_interfaces)
+ self.interfaces.extend(self.sub_interfaces)
+
+ # setup all interfaces
+ for i in self.interfaces:
+ i.admin_up()
+ i.config_ip6()
+ i.resolve_ndp()
+
+ # config 2M FIB entries
+ self.config_fib_entries(200)
+
+ def tearDown(self):
+ """Run standard test teardown and log ``show ip6 neighbors``."""
+ for i in self.sub_interfaces:
+ i.unconfig_ip6()
+ i.ip6_disable()
+ i.admin_down()
+ i.remove_vpp_config()
+
+ super(TestIPv6, self).tearDown()
+ if not self.vpp_dead:
+ self.logger.info(self.vapi.cli("show ip6 neighbors"))
+ # info(self.vapi.cli("show ip6 fib")) # many entries
+
+ def config_fib_entries(self, count):
+ """For each interface add to the FIB table *count* routes to
+ "fd02::1/128" destination with interface's local address as next-hop
+ address.
+
+ :param int count: Number of FIB entries.
+
+ - *TODO:* check if the next-hop address shouldn't be remote address
+ instead of local address.
+ """
+ n_int = len(self.interfaces)
+ percent = 0
+ counter = 0.0
+ dest_addr = inet_pton(AF_INET6, "fd02::1")
+ dest_addr_len = 128
+ for i in self.interfaces:
+ next_hop_address = i.local_ip6n
+ for j in range(count / n_int):
+ self.vapi.ip_add_del_route(
+ dest_addr, dest_addr_len, next_hop_address, is_ipv6=1)
+ counter += 1
+ if counter / count * 100 > percent:
+ self.logger.info("Configure %d FIB entries .. %d%% done" %
+ (count, percent))
+ percent += 1
+
+ def create_stream(self, src_if, packet_sizes):
+ """Create input packet stream for defined interface.
+
+ :param VppInterface src_if: Interface to create packet stream for.
+ :param list packet_sizes: Required packet sizes.
+ """
+ pkts = []
+ for i in range(0, 257):
+ dst_if = self.flows[src_if][i % 2]
+ info = self.create_packet_info(src_if, dst_if)
+ payload = self.info_to_payload(info)
+ p = (Ether(dst=src_if.local_mac, src=src_if.remote_mac) /
+ IPv6(src=src_if.remote_ip6, dst=dst_if.remote_ip6) /
+ UDP(sport=1234, dport=1234) /
+ Raw(payload))
+ info.data = p.copy()
+ if isinstance(src_if, VppSubInterface):
+ p = src_if.add_dot1_layer(p)
+ size = packet_sizes[(i // 2) % len(packet_sizes)]
+ self.extend_packet(p, size)
+ pkts.append(p)
+ return pkts
+
+ def verify_capture(self, dst_if, capture):
+ """Verify captured input packet stream for defined interface.
+
+ :param VppInterface dst_if: Interface to verify captured packet stream
+ for.
+ :param list capture: Captured packet stream.
+ """
+ self.logger.info("Verifying capture on interface %s" % dst_if.name)
+ last_info = dict()
+ for i in self.interfaces:
+ last_info[i.sw_if_index] = None
+ is_sub_if = False
+ dst_sw_if_index = dst_if.sw_if_index
+ if hasattr(dst_if, 'parent'):
+ is_sub_if = True
+ for packet in capture:
+ if is_sub_if:
+ # Check VLAN tags and Ethernet header
+ packet = dst_if.remove_dot1_layer(packet)
+ self.assertTrue(Dot1Q not in packet)
+ try:
+ ip = packet[IPv6]
+ udp = packet[UDP]
+ payload_info = self.payload_to_info(str(packet[Raw]))
+ packet_index = payload_info.index
+ self.assertEqual(payload_info.dst, dst_sw_if_index)
+ self.logger.debug(
+ "Got packet on port %s: src=%u (id=%u)" %
+ (dst_if.name, payload_info.src, packet_index))
+ next_info = self.get_next_packet_info_for_interface2(
+ payload_info.src, dst_sw_if_index,
+ last_info[payload_info.src])
+ last_info[payload_info.src] = next_info
+ self.assertTrue(next_info is not None)
+ self.assertEqual(packet_index, next_info.index)
+ saved_packet = next_info.data
+ # Check standard fields
+ self.assertEqual(ip.src, saved_packet[IPv6].src)
+ self.assertEqual(ip.dst, saved_packet[IPv6].dst)
+ self.assertEqual(udp.sport, saved_packet[UDP].sport)
+ self.assertEqual(udp.dport, saved_packet[UDP].dport)
+ except:
+ self.logger.error(ppp("Unexpected or invalid packet:", packet))
+ raise
+ for i in self.interfaces:
+ remaining_packet = self.get_next_packet_info_for_interface2(
+ i.sw_if_index, dst_sw_if_index, last_info[i.sw_if_index])
+ self.assertTrue(remaining_packet is None,
+ "Interface %s: Packet expected from interface %s "
+ "didn't arrive" % (dst_if.name, i.name))
+
+ def test_fib(self):
+ """ IPv6 FIB test
+
+ Test scenario:
+ - Create IPv6 stream for pg0 interface
+ - Create IPv6 tagged streams for pg1's and pg2's subinterface.
+ - Send and verify received packets on each interface.
+ """
+
+ pkts = self.create_stream(self.pg0, self.pg_if_packet_sizes)
+ self.pg0.add_stream(pkts)
+
+ for i in self.sub_interfaces:
+ pkts = self.create_stream(i, self.sub_if_packet_sizes)
+ i.parent.add_stream(pkts)
+
+ self.pg_enable_capture(self.pg_interfaces)
+ self.pg_start()
+
+ pkts = self.pg0.get_capture()
+ self.verify_capture(self.pg0, pkts)
+
+ for i in self.sub_interfaces:
+ pkts = i.parent.get_capture()
+ self.verify_capture(i, pkts)
+
+ def test_ns(self):
+ """ IPv6 Neighbour Solicitation Exceptions
+
+ Test scenario:
+ - Send an NS Sourced from an address not covered by the link sub-net
+ - Send an NS to an mcast address the router has not joined
+ - Send NS for a target address the router does not onn.
+ """
+
+ #
+ # An NS from a non link source address
+ #
+ nsma = in6_getnsma(inet_pton(AF_INET6, self.pg0.local_ip6))
+ d = inet_ntop(AF_INET6, nsma)
+
+ p = (Ether(dst=in6_getnsmac(nsma)) /
+ IPv6(dst=d, src="2002::2") /
+ ICMPv6ND_NS(tgt=self.pg0.local_ip6) /
+ ICMPv6NDOptSrcLLAddr(lladdr=self.pg0.remote_mac))
+ pkts = [p]
+
+ self.send_and_assert_no_replies(
+ self.pg0, pkts,
+ "No response to NS source by address not on sub-net")
+
+ #
+ # An NS for sent to a solicited mcast group the router is
+ # not a member of FAILS
+ #
+ if 0:
+ nsma = in6_getnsma(inet_pton(AF_INET6, "fd::ffff"))
+ d = inet_ntop(AF_INET6, nsma)
+
+ p = (Ether(dst=in6_getnsmac(nsma)) /
+ IPv6(dst=d, src=self.pg0.remote_ip6) /
+ ICMPv6ND_NS(tgt=self.pg0.local_ip6) /
+ ICMPv6NDOptSrcLLAddr(lladdr=self.pg0.remote_mac))
+ pkts = [p]
+
+ self.send_and_assert_no_replies(
+ self.pg0, pkts,
+ "No response to NS sent to unjoined mcast address")
+
+ #
+ # An NS whose target address is one the router does not own
+ #
+ nsma = in6_getnsma(inet_pton(AF_INET6, self.pg0.local_ip6))
+ d = inet_ntop(AF_INET6, nsma)
+
+ p = (Ether(dst=in6_getnsmac(nsma)) /
+ IPv6(dst=d, src=self.pg0.remote_ip6) /
+ ICMPv6ND_NS(tgt="fd::ffff") /
+ ICMPv6NDOptSrcLLAddr(lladdr=self.pg0.remote_mac))
+ pkts = [p]
+
+ self.send_and_assert_no_replies(self.pg0, pkts,
+ "No response to NS for unknown target")
+
+ #
+ # A neighbor entry that has no associated FIB-entry
+ #
+ self.pg0.generate_remote_hosts(4)
+ nd_entry = VppNeighbor(self,
+ self.pg0.sw_if_index,
+ self.pg0.remote_hosts[2].mac,
+ self.pg0.remote_hosts[2].ip6,
+ af=AF_INET6,
+ is_no_fib_entry=1)
+ nd_entry.add_vpp_config()
+
+ #
+ # check we have the neighbor, but no route
+ #
+ self.assertTrue(find_nbr(self,
+ self.pg0.sw_if_index,
+ self.pg0._remote_hosts[2].ip6,
+ inet=AF_INET6))
+ self.assertFalse(find_route(self,
+ self.pg0._remote_hosts[2].ip6,
+ 128,
+ inet=AF_INET6))
+
+ #
+ # send an NS from a link local address to the interface's global
+ # address
+ #
+ p = (Ether(dst=in6_getnsmac(nsma), src=self.pg0.remote_mac) /
+ IPv6(dst=d, src=self.pg0._remote_hosts[2].ip6_ll) /
+ ICMPv6ND_NS(tgt=self.pg0.local_ip6) /
+ ICMPv6NDOptSrcLLAddr(lladdr=self.pg0.remote_mac))
+
+ self.send_and_expect_na(self.pg0, p,
+ "NS from link-local",
+ dst_ip=self.pg0._remote_hosts[2].ip6_ll,
+ tgt_ip=self.pg0.local_ip6)
+
+ #
+ # we should have learned an ND entry for the peer's link-local
+ # but not inserted a route to it in the FIB
+ #
+ self.assertTrue(find_nbr(self,
+ self.pg0.sw_if_index,
+ self.pg0._remote_hosts[2].ip6_ll,
+ inet=AF_INET6))
+ self.assertFalse(find_route(self,
+ self.pg0._remote_hosts[2].ip6_ll,
+ 128,
+ inet=AF_INET6))
+
+ #
+ # An NS to the router's own Link-local
+ #
+ p = (Ether(dst=in6_getnsmac(nsma), src=self.pg0.remote_mac) /
+ IPv6(dst=d, src=self.pg0._remote_hosts[3].ip6_ll) /
+ ICMPv6ND_NS(tgt=self.pg0.local_ip6_ll) /
+ ICMPv6NDOptSrcLLAddr(lladdr=self.pg0.remote_mac))
+
+ self.send_and_expect_na(self.pg0, p,
+ "NS to/from link-local",
+ dst_ip=self.pg0._remote_hosts[3].ip6_ll,
+ tgt_ip=self.pg0.local_ip6_ll)
+
+ #
+ # we should have learned an ND entry for the peer's link-local
+ # but not inserted a route to it in the FIB
+ #
+ self.assertTrue(find_nbr(self,
+ self.pg0.sw_if_index,
+ self.pg0._remote_hosts[3].ip6_ll,
+ inet=AF_INET6))
+ self.assertFalse(find_route(self,
+ self.pg0._remote_hosts[3].ip6_ll,
+ 128,
+ inet=AF_INET6))
+
+ def test_ns_duplicates(self):
+ """ ND Duplicates"""
+
+ #
+ # Generate some hosts on the LAN
+ #
+ self.pg1.generate_remote_hosts(3)
+
+ #
+ # Add host 1 on pg1 and pg2
+ #
+ ns_pg1 = VppNeighbor(self,
+ self.pg1.sw_if_index,
+ self.pg1.remote_hosts[1].mac,
+ self.pg1.remote_hosts[1].ip6,
+ af=AF_INET6)
+ ns_pg1.add_vpp_config()
+ ns_pg2 = VppNeighbor(self,
+ self.pg2.sw_if_index,
+ self.pg2.remote_mac,
+ self.pg1.remote_hosts[1].ip6,
+ af=AF_INET6)
+ ns_pg2.add_vpp_config()
+
+ #
+ # IP packet destined for pg1 remote host arrives on pg1 again.
+ #
+ p = (Ether(dst=self.pg0.local_mac,
+ src=self.pg0.remote_mac) /
+ IPv6(src=self.pg0.remote_ip6,
+ dst=self.pg1.remote_hosts[1].ip6) /
+ UDP(sport=1234, dport=1234) /
+ Raw())
+
+ self.pg0.add_stream(p)
+ self.pg_enable_capture(self.pg_interfaces)
+ self.pg_start()
+
+ rx1 = self.pg1.get_capture(1)
+
+ self.verify_ip(rx1[0],
+ self.pg1.local_mac,
+ self.pg1.remote_hosts[1].mac,
+ self.pg0.remote_ip6,
+ self.pg1.remote_hosts[1].ip6)
+
+ #
+ # remove the duplicate on pg1
+ # packet stream shoud generate NSs out of pg1
+ #
+ ns_pg1.remove_vpp_config()
+
+ self.send_and_expect_ns(self.pg0, self.pg1,
+ p, self.pg1.remote_hosts[1].ip6)
+
+ #
+ # Add it back
+ #
+ ns_pg1.add_vpp_config()
+
+ self.pg0.add_stream(p)
+ self.pg_enable_capture(self.pg_interfaces)
+ self.pg_start()
+
+ rx1 = self.pg1.get_capture(1)
+
+ self.verify_ip(rx1[0],
+ self.pg1.local_mac,
+ self.pg1.remote_hosts[1].mac,
+ self.pg0.remote_ip6,
+ self.pg1.remote_hosts[1].ip6)
+
+ def validate_ra(self, intf, rx, dst_ip=None, mtu=9000, pi_opt=None):
+ if not dst_ip:
+ dst_ip = intf.remote_ip6
+
+ # unicasted packets must come to the unicast mac
+ self.assertEqual(rx[Ether].dst, intf.remote_mac)
+
+ # and from the router's MAC
+ self.assertEqual(rx[Ether].src, intf.local_mac)
+
+ # the rx'd RA should be addressed to the sender's source
+ self.assertTrue(rx.haslayer(ICMPv6ND_RA))
+ self.assertEqual(in6_ptop(rx[IPv6].dst),
+ in6_ptop(dst_ip))
+
+ # and come from the router's link local
+ self.assertTrue(in6_islladdr(rx[IPv6].src))
+ self.assertEqual(in6_ptop(rx[IPv6].src),
+ in6_ptop(mk_ll_addr(intf.local_mac)))
+
+ # it should contain the links MTU
+ ra = rx[ICMPv6ND_RA]
+ self.assertEqual(ra[ICMPv6NDOptMTU].mtu, mtu)
+
+ # it should contain the source's link layer address option
+ sll = ra[ICMPv6NDOptSrcLLAddr]
+ self.assertEqual(sll.lladdr, intf.local_mac)
+
+ if not pi_opt:
+ # the RA should not contain prefix information
+ self.assertFalse(ra.haslayer(ICMPv6NDOptPrefixInfo))
+ else:
+ raos = rx.getlayer(ICMPv6NDOptPrefixInfo, 1)
+
+ # the options are nested in the scapy packet in way that i cannot
+ # decipher how to decode. this 1st layer of option always returns
+ # nested classes, so a direct obj1=obj2 comparison always fails.
+ # however, the getlayer(.., 2) does give one instnace.
+ # so we cheat here and construct a new opt instnace for comparison
+ rd = ICMPv6NDOptPrefixInfo(prefixlen=raos.prefixlen,
+ prefix=raos.prefix,
+ L=raos.L,
+ A=raos.A)
+ if type(pi_opt) is list:
+ for ii in range(len(pi_opt)):
+ self.assertEqual(pi_opt[ii], rd)
+ rd = rx.getlayer(ICMPv6NDOptPrefixInfo, ii+2)
+ else:
+ self.assertEqual(pi_opt, raos)
+
+ def send_and_expect_ra(self, intf, pkts, remark, dst_ip=None,
+ filter_out_fn=is_ipv6_misc,
+ opt=None):
+ intf.add_stream(pkts)
+ self.pg_enable_capture(self.pg_interfaces)
+ self.pg_start()
+ rx = intf.get_capture(1, filter_out_fn=filter_out_fn)
+
+ self.assertEqual(len(rx), 1)
+ rx = rx[0]
+ self.validate_ra(intf, rx, dst_ip, pi_opt=opt)
+
+ def test_rs(self):
+ """ IPv6 Router Solicitation Exceptions
+
+ Test scenario:
+ """
+
+ #
+ # Before we begin change the IPv6 RA responses to use the unicast
+ # address - that way we will not confuse them with the periodic
+ # RAs which go to the mcast address
+ # Sit and wait for the first periodic RA.
+ #
+ # TODO
+ #
+ self.pg0.ip6_ra_config(send_unicast=1)
+
+ #
+ # An RS from a link source address
+ # - expect an RA in return
+ #
+ p = (Ether(dst=self.pg0.local_mac, src=self.pg0.remote_mac) /
+ IPv6(dst=self.pg0.local_ip6, src=self.pg0.remote_ip6) /
+ ICMPv6ND_RS())
+ pkts = [p]
+ self.send_and_expect_ra(self.pg0, pkts, "Genuine RS")
+
+ #
+ # For the next RS sent the RA should be rate limited
+ #
+ self.send_and_assert_no_replies(self.pg0, pkts, "RA rate limited")
+
+ #
+ # When we reconfiure the IPv6 RA config, we reset the RA rate limiting,
+ # so we need to do this before each test below so as not to drop
+ # packets for rate limiting reasons. Test this works here.
+ #
+ self.pg0.ip6_ra_config(send_unicast=1)
+ self.send_and_expect_ra(self.pg0, pkts, "Rate limit reset RS")
+
+ #
+ # An RS sent from a non-link local source
+ #
+ self.pg0.ip6_ra_config(send_unicast=1)
+ p = (Ether(dst=self.pg0.local_mac, src=self.pg0.remote_mac) /
+ IPv6(dst=self.pg0.local_ip6, src="2002::ffff") /
+ ICMPv6ND_RS())
+ pkts = [p]
+ self.send_and_assert_no_replies(self.pg0, pkts,
+ "RS from non-link source")
+
+ #
+ # Source an RS from a link local address
+ #
+ self.pg0.ip6_ra_config(send_unicast=1)
+ ll = mk_ll_addr(self.pg0.remote_mac)
+ p = (Ether(dst=self.pg0.local_mac, src=self.pg0.remote_mac) /
+ IPv6(dst=self.pg0.local_ip6, src=ll) /
+ ICMPv6ND_RS())
+ pkts = [p]
+ self.send_and_expect_ra(self.pg0, pkts,
+ "RS sourced from link-local",
+ dst_ip=ll)
+
+ #
+ # Send the RS multicast
+ #
+ self.pg0.ip6_ra_config(send_unicast=1)
+ dmac = in6_getnsmac(inet_pton(AF_INET6, "ff02::2"))
+ ll = mk_ll_addr(self.pg0.remote_mac)
+ p = (Ether(dst=dmac, src=self.pg0.remote_mac) /
+ IPv6(dst="ff02::2", src=ll) /
+ ICMPv6ND_RS())
+ pkts = [p]
+ self.send_and_expect_ra(self.pg0, pkts,
+ "RS sourced from link-local",
+ dst_ip=ll)
+
+ #
+ # Source from the unspecified address ::. This happens when the RS
+ # is sent before the host has a configured address/sub-net,
+ # i.e. auto-config. Since the sender has no IP address, the reply
+ # comes back mcast - so the capture needs to not filter this.
+ # If we happen to pick up the periodic RA at this point then so be it,
+ # it's not an error.
+ #
+ self.pg0.ip6_ra_config(send_unicast=1, suppress=1)
+ p = (Ether(dst=dmac, src=self.pg0.remote_mac) /
+ IPv6(dst="ff02::2", src="::") /
+ ICMPv6ND_RS())
+ pkts = [p]
+ self.send_and_expect_ra(self.pg0, pkts,
+ "RS sourced from unspecified",
+ dst_ip="ff02::1",
+ filter_out_fn=None)
+
+ #
+ # Configure The RA to announce the links prefix
+ #
+ self.pg0.ip6_ra_prefix(self.pg0.local_ip6n,
+ self.pg0.local_ip6_prefix_len)
+
+ #
+ # RAs should now contain the prefix information option
+ #
+ opt = ICMPv6NDOptPrefixInfo(prefixlen=self.pg0.local_ip6_prefix_len,
+ prefix=self.pg0.local_ip6,
+ L=1,
+ A=1)
+
+ self.pg0.ip6_ra_config(send_unicast=1)
+ ll = mk_ll_addr(self.pg0.remote_mac)
+ p = (Ether(dst=self.pg0.local_mac, src=self.pg0.remote_mac) /
+ IPv6(dst=self.pg0.local_ip6, src=ll) /
+ ICMPv6ND_RS())
+ self.send_and_expect_ra(self.pg0, p,
+ "RA with prefix-info",
+ dst_ip=ll,
+ opt=opt)
+
+ #
+ # Change the prefix info to not off-link
+ # L-flag is clear
+ #
+ self.pg0.ip6_ra_prefix(self.pg0.local_ip6n,
+ self.pg0.local_ip6_prefix_len,
+ off_link=1)
+
+ opt = ICMPv6NDOptPrefixInfo(prefixlen=self.pg0.local_ip6_prefix_len,
+ prefix=self.pg0.local_ip6,
+ L=0,
+ A=1)
+
+ self.pg0.ip6_ra_config(send_unicast=1)
+ self.send_and_expect_ra(self.pg0, p,
+ "RA with Prefix info with L-flag=0",
+ dst_ip=ll,
+ opt=opt)
+
+ #
+ # Change the prefix info to not off-link, no-autoconfig
+ # L and A flag are clear in the advert
+ #
+ self.pg0.ip6_ra_prefix(self.pg0.local_ip6n,
+ self.pg0.local_ip6_prefix_len,
+ off_link=1,
+ no_autoconfig=1)
+
+ opt = ICMPv6NDOptPrefixInfo(prefixlen=self.pg0.local_ip6_prefix_len,
+ prefix=self.pg0.local_ip6,
+ L=0,
+ A=0)
+
+ self.pg0.ip6_ra_config(send_unicast=1)
+ self.send_and_expect_ra(self.pg0, p,
+ "RA with Prefix info with A & L-flag=0",
+ dst_ip=ll,
+ opt=opt)
+
+ #
+ # Change the flag settings back to the defaults
+ # L and A flag are set in the advert
+ #
+ self.pg0.ip6_ra_prefix(self.pg0.local_ip6n,
+ self.pg0.local_ip6_prefix_len)
+
+ opt = ICMPv6NDOptPrefixInfo(prefixlen=self.pg0.local_ip6_prefix_len,
+ prefix=self.pg0.local_ip6,
+ L=1,
+ A=1)
+
+ self.pg0.ip6_ra_config(send_unicast=1)
+ self.send_and_expect_ra(self.pg0, p,
+ "RA with Prefix info",
+ dst_ip=ll,
+ opt=opt)
+
+ #
+ # Change the prefix info to not off-link, no-autoconfig
+ # L and A flag are clear in the advert
+ #
+ self.pg0.ip6_ra_prefix(self.pg0.local_ip6n,
+ self.pg0.local_ip6_prefix_len,
+ off_link=1,
+ no_autoconfig=1)
+
+ opt = ICMPv6NDOptPrefixInfo(prefixlen=self.pg0.local_ip6_prefix_len,
+ prefix=self.pg0.local_ip6,
+ L=0,
+ A=0)
+
+ self.pg0.ip6_ra_config(send_unicast=1)
+ self.send_and_expect_ra(self.pg0, p,
+ "RA with Prefix info with A & L-flag=0",
+ dst_ip=ll,
+ opt=opt)
+
+ #
+ # Use the reset to defults option to revert to defaults
+ # L and A flag are clear in the advert
+ #
+ self.pg0.ip6_ra_prefix(self.pg0.local_ip6n,
+ self.pg0.local_ip6_prefix_len,
+ use_default=1)
+
+ opt = ICMPv6NDOptPrefixInfo(prefixlen=self.pg0.local_ip6_prefix_len,
+ prefix=self.pg0.local_ip6,
+ L=1,
+ A=1)
+
+ self.pg0.ip6_ra_config(send_unicast=1)
+ self.send_and_expect_ra(self.pg0, p,
+ "RA with Prefix reverted to defaults",
+ dst_ip=ll,
+ opt=opt)
+
+ #
+ # Advertise Another prefix. With no L-flag/A-flag
+ #
+ self.pg0.ip6_ra_prefix(self.pg1.local_ip6n,
+ self.pg1.local_ip6_prefix_len,
+ off_link=1,
+ no_autoconfig=1)
+
+ opt = [ICMPv6NDOptPrefixInfo(prefixlen=self.pg0.local_ip6_prefix_len,
+ prefix=self.pg0.local_ip6,
+ L=1,
+ A=1),
+ ICMPv6NDOptPrefixInfo(prefixlen=self.pg1.local_ip6_prefix_len,
+ prefix=self.pg1.local_ip6,
+ L=0,
+ A=0)]
+
+ self.pg0.ip6_ra_config(send_unicast=1)
+ ll = mk_ll_addr(self.pg0.remote_mac)
+ p = (Ether(dst=self.pg0.local_mac, src=self.pg0.remote_mac) /
+ IPv6(dst=self.pg0.local_ip6, src=ll) /
+ ICMPv6ND_RS())
+ self.send_and_expect_ra(self.pg0, p,
+ "RA with multiple Prefix infos",
+ dst_ip=ll,
+ opt=opt)
+
+ #
+ # Remove the first refix-info - expect the second is still in the
+ # advert
+ #
+ self.pg0.ip6_ra_prefix(self.pg0.local_ip6n,
+ self.pg0.local_ip6_prefix_len,
+ is_no=1)
+
+ opt = ICMPv6NDOptPrefixInfo(prefixlen=self.pg1.local_ip6_prefix_len,
+ prefix=self.pg1.local_ip6,
+ L=0,
+ A=0)
+
+ self.pg0.ip6_ra_config(send_unicast=1)
+ self.send_and_expect_ra(self.pg0, p,
+ "RA with Prefix reverted to defaults",
+ dst_ip=ll,
+ opt=opt)
+
+ #
+ # Remove the second prefix-info - expect no prefix-info i nthe adverts
+ #
+ self.pg0.ip6_ra_prefix(self.pg1.local_ip6n,
+ self.pg1.local_ip6_prefix_len,
+ is_no=1)
+
+ self.pg0.ip6_ra_config(send_unicast=1)
+ self.send_and_expect_ra(self.pg0, p,
+ "RA with Prefix reverted to defaults",
+ dst_ip=ll)
+
+ #
+ # Reset the periodic advertisements back to default values
+ #
+ self.pg0.ip6_ra_config(no=1, suppress=1, send_unicast=0)
+
+
+class IPv6NDProxyTest(TestIPv6ND):
+ """ IPv6 ND ProxyTest Case """
+
+ def setUp(self):
+ super(IPv6NDProxyTest, self).setUp()
+
+ # create 3 pg interfaces
+ self.create_pg_interfaces(range(3))
+
+ # pg0 is the master interface, with the configured subnet
+ self.pg0.admin_up()
+ self.pg0.config_ip6()
+ self.pg0.resolve_ndp()
+
+ self.pg1.ip6_enable()
+ self.pg2.ip6_enable()
+
+ def tearDown(self):
+ super(IPv6NDProxyTest, self).tearDown()
+
+ def test_nd_proxy(self):
+ """ IPv6 Proxy ND """
+
+ #
+ # Generate some hosts in the subnet that we are proxying
+ #
+ self.pg0.generate_remote_hosts(8)
+
+ nsma = in6_getnsma(inet_pton(AF_INET6, self.pg0.local_ip6))
+ d = inet_ntop(AF_INET6, nsma)
+
+ #
+ # Send an NS for one of those remote hosts on one of the proxy links
+ # expect no response since it's from an address that is not
+ # on the link that has the prefix configured
+ #
+ ns_pg1 = (Ether(dst=in6_getnsmac(nsma), src=self.pg1.remote_mac) /
+ IPv6(dst=d, src=self.pg0._remote_hosts[2].ip6) /
+ ICMPv6ND_NS(tgt=self.pg0.local_ip6) /
+ ICMPv6NDOptSrcLLAddr(lladdr=self.pg0._remote_hosts[2].mac))
+
+ self.send_and_assert_no_replies(self.pg1, ns_pg1, "Off link NS")
+
+ #
+ # Add proxy support for the host
+ #
+ self.vapi.ip6_nd_proxy(
+ inet_pton(AF_INET6, self.pg0._remote_hosts[2].ip6),
+ self.pg1.sw_if_index)
+
+ #
+ # try that NS again. this time we expect an NA back
+ #
+ self.send_and_expect_na(self.pg1, ns_pg1,
+ "NS to proxy entry",
+ dst_ip=self.pg0._remote_hosts[2].ip6,
+ tgt_ip=self.pg0.local_ip6)
+
+ #
+ # ... and that we have an entry in the ND cache
+ #
+ self.assertTrue(find_nbr(self,
+ self.pg1.sw_if_index,
+ self.pg0._remote_hosts[2].ip6,
+ inet=AF_INET6))
+
+ #
+ # ... and we can route traffic to it
+ #
+ t = (Ether(dst=self.pg0.local_mac, src=self.pg0.remote_mac) /
+ IPv6(dst=self.pg0._remote_hosts[2].ip6,
+ src=self.pg0.remote_ip6) /
+ UDP(sport=10000, dport=20000) /
+ Raw('\xa5' * 100))
+
+ self.pg0.add_stream(t)
+ self.pg_enable_capture(self.pg_interfaces)
+ self.pg_start()
+ rx = self.pg1.get_capture(1)
+ rx = rx[0]
+
+ self.assertEqual(rx[Ether].dst, self.pg0._remote_hosts[2].mac)
+ self.assertEqual(rx[Ether].src, self.pg1.local_mac)
+
+ self.assertEqual(rx[IPv6].src, t[IPv6].src)
+ self.assertEqual(rx[IPv6].dst, t[IPv6].dst)
+
+ #
+ # Test we proxy for the host on the main interface
+ #
+ ns_pg0 = (Ether(dst=in6_getnsmac(nsma), src=self.pg0.remote_mac) /
+ IPv6(dst=d, src=self.pg0.remote_ip6) /
+ ICMPv6ND_NS(tgt=self.pg0._remote_hosts[2].ip6) /
+ ICMPv6NDOptSrcLLAddr(lladdr=self.pg0.remote_mac))
+
+ self.send_and_expect_na(self.pg0, ns_pg0,
+ "NS to proxy entry on main",
+ tgt_ip=self.pg0._remote_hosts[2].ip6,
+ dst_ip=self.pg0.remote_ip6)
+
+ #
+ # Setup and resolve proxy for another host on another interface
+ #
+ ns_pg2 = (Ether(dst=in6_getnsmac(nsma), src=self.pg2.remote_mac) /
+ IPv6(dst=d, src=self.pg0._remote_hosts[3].ip6) /
+ ICMPv6ND_NS(tgt=self.pg0.local_ip6) /
+ ICMPv6NDOptSrcLLAddr(lladdr=self.pg0._remote_hosts[2].mac))
+
+ self.vapi.ip6_nd_proxy(
+ inet_pton(AF_INET6, self.pg0._remote_hosts[3].ip6),
+ self.pg2.sw_if_index)
+
+ self.send_and_expect_na(self.pg2, ns_pg2,
+ "NS to proxy entry other interface",
+ dst_ip=self.pg0._remote_hosts[3].ip6,
+ tgt_ip=self.pg0.local_ip6)
+
+ self.assertTrue(find_nbr(self,
+ self.pg2.sw_if_index,
+ self.pg0._remote_hosts[3].ip6,
+ inet=AF_INET6))
+
+ #
+ # hosts can communicate. pg2->pg1
+ #
+ t2 = (Ether(dst=self.pg2.local_mac,
+ src=self.pg0.remote_hosts[3].mac) /
+ IPv6(dst=self.pg0._remote_hosts[2].ip6,
+ src=self.pg0._remote_hosts[3].ip6) /
+ UDP(sport=10000, dport=20000) /
+ Raw('\xa5' * 100))
+
+ self.pg2.add_stream(t2)
+ self.pg_enable_capture(self.pg_interfaces)
+ self.pg_start()
+ rx = self.pg1.get_capture(1)
+ rx = rx[0]
+
+ self.assertEqual(rx[Ether].dst, self.pg0._remote_hosts[2].mac)
+ self.assertEqual(rx[Ether].src, self.pg1.local_mac)
+
+ self.assertEqual(rx[IPv6].src, t2[IPv6].src)
+ self.assertEqual(rx[IPv6].dst, t2[IPv6].dst)
+
+ #
+ # remove the proxy configs
+ #
+ self.vapi.ip6_nd_proxy(
+ inet_pton(AF_INET6, self.pg0._remote_hosts[2].ip6),
+ self.pg1.sw_if_index,
+ is_del=1)
+ self.vapi.ip6_nd_proxy(
+ inet_pton(AF_INET6, self.pg0._remote_hosts[3].ip6),
+ self.pg2.sw_if_index,
+ is_del=1)
+
+ self.assertFalse(find_nbr(self,
+ self.pg2.sw_if_index,
+ self.pg0._remote_hosts[3].ip6,
+ inet=AF_INET6))
+ self.assertFalse(find_nbr(self,
+ self.pg1.sw_if_index,
+ self.pg0._remote_hosts[2].ip6,
+ inet=AF_INET6))
+
+ #
+ # no longer proxy-ing...
+ #
+ self.send_and_assert_no_replies(self.pg0, ns_pg0, "Proxy unconfigured")
+ self.send_and_assert_no_replies(self.pg1, ns_pg1, "Proxy unconfigured")
+ self.send_and_assert_no_replies(self.pg2, ns_pg2, "Proxy unconfigured")
+
+ #
+ # no longer forwarding. traffic generates NS out of the glean/main
+ # interface
+ #
+ self.pg2.add_stream(t2)
+ self.pg_enable_capture(self.pg_interfaces)
+ self.pg_start()
+
+ rx = self.pg0.get_capture(1)
+
+ self.assertTrue(rx[0].haslayer(ICMPv6ND_NS))
+
+
+class TestIPNull(VppTestCase):
+ """ IPv6 routes via NULL """
+
+ def setUp(self):
+ super(TestIPNull, self).setUp()
+
+ # create 2 pg interfaces
+ self.create_pg_interfaces(range(1))
+
+ for i in self.pg_interfaces:
+ i.admin_up()
+ i.config_ip6()
+ i.resolve_ndp()
+
+ def tearDown(self):
+ super(TestIPNull, self).tearDown()
+ for i in self.pg_interfaces:
+ i.unconfig_ip6()
+ i.admin_down()
+
+ def test_ip_null(self):
+ """ IP NULL route """
+
+ p = (Ether(src=self.pg0.remote_mac,
+ dst=self.pg0.local_mac) /
+ IPv6(src=self.pg0.remote_ip6, dst="2001::1") /
+ UDP(sport=1234, dport=1234) /
+ Raw('\xa5' * 100))
+
+ #
+ # A route via IP NULL that will reply with ICMP unreachables
+ #
+ ip_unreach = VppIpRoute(self, "2001::", 64, [], is_unreach=1, is_ip6=1)
+ ip_unreach.add_vpp_config()
+
+ self.pg0.add_stream(p)
+ self.pg_enable_capture(self.pg_interfaces)
+ self.pg_start()
+
+ rx = self.pg0.get_capture(1)
+ rx = rx[0]
+ icmp = rx[ICMPv6DestUnreach]
+
+ # 0 = "No route to destination"
+ self.assertEqual(icmp.code, 0)
+
+ # ICMP is rate limited. pause a bit
+ self.sleep(1)
+
+ #
+ # A route via IP NULL that will reply with ICMP prohibited
+ #
+ ip_prohibit = VppIpRoute(self, "2001::1", 128, [],
+ is_prohibit=1, is_ip6=1)
+ ip_prohibit.add_vpp_config()
+
+ self.pg0.add_stream(p)
+ self.pg_enable_capture(self.pg_interfaces)
+ self.pg_start()
+
+ rx = self.pg0.get_capture(1)
+ rx = rx[0]
+ icmp = rx[ICMPv6DestUnreach]
+
+ # 1 = "Communication with destination administratively prohibited"
+ self.assertEqual(icmp.code, 1)
+
+
+class TestIPDisabled(VppTestCase):
+ """ IPv6 disabled """
+
+ def setUp(self):
+ super(TestIPDisabled, self).setUp()
+
+ # create 2 pg interfaces
+ self.create_pg_interfaces(range(2))
+
+ # PG0 is IP enalbed
+ self.pg0.admin_up()
+ self.pg0.config_ip6()
+ self.pg0.resolve_ndp()
+
+ # PG 1 is not IP enabled
+ self.pg1.admin_up()
+
+ def tearDown(self):
+ super(TestIPDisabled, self).tearDown()
+ for i in self.pg_interfaces:
+ i.unconfig_ip4()
+ i.admin_down()
+
+ def send_and_assert_no_replies(self, intf, pkts, remark):
+ intf.add_stream(pkts)
+ self.pg_enable_capture(self.pg_interfaces)
+ self.pg_start()
+ for i in self.pg_interfaces:
+ i.get_capture(0)
+ i.assert_nothing_captured(remark=remark)
+
+ def test_ip_disabled(self):
+ """ IP Disabled """
+
+ #
+ # An (S,G).
+ # one accepting interface, pg0, 2 forwarding interfaces
+ #
+ route_ff_01 = VppIpMRoute(
+ self,
+ "::",
+ "ffef::1", 128,
+ MRouteEntryFlags.MFIB_ENTRY_FLAG_NONE,
+ [VppMRoutePath(self.pg1.sw_if_index,
+ MRouteItfFlags.MFIB_ITF_FLAG_ACCEPT),
+ VppMRoutePath(self.pg0.sw_if_index,
+ MRouteItfFlags.MFIB_ITF_FLAG_FORWARD)],
+ is_ip6=1)
+ route_ff_01.add_vpp_config()
+
+ pu = (Ether(src=self.pg1.remote_mac,
+ dst=self.pg1.local_mac) /
+ IPv6(src="2001::1", dst=self.pg0.remote_ip6) /
+ UDP(sport=1234, dport=1234) /
+ Raw('\xa5' * 100))
+ pm = (Ether(src=self.pg1.remote_mac,
+ dst=self.pg1.local_mac) /
+ IPv6(src="2001::1", dst="ffef::1") /
+ UDP(sport=1234, dport=1234) /
+ Raw('\xa5' * 100))
+
+ #
+ # PG1 does not forward IP traffic
+ #
+ self.send_and_assert_no_replies(self.pg1, pu, "IPv6 disabled")
+ self.send_and_assert_no_replies(self.pg1, pm, "IPv6 disabled")
+
+ #
+ # IP enable PG1
+ #
+ self.pg1.config_ip6()
+
+ #
+ # Now we get packets through
+ #
+ self.pg1.add_stream(pu)
+ self.pg_enable_capture(self.pg_interfaces)
+ self.pg_start()
+ rx = self.pg0.get_capture(1)
+
+ self.pg1.add_stream(pm)
+ self.pg_enable_capture(self.pg_interfaces)
+ self.pg_start()
+ rx = self.pg0.get_capture(1)
+
+ #
+ # Disable PG1
+ #
+ self.pg1.unconfig_ip6()
+
+ #
+ # PG1 does not forward IP traffic
+ #
+ self.send_and_assert_no_replies(self.pg1, pu, "IPv6 disabled")
+ self.send_and_assert_no_replies(self.pg1, pm, "IPv6 disabled")
+
+
+class TestIP6LoadBalance(VppTestCase):
+ """ IPv6 Load-Balancing """
+
+ def setUp(self):
+ super(TestIP6LoadBalance, self).setUp()
+
+ self.create_pg_interfaces(range(5))
+
+ mpls_tbl = VppMplsTable(self, 0)
+ mpls_tbl.add_vpp_config()
+
+ for i in self.pg_interfaces:
+ i.admin_up()
+ i.config_ip6()
+ i.resolve_ndp()
+ i.enable_mpls()
+
+ def tearDown(self):
+ for i in self.pg_interfaces:
+ i.unconfig_ip6()
+ i.admin_down()
+ i.disable_mpls()
+ super(TestIP6LoadBalance, self).tearDown()
+
+ def send_and_expect_load_balancing(self, input, pkts, outputs):
+ input.add_stream(pkts)
+ self.pg_enable_capture(self.pg_interfaces)
+ self.pg_start()
+ for oo in outputs:
+ rx = oo._get_capture(1)
+ self.assertNotEqual(0, len(rx))
+
+ def send_and_expect_one_itf(self, input, pkts, itf):
+ input.add_stream(pkts)
+ self.pg_enable_capture(self.pg_interfaces)
+ self.pg_start()
+ rx = itf.get_capture(len(pkts))
+
+ def test_ip6_load_balance(self):
+ """ IPv6 Load-Balancing """
+
+ #
+ # An array of packets that differ only in the destination port
+ # - IP only
+ # - MPLS EOS
+ # - MPLS non-EOS
+ # - MPLS non-EOS with an entropy label
+ #
+ port_ip_pkts = []
+ port_mpls_pkts = []
+ port_mpls_neos_pkts = []
+ port_ent_pkts = []
+
+ #
+ # An array of packets that differ only in the source address
+ #
+ src_ip_pkts = []
+ src_mpls_pkts = []
+
+ for ii in range(65):
+ port_ip_hdr = (IPv6(dst="3000::1", src="3000:1::1") /
+ UDP(sport=1234, dport=1234 + ii) /
+ Raw('\xa5' * 100))
+ port_ip_pkts.append((Ether(src=self.pg0.remote_mac,
+ dst=self.pg0.local_mac) /
+ port_ip_hdr))
+ port_mpls_pkts.append((Ether(src=self.pg0.remote_mac,
+ dst=self.pg0.local_mac) /
+ MPLS(label=66, ttl=2) /
+ port_ip_hdr))
+ port_mpls_neos_pkts.append((Ether(src=self.pg0.remote_mac,
+ dst=self.pg0.local_mac) /
+ MPLS(label=67, ttl=2) /
+ MPLS(label=77, ttl=2) /
+ port_ip_hdr))
+ port_ent_pkts.append((Ether(src=self.pg0.remote_mac,
+ dst=self.pg0.local_mac) /
+ MPLS(label=67, ttl=2) /
+ MPLS(label=14, ttl=2) /
+ MPLS(label=999, ttl=2) /
+ port_ip_hdr))
+ src_ip_hdr = (IPv6(dst="3000::1", src="3000:1::%d" % ii) /
+ UDP(sport=1234, dport=1234) /
+ Raw('\xa5' * 100))
+ src_ip_pkts.append((Ether(src=self.pg0.remote_mac,
+ dst=self.pg0.local_mac) /
+ src_ip_hdr))
+ src_mpls_pkts.append((Ether(src=self.pg0.remote_mac,
+ dst=self.pg0.local_mac) /
+ MPLS(label=66, ttl=2) /
+ src_ip_hdr))
+
+ #
+ # A route for the IP pacekts
+ #
+ route_3000_1 = VppIpRoute(self, "3000::1", 128,
+ [VppRoutePath(self.pg1.remote_ip6,
+ self.pg1.sw_if_index,
+ proto=DpoProto.DPO_PROTO_IP6),
+ VppRoutePath(self.pg2.remote_ip6,
+ self.pg2.sw_if_index,
+ proto=DpoProto.DPO_PROTO_IP6)],
+ is_ip6=1)
+ route_3000_1.add_vpp_config()
+
+ #
+ # a local-label for the EOS packets
+ #
+ binding = VppMplsIpBind(self, 66, "3000::1", 128, is_ip6=1)
+ binding.add_vpp_config()
+
+ #
+ # An MPLS route for the non-EOS packets
+ #
+ route_67 = VppMplsRoute(self, 67, 0,
+ [VppRoutePath(self.pg1.remote_ip6,
+ self.pg1.sw_if_index,
+ labels=[67],
+ proto=DpoProto.DPO_PROTO_IP6),
+ VppRoutePath(self.pg2.remote_ip6,
+ self.pg2.sw_if_index,
+ labels=[67],
+ proto=DpoProto.DPO_PROTO_IP6)])
+ route_67.add_vpp_config()
+
+ #
+ # inject the packet on pg0 - expect load-balancing across the 2 paths
+ # - since the default hash config is to use IP src,dst and port
+ # src,dst
+ # We are not going to ensure equal amounts of packets across each link,
+ # since the hash algorithm is statistical and therefore this can never
+ # be guaranteed. But wuth 64 different packets we do expect some
+ # balancing. So instead just ensure there is traffic on each link.
+ #
+ self.send_and_expect_load_balancing(self.pg0, port_ip_pkts,
+ [self.pg1, self.pg2])
+ self.send_and_expect_load_balancing(self.pg0, src_ip_pkts,
+ [self.pg1, self.pg2])
+ self.send_and_expect_load_balancing(self.pg0, port_mpls_pkts,
+ [self.pg1, self.pg2])
+ self.send_and_expect_load_balancing(self.pg0, src_mpls_pkts,
+ [self.pg1, self.pg2])
+ self.send_and_expect_load_balancing(self.pg0, port_mpls_neos_pkts,
+ [self.pg1, self.pg2])
+
+ #
+ # The packets with Entropy label in should not load-balance,
+ # since the Entorpy value is fixed.
+ #
+ self.send_and_expect_one_itf(self.pg0, port_ent_pkts, self.pg1)
+
+ #
+ # change the flow hash config so it's only IP src,dst
+ # - now only the stream with differing source address will
+ # load-balance
+ #
+ self.vapi.set_ip_flow_hash(0, is_ip6=1, src=1, dst=1, sport=0, dport=0)
+
+ self.send_and_expect_load_balancing(self.pg0, src_ip_pkts,
+ [self.pg1, self.pg2])
+ self.send_and_expect_load_balancing(self.pg0, src_mpls_pkts,
+ [self.pg1, self.pg2])
+ self.send_and_expect_one_itf(self.pg0, port_ip_pkts, self.pg2)
+
+ #
+ # change the flow hash config back to defaults
+ #
+ self.vapi.set_ip_flow_hash(0, is_ip6=1, src=1, dst=1, sport=1, dport=1)
+
+ #
+ # Recursive prefixes
+ # - testing that 2 stages of load-balancing occurs and there is no
+ # polarisation (i.e. only 2 of 4 paths are used)
+ #
+ port_pkts = []
+ src_pkts = []
+
+ for ii in range(257):
+ port_pkts.append((Ether(src=self.pg0.remote_mac,
+ dst=self.pg0.local_mac) /
+ IPv6(dst="4000::1", src="4000:1::1") /
+ UDP(sport=1234, dport=1234 + ii) /
+ Raw('\xa5' * 100)))
+ src_pkts.append((Ether(src=self.pg0.remote_mac,
+ dst=self.pg0.local_mac) /
+ IPv6(dst="4000::1", src="4000:1::%d" % ii) /
+ UDP(sport=1234, dport=1234) /
+ Raw('\xa5' * 100)))
+
+ route_3000_2 = VppIpRoute(self, "3000::2", 128,
+ [VppRoutePath(self.pg3.remote_ip6,
+ self.pg3.sw_if_index,
+ proto=DpoProto.DPO_PROTO_IP6),
+ VppRoutePath(self.pg4.remote_ip6,
+ self.pg4.sw_if_index,
+ proto=DpoProto.DPO_PROTO_IP6)],
+ is_ip6=1)
+ route_3000_2.add_vpp_config()
+
+ route_4000_1 = VppIpRoute(self, "4000::1", 128,
+ [VppRoutePath("3000::1",
+ 0xffffffff,
+ proto=DpoProto.DPO_PROTO_IP6),
+ VppRoutePath("3000::2",
+ 0xffffffff,
+ proto=DpoProto.DPO_PROTO_IP6)],
+ is_ip6=1)
+ route_4000_1.add_vpp_config()
+
+ #
+ # inject the packet on pg0 - expect load-balancing across all 4 paths
+ #
+ self.vapi.cli("clear trace")
+ self.send_and_expect_load_balancing(self.pg0, port_pkts,
+ [self.pg1, self.pg2,
+ self.pg3, self.pg4])
+ self.send_and_expect_load_balancing(self.pg0, src_pkts,
+ [self.pg1, self.pg2,
+ self.pg3, self.pg4])
+
+ #
+ # Recursive prefixes
+ # - testing that 2 stages of load-balancing no choices
+ #
+ port_pkts = []
+
+ for ii in range(257):
+ port_pkts.append((Ether(src=self.pg0.remote_mac,
+ dst=self.pg0.local_mac) /
+ IPv6(dst="6000::1", src="6000:1::1") /
+ UDP(sport=1234, dport=1234 + ii) /
+ Raw('\xa5' * 100)))
+
+ route_5000_2 = VppIpRoute(self, "5000::2", 128,
+ [VppRoutePath(self.pg3.remote_ip6,
+ self.pg3.sw_if_index,
+ proto=DpoProto.DPO_PROTO_IP6)],
+ is_ip6=1)
+ route_5000_2.add_vpp_config()
+
+ route_6000_1 = VppIpRoute(self, "6000::1", 128,
+ [VppRoutePath("5000::2",
+ 0xffffffff,
+ proto=DpoProto.DPO_PROTO_IP6)],
+ is_ip6=1)
+ route_6000_1.add_vpp_config()
+
+ #
+ # inject the packet on pg0 - expect load-balancing across all 4 paths
+ #
+ self.vapi.cli("clear trace")
+ self.send_and_expect_one_itf(self.pg0, port_pkts, self.pg3)
+
+
+if __name__ == '__main__':
+ unittest.main(testRunner=VppTestRunner)
diff --git a/test/test_ip6_vrf_multi_instance.py b/test/test_ip6_vrf_multi_instance.py
new file mode 100644
index 00000000..8dd228ae
--- /dev/null
+++ b/test/test_ip6_vrf_multi_instance.py
@@ -0,0 +1,446 @@
+#!/usr/bin/env python
+"""IP6 VRF Multi-instance Test Case HLD:
+
+**NOTES:**
+ - higher number of pg-ip6 interfaces causes problems => only 15 pg-ip6 \
+ interfaces in 5 VRFs are tested
+ - jumbo packets in configuration with 15 pg-ip6 interfaces leads to \
+ problems too
+
+**config 1**
+ - add 15 pg-ip6 interfaces
+ - configure 5 hosts per pg-ip6 interface
+ - configure 4 VRFs
+ - add 3 pg-ip6 interfaces per VRF
+
+**test 1**
+ - send IP6 packets between all pg-ip6 interfaces in all VRF groups
+
+**verify 1**
+ - check VRF data by parsing output of ip6_fib_dump API command
+ - all packets received correctly in case of pg-ip6 interfaces in VRF
+ - no packet received in case of pg-ip6 interfaces not in VRF
+
+**config 2**
+ - reset 2 VRFs
+
+**test 2**
+ - send IP6 packets between all pg-ip6 interfaces in all VRF groups
+
+**verify 2**
+ - check VRF data by parsing output of ip6_fib_dump API command
+ - all packets received correctly in case of pg-ip6 interfaces in VRF
+ - no packet received in case of pg-ip6 interfaces not in VRF
+
+**config 3**
+ - add 1 of reset VRFs and 1 new VRF
+
+**test 3**
+ - send IP6 packets between all pg-ip6 interfaces in all VRF groups
+
+**verify 3**
+ - check VRF data by parsing output of ip6_fib_dump API command
+ - all packets received correctly in case of pg-ip6 interfaces in VRF
+ - no packet received in case of pg-ip6 interfaces not in VRF
+
+**config 4**
+ - reset all VRFs (i.e. no VRF except VRF=0 created)
+
+**test 4**
+ - send IP6 packets between all pg-ip6 interfaces in all VRF groups
+
+**verify 4**
+ - check VRF data by parsing output of ip6_fib_dump API command
+ - all packets received correctly in case of pg-ip6 interfaces in VRF
+ - no packet received in case of pg-ip6 interfaces not in VRF
+"""
+
+import unittest
+import random
+import socket
+
+from scapy.packet import Raw
+from scapy.layers.l2 import Ether
+from scapy.layers.inet6 import UDP, IPv6, ICMPv6ND_NS, ICMPv6ND_RA, \
+ RouterAlert, IPv6ExtHdrHopByHop
+from scapy.utils6 import in6_ismaddr, in6_isllsnmaddr, in6_getAddrType
+from scapy.pton_ntop import inet_ntop
+from scapy.data import IPV6_ADDR_UNICAST
+
+from framework import VppTestCase, VppTestRunner
+from util import ppp
+
+# VRF status constants
+VRF_NOT_CONFIGURED = 0
+VRF_CONFIGURED = 1
+VRF_RESET = 2
+
+
+def is_ipv6_misc_ext(p):
+ """ Is packet one of uninteresting IPv6 broadcasts (extended to filter out
+ ICMPv6 Neighbor Discovery - Neighbor Advertisement packets too)? """
+ if p.haslayer(ICMPv6ND_RA):
+ if in6_ismaddr(p[IPv6].dst):
+ return True
+ if p.haslayer(ICMPv6ND_NS):
+ if in6_isllsnmaddr(p[IPv6].dst):
+ return True
+ if p.haslayer(IPv6ExtHdrHopByHop):
+ for o in p[IPv6ExtHdrHopByHop].options:
+ if isinstance(o, RouterAlert):
+ return True
+ return False
+
+
+class TestIP6VrfMultiInst(VppTestCase):
+ """ IP6 VRF Multi-instance Test Case """
+
+ @classmethod
+ def setUpClass(cls):
+ """
+ Perform standard class setup (defined by class method setUpClass in
+ class VppTestCase) before running the test case, set test case related
+ variables and configure VPP.
+ """
+ super(TestIP6VrfMultiInst, cls).setUpClass()
+
+ # Test variables
+ cls.hosts_per_pg = 5
+ cls.nr_of_vrfs = 5
+ cls.pg_ifs_per_vrf = 3
+
+ try:
+ # Create pg interfaces
+ cls.create_pg_interfaces(
+ range(cls.nr_of_vrfs * cls.pg_ifs_per_vrf))
+
+ # Packet flows mapping pg0 -> pg1, pg2 etc.
+ cls.flows = dict()
+ for i in range(len(cls.pg_interfaces)):
+ multiplicand = i / cls.pg_ifs_per_vrf
+ pg_list = [
+ cls.pg_interfaces[multiplicand * cls.pg_ifs_per_vrf + j]
+ for j in range(cls.pg_ifs_per_vrf)
+ if (multiplicand * cls.pg_ifs_per_vrf + j) != i]
+ cls.flows[cls.pg_interfaces[i]] = pg_list
+
+ # Packet sizes - jumbo packet (9018 bytes) skipped
+ cls.pg_if_packet_sizes = [64, 512, 1518]
+
+ # Set up all interfaces
+ for pg_if in cls.pg_interfaces:
+ pg_if.admin_up()
+ pg_if.generate_remote_hosts(cls.hosts_per_pg)
+
+ # Create list of VRFs
+ cls.vrf_list = list()
+
+ # Create list of reset VRFs
+ cls.vrf_reset_list = list()
+
+ # Create list of pg_interfaces in VRFs
+ cls.pg_in_vrf = list()
+
+ # Create list of pg_interfaces not in BDs
+ cls.pg_not_in_vrf = [pg_if for pg_if in cls.pg_interfaces]
+
+ # Create mapping of pg_interfaces to VRF IDs
+ cls.pg_if_by_vrf_id = dict()
+ for i in range(cls.nr_of_vrfs):
+ vrf_id = i + 1
+ pg_list = [
+ cls.pg_interfaces[i * cls.pg_ifs_per_vrf + j]
+ for j in range(cls.pg_ifs_per_vrf)]
+ cls.pg_if_by_vrf_id[vrf_id] = pg_list
+
+ except Exception:
+ super(TestIP6VrfMultiInst, cls).tearDownClass()
+ raise
+
+ def setUp(self):
+ """
+ Clear trace and packet infos before running each test.
+ """
+ super(TestIP6VrfMultiInst, self).setUp()
+ self.reset_packet_infos()
+
+ def tearDown(self):
+ """
+ Show various debug prints after each test.
+ """
+ super(TestIP6VrfMultiInst, self).tearDown()
+ if not self.vpp_dead:
+ self.logger.info(self.vapi.ppcli("show ip6 fib"))
+ self.logger.info(self.vapi.ppcli("show ip6 neighbors"))
+
+ def create_vrf_and_assign_interfaces(self, count, start=1):
+ """
+ Create required number of FIB tables / VRFs, put 3 l2-pg interfaces
+ to every FIB table / VRF.
+
+ :param int count: Number of FIB tables / VRFs to be created.
+ :param int start: Starting number of the FIB table / VRF ID. \
+ (Default value = 1)
+ """
+ for i in range(count):
+ vrf_id = i + start
+ pg_if = self.pg_if_by_vrf_id[vrf_id][0]
+ dest_addr = pg_if.remote_hosts[0].ip6n
+ dest_addr_len = 64
+ self.vapi.ip_table_add_del(vrf_id, is_add=1, is_ipv6=1)
+ self.vapi.ip_add_del_route(
+ dest_addr, dest_addr_len, pg_if.local_ip6n, is_ipv6=1,
+ table_id=vrf_id, is_multipath=1)
+ self.logger.info("IPv6 VRF ID %d created" % vrf_id)
+ if vrf_id not in self.vrf_list:
+ self.vrf_list.append(vrf_id)
+ if vrf_id in self.vrf_reset_list:
+ self.vrf_reset_list.remove(vrf_id)
+ for j in range(self.pg_ifs_per_vrf):
+ pg_if = self.pg_if_by_vrf_id[vrf_id][j]
+ pg_if.set_table_ip6(vrf_id)
+ self.logger.info("pg-interface %s added to IPv6 VRF ID %d"
+ % (pg_if.name, vrf_id))
+ if pg_if not in self.pg_in_vrf:
+ self.pg_in_vrf.append(pg_if)
+ if pg_if in self.pg_not_in_vrf:
+ self.pg_not_in_vrf.remove(pg_if)
+ pg_if.config_ip6()
+ pg_if.disable_ipv6_ra()
+ pg_if.configure_ipv6_neighbors()
+ self.logger.debug(self.vapi.ppcli("show ip6 fib"))
+ self.logger.debug(self.vapi.ppcli("show ip6 neighbors"))
+
+ def reset_vrf(self, vrf_id):
+ """
+ Reset required FIB table / VRF.
+
+ :param int vrf_id: The FIB table / VRF ID to be reset.
+ """
+ # self.vapi.reset_vrf(vrf_id, is_ipv6=1)
+ self.vapi.reset_fib(vrf_id, is_ipv6=1)
+ if vrf_id in self.vrf_list:
+ self.vrf_list.remove(vrf_id)
+ if vrf_id not in self.vrf_reset_list:
+ self.vrf_reset_list.append(vrf_id)
+ for j in range(self.pg_ifs_per_vrf):
+ pg_if = self.pg_if_by_vrf_id[vrf_id][j]
+ pg_if.unconfig_ip6()
+ if pg_if in self.pg_in_vrf:
+ self.pg_in_vrf.remove(pg_if)
+ if pg_if not in self.pg_not_in_vrf:
+ self.pg_not_in_vrf.append(pg_if)
+ self.logger.info("IPv6 VRF ID %d reset" % vrf_id)
+ self.logger.debug(self.vapi.ppcli("show ip6 fib"))
+ self.logger.debug(self.vapi.ppcli("show ip6 neighbors"))
+ self.vapi.ip_table_add_del(vrf_id, is_add=0, is_ipv6=1)
+
+ def create_stream(self, src_if, packet_sizes):
+ """
+ Create input packet stream for defined interface using hosts list.
+
+ :param object src_if: Interface to create packet stream for.
+ :param list packet_sizes: List of required packet sizes.
+ :return: Stream of packets.
+ """
+ pkts = []
+ src_hosts = src_if.remote_hosts
+ for dst_if in self.flows[src_if]:
+ for dst_host in dst_if.remote_hosts:
+ src_host = random.choice(src_hosts)
+ pkt_info = self.create_packet_info(src_if, dst_if)
+ payload = self.info_to_payload(pkt_info)
+ p = (Ether(dst=src_if.local_mac, src=src_host.mac) /
+ IPv6(src=src_host.ip6, dst=dst_host.ip6) /
+ UDP(sport=1234, dport=1234) /
+ Raw(payload))
+ pkt_info.data = p.copy()
+ size = random.choice(packet_sizes)
+ self.extend_packet(p, size)
+ pkts.append(p)
+ self.logger.debug("Input stream created for port %s. Length: %u pkt(s)"
+ % (src_if.name, len(pkts)))
+ return pkts
+
+ def verify_capture(self, pg_if, capture):
+ """
+ Verify captured input packet stream for defined interface.
+
+ :param object pg_if: Interface to verify captured packet stream for.
+ :param list capture: Captured packet stream.
+ """
+ last_info = dict()
+ for i in self.pg_interfaces:
+ last_info[i.sw_if_index] = None
+ dst_sw_if_index = pg_if.sw_if_index
+ for packet in capture:
+ try:
+ ip = packet[IPv6]
+ udp = packet[UDP]
+ payload_info = self.payload_to_info(str(packet[Raw]))
+ packet_index = payload_info.index
+ self.assertEqual(payload_info.dst, dst_sw_if_index)
+ self.logger.debug("Got packet on port %s: src=%u (id=%u)" %
+ (pg_if.name, payload_info.src, packet_index))
+ next_info = self.get_next_packet_info_for_interface2(
+ payload_info.src, dst_sw_if_index,
+ last_info[payload_info.src])
+ last_info[payload_info.src] = next_info
+ self.assertIsNotNone(next_info)
+ self.assertEqual(packet_index, next_info.index)
+ saved_packet = next_info.data
+ # Check standard fields
+ self.assertEqual(ip.src, saved_packet[IPv6].src)
+ self.assertEqual(ip.dst, saved_packet[IPv6].dst)
+ self.assertEqual(udp.sport, saved_packet[UDP].sport)
+ self.assertEqual(udp.dport, saved_packet[UDP].dport)
+ except:
+ self.logger.error(ppp("Unexpected or invalid packet:", packet))
+ raise
+ for i in self.pg_interfaces:
+ remaining_packet = self.get_next_packet_info_for_interface2(
+ i, dst_sw_if_index, last_info[i.sw_if_index])
+ self.assertIsNone(
+ remaining_packet,
+ "Port %u: Packet expected from source %u didn't arrive" %
+ (dst_sw_if_index, i.sw_if_index))
+
+ def verify_vrf(self, vrf_id):
+ """
+ Check if the FIB table / VRF ID is configured.
+
+ :param int vrf_id: The FIB table / VRF ID to be verified.
+ :return: 1 if the FIB table / VRF ID is configured, otherwise return 0.
+ """
+ ip6_fib_dump = self.vapi.ip6_fib_dump()
+ vrf_exist = False
+ vrf_count = 0
+ for ip6_fib_details in ip6_fib_dump:
+ if ip6_fib_details.table_id == vrf_id:
+ if not vrf_exist:
+ vrf_exist = True
+ addr = inet_ntop(socket.AF_INET6, ip6_fib_details.address)
+ addrtype = in6_getAddrType(addr)
+ vrf_count += 1 if addrtype == IPV6_ADDR_UNICAST else 0
+ if not vrf_exist and vrf_count == 0:
+ self.logger.info("IPv6 VRF ID %d is not configured" % vrf_id)
+ return VRF_NOT_CONFIGURED
+ elif vrf_exist and vrf_count == 0:
+ self.logger.info("IPv6 VRF ID %d has been reset" % vrf_id)
+ return VRF_RESET
+ else:
+ self.logger.info("IPv6 VRF ID %d is configured" % vrf_id)
+ return VRF_CONFIGURED
+
+ def run_verify_test(self):
+ """
+ Create packet streams for all configured l2-pg interfaces, send all \
+ prepared packet streams and verify that:
+ - all packets received correctly on all pg-l2 interfaces assigned
+ to bridge domains
+ - no packet received on all pg-l2 interfaces not assigned to bridge
+ domains
+
+ :raise RuntimeError: If no packet captured on l2-pg interface assigned
+ to the bridge domain or if any packet is captured on l2-pg
+ interface not assigned to the bridge domain.
+ """
+ # Test
+ # Create incoming packet streams for packet-generator interfaces
+ for pg_if in self.pg_interfaces:
+ pkts = self.create_stream(pg_if, self.pg_if_packet_sizes)
+ pg_if.add_stream(pkts)
+
+ # Enable packet capture and start packet sending
+ self.pg_enable_capture(self.pg_interfaces)
+ self.pg_start()
+
+ # Verify
+ # Verify outgoing packet streams per packet-generator interface
+ for pg_if in self.pg_interfaces:
+ if pg_if in self.pg_in_vrf:
+ capture = pg_if.get_capture(remark="interface is in VRF")
+ self.verify_capture(pg_if, capture)
+ elif pg_if in self.pg_not_in_vrf:
+ pg_if.assert_nothing_captured(remark="interface is not in VRF",
+ filter_out_fn=is_ipv6_misc_ext)
+ self.logger.debug("No capture for interface %s" % pg_if.name)
+ else:
+ raise Exception("Unknown interface: %s" % pg_if.name)
+
+ def test_ip6_vrf_01(self):
+ """ IP6 VRF Multi-instance test 1 - create 4 VRFs
+ """
+ # Config 1
+ # Create 4 VRFs
+ self.create_vrf_and_assign_interfaces(4)
+
+ # Verify 1
+ for vrf_id in self.vrf_list:
+ self.assertEqual(self.verify_vrf(vrf_id), VRF_CONFIGURED)
+
+ # Test 1
+ self.run_verify_test()
+
+ def test_ip6_vrf_02(self):
+ """ IP6 VRF Multi-instance test 2 - reset 2 VRFs
+ """
+ # Config 2
+ # Delete 2 VRFs
+ self.reset_vrf(1)
+ self.reset_vrf(2)
+
+ # Verify 2
+ for vrf_id in self.vrf_reset_list:
+ self.assertEqual(self.verify_vrf(vrf_id), VRF_RESET)
+ for vrf_id in self.vrf_list:
+ self.assertEqual(self.verify_vrf(vrf_id), VRF_CONFIGURED)
+
+ # Test 2
+ self.run_verify_test()
+
+ # Reset routes learned from ICMPv6 Neighbor Discovery
+ for vrf_id in self.vrf_reset_list:
+ self.reset_vrf(vrf_id)
+
+ def test_ip6_vrf_03(self):
+ """ IP6 VRF Multi-instance 3 - add 2 VRFs
+ """
+ # Config 3
+ # Add 1 of reset VRFs and 1 new VRF
+ self.create_vrf_and_assign_interfaces(1)
+ self.create_vrf_and_assign_interfaces(1, start=5)
+
+ # Verify 3
+ for vrf_id in self.vrf_reset_list:
+ self.assertEqual(self.verify_vrf(vrf_id), VRF_RESET)
+ for vrf_id in self.vrf_list:
+ self.assertEqual(self.verify_vrf(vrf_id), VRF_CONFIGURED)
+
+ # Test 3
+ self.run_verify_test()
+
+ # Reset routes learned from ICMPv6 Neighbor Discovery
+ for vrf_id in self.vrf_reset_list:
+ self.reset_vrf(vrf_id)
+
+ def test_ip6_vrf_04(self):
+ """ IP6 VRF Multi-instance test 4 - reset 4 VRFs
+ """
+ # Config 4
+ # Reset all VRFs (i.e. no VRF except VRF=0 created)
+ for i in range(len(self.vrf_list)):
+ self.reset_vrf(self.vrf_list[0])
+
+ # Verify 4
+ for vrf_id in self.vrf_reset_list:
+ self.assertEqual(self.verify_vrf(vrf_id), VRF_RESET)
+ for vrf_id in self.vrf_list:
+ self.assertEqual(self.verify_vrf(vrf_id), VRF_CONFIGURED)
+
+ # Test 4
+ self.run_verify_test()
+
+
+if __name__ == '__main__':
+ unittest.main(testRunner=VppTestRunner)
diff --git a/test/test_ip_mcast.py b/test/test_ip_mcast.py
new file mode 100644
index 00000000..7cad683c
--- /dev/null
+++ b/test/test_ip_mcast.py
@@ -0,0 +1,760 @@
+#!/usr/bin/env python
+
+import unittest
+
+from framework import VppTestCase, VppTestRunner
+from vpp_sub_interface import VppSubInterface, VppDot1QSubint, VppDot1ADSubint
+from vpp_ip_route import VppIpMRoute, VppMRoutePath, VppMFibSignal, \
+ MRouteItfFlags, MRouteEntryFlags, VppIpTable
+
+from scapy.packet import Raw
+from scapy.layers.l2 import Ether
+from scapy.layers.inet import IP, UDP, getmacbyip, ICMP
+from scapy.layers.inet6 import IPv6, getmacbyip6
+from util import ppp
+
+#
+# The number of packets sent is set to 90 so that when we replicate more than 3
+# times, which we do for some entries, we will generate more than 256 packets
+# to the next node in the VLIB graph. Thus we are testing the code's
+# correctness handling this over-flow
+#
+N_PKTS_IN_STREAM = 90
+
+
+class TestMFIB(VppTestCase):
+ """ MFIB Test Case """
+
+ def setUp(self):
+ super(TestMFIB, self).setUp()
+
+ def test_mfib(self):
+ """ MFIB Unit Tests """
+ error = self.vapi.cli("test mfib")
+
+ if error:
+ self.logger.critical(error)
+ self.assertEqual(error.find("Failed"), -1)
+
+
+class TestIPMcast(VppTestCase):
+ """ IP Multicast Test Case """
+
+ def setUp(self):
+ super(TestIPMcast, self).setUp()
+
+ # create 8 pg interfaces
+ self.create_pg_interfaces(range(9))
+
+ # setup interfaces
+ for i in self.pg_interfaces[:8]:
+ i.admin_up()
+ i.config_ip4()
+ i.config_ip6()
+ i.resolve_arp()
+ i.resolve_ndp()
+
+ # one more in a vrf
+ tbl4 = VppIpTable(self, 10)
+ tbl4.add_vpp_config()
+ self.pg8.set_table_ip4(10)
+ self.pg8.config_ip4()
+
+ tbl6 = VppIpTable(self, 10, is_ip6=1)
+ tbl6.add_vpp_config()
+ self.pg8.set_table_ip6(10)
+ self.pg8.config_ip6()
+
+ def tearDown(self):
+ for i in self.pg_interfaces:
+ i.unconfig_ip4()
+ i.unconfig_ip6()
+ i.admin_down()
+
+ self.pg8.set_table_ip4(0)
+ self.pg8.set_table_ip6(0)
+ super(TestIPMcast, self).tearDown()
+
+ def create_stream_ip4(self, src_if, src_ip, dst_ip, payload_size=0):
+ pkts = []
+ # default to small packet sizes
+ p = (Ether(dst=src_if.local_mac, src=src_if.remote_mac) /
+ IP(src=src_ip, dst=dst_ip) /
+ UDP(sport=1234, dport=1234))
+ if not payload_size:
+ payload_size = 64 - len(p)
+ p = p / Raw('\xa5' * payload_size)
+
+ for i in range(0, N_PKTS_IN_STREAM):
+ pkts.append(p)
+ return pkts
+
+ def create_stream_ip6(self, src_if, src_ip, dst_ip):
+ pkts = []
+ for i in range(0, N_PKTS_IN_STREAM):
+ info = self.create_packet_info(src_if, src_if)
+ payload = self.info_to_payload(info)
+ p = (Ether(dst=src_if.local_mac, src=src_if.remote_mac) /
+ IPv6(src=src_ip, dst=dst_ip) /
+ UDP(sport=1234, dport=1234) /
+ Raw(payload))
+ info.data = p.copy()
+ pkts.append(p)
+ return pkts
+
+ def verify_filter(self, capture, sent):
+ if not len(capture) == len(sent):
+ # filter out any IPv6 RAs from the captur
+ for p in capture:
+ if (p.haslayer(IPv6)):
+ capture.remove(p)
+ return capture
+
+ def verify_capture_ip4(self, rx_if, sent):
+ rxd = rx_if.get_capture(len(sent))
+
+ try:
+ capture = self.verify_filter(rxd, sent)
+
+ self.assertEqual(len(capture), len(sent))
+
+ for i in range(len(capture)):
+ tx = sent[i]
+ rx = capture[i]
+
+ eth = rx[Ether]
+ self.assertEqual(eth.type, 0x800)
+
+ tx_ip = tx[IP]
+ rx_ip = rx[IP]
+
+ # check the MAC address on the RX'd packet is correctly formed
+ self.assertEqual(eth.dst, getmacbyip(rx_ip.dst))
+
+ self.assertEqual(rx_ip.src, tx_ip.src)
+ self.assertEqual(rx_ip.dst, tx_ip.dst)
+ # IP processing post pop has decremented the TTL
+ self.assertEqual(rx_ip.ttl + 1, tx_ip.ttl)
+
+ except:
+ raise
+
+ def verify_capture_ip6(self, rx_if, sent):
+ capture = rx_if.get_capture(len(sent))
+
+ self.assertEqual(len(capture), len(sent))
+
+ for i in range(len(capture)):
+ tx = sent[i]
+ rx = capture[i]
+
+ eth = rx[Ether]
+ self.assertEqual(eth.type, 0x86DD)
+
+ tx_ip = tx[IPv6]
+ rx_ip = rx[IPv6]
+
+ # check the MAC address on the RX'd packet is correctly formed
+ self.assertEqual(eth.dst, getmacbyip6(rx_ip.dst))
+
+ self.assertEqual(rx_ip.src, tx_ip.src)
+ self.assertEqual(rx_ip.dst, tx_ip.dst)
+ # IP processing post pop has decremented the TTL
+ self.assertEqual(rx_ip.hlim + 1, tx_ip.hlim)
+
+ def test_ip_mcast(self):
+ """ IP Multicast Replication """
+
+ #
+ # a stream that matches the default route. gets dropped.
+ #
+ self.vapi.cli("clear trace")
+ tx = self.create_stream_ip4(self.pg0, "1.1.1.1", "232.1.1.1")
+ self.pg0.add_stream(tx)
+
+ self.pg_enable_capture(self.pg_interfaces)
+ self.pg_start()
+
+ self.pg0.assert_nothing_captured(
+ remark="IP multicast packets forwarded on default route")
+
+ #
+ # A (*,G).
+ # one accepting interface, pg0, 7 forwarding interfaces
+ # many forwarding interfaces test the case where the replicare DPO
+ # needs to use extra cache lines for the buckets.
+ #
+ route_232_1_1_1 = VppIpMRoute(
+ self,
+ "0.0.0.0",
+ "232.1.1.1", 32,
+ MRouteEntryFlags.MFIB_ENTRY_FLAG_NONE,
+ [VppMRoutePath(self.pg0.sw_if_index,
+ MRouteItfFlags.MFIB_ITF_FLAG_ACCEPT),
+ VppMRoutePath(self.pg1.sw_if_index,
+ MRouteItfFlags.MFIB_ITF_FLAG_FORWARD),
+ VppMRoutePath(self.pg2.sw_if_index,
+ MRouteItfFlags.MFIB_ITF_FLAG_FORWARD),
+ VppMRoutePath(self.pg3.sw_if_index,
+ MRouteItfFlags.MFIB_ITF_FLAG_FORWARD),
+ VppMRoutePath(self.pg4.sw_if_index,
+ MRouteItfFlags.MFIB_ITF_FLAG_FORWARD),
+ VppMRoutePath(self.pg5.sw_if_index,
+ MRouteItfFlags.MFIB_ITF_FLAG_FORWARD),
+ VppMRoutePath(self.pg6.sw_if_index,
+ MRouteItfFlags.MFIB_ITF_FLAG_FORWARD),
+ VppMRoutePath(self.pg7.sw_if_index,
+ MRouteItfFlags.MFIB_ITF_FLAG_FORWARD)])
+ route_232_1_1_1.add_vpp_config()
+
+ #
+ # An (S,G).
+ # one accepting interface, pg0, 2 forwarding interfaces
+ #
+ route_1_1_1_1_232_1_1_1 = VppIpMRoute(
+ self,
+ "1.1.1.1",
+ "232.1.1.1", 64,
+ MRouteEntryFlags.MFIB_ENTRY_FLAG_NONE,
+ [VppMRoutePath(self.pg0.sw_if_index,
+ MRouteItfFlags.MFIB_ITF_FLAG_ACCEPT),
+ VppMRoutePath(self.pg1.sw_if_index,
+ MRouteItfFlags.MFIB_ITF_FLAG_FORWARD),
+ VppMRoutePath(self.pg2.sw_if_index,
+ MRouteItfFlags.MFIB_ITF_FLAG_FORWARD)])
+ route_1_1_1_1_232_1_1_1.add_vpp_config()
+
+ #
+ # An (*,G/m).
+ # one accepting interface, pg0, 1 forwarding interfaces
+ #
+ route_232 = VppIpMRoute(
+ self,
+ "0.0.0.0",
+ "232.0.0.0", 8,
+ MRouteEntryFlags.MFIB_ENTRY_FLAG_NONE,
+ [VppMRoutePath(self.pg0.sw_if_index,
+ MRouteItfFlags.MFIB_ITF_FLAG_ACCEPT),
+ VppMRoutePath(self.pg1.sw_if_index,
+ MRouteItfFlags.MFIB_ITF_FLAG_FORWARD)])
+ route_232.add_vpp_config()
+
+ #
+ # a stream that matches the route for (1.1.1.1,232.1.1.1)
+ # small packets
+ #
+ self.vapi.cli("clear trace")
+ tx = self.create_stream_ip4(self.pg0, "1.1.1.1", "232.1.1.1")
+ self.pg0.add_stream(tx)
+
+ self.pg_enable_capture(self.pg_interfaces)
+ self.pg_start()
+
+ # We expect replications on Pg1->7
+ self.verify_capture_ip4(self.pg1, tx)
+ self.verify_capture_ip4(self.pg2, tx)
+
+ # no replications on Pg0
+ self.pg0.assert_nothing_captured(
+ remark="IP multicast packets forwarded on PG0")
+ self.pg3.assert_nothing_captured(
+ remark="IP multicast packets forwarded on PG3")
+
+ #
+ # a stream that matches the route for (1.1.1.1,232.1.1.1)
+ # large packets
+ #
+ self.vapi.cli("clear trace")
+ tx = self.create_stream_ip4(self.pg0, "1.1.1.1", "232.1.1.1",
+ payload_size=1024)
+ self.pg0.add_stream(tx)
+
+ self.pg_enable_capture(self.pg_interfaces)
+ self.pg_start()
+
+ # We expect replications on Pg1->7
+ self.verify_capture_ip4(self.pg1, tx)
+ self.verify_capture_ip4(self.pg2, tx)
+
+ # no replications on Pg0
+ self.pg0.assert_nothing_captured(
+ remark="IP multicast packets forwarded on PG0")
+ self.pg3.assert_nothing_captured(
+ remark="IP multicast packets forwarded on PG3")
+
+ #
+ # a stream that matches the route for (*,232.0.0.0/8)
+ # Send packets with the 9th bit set so we test the correct clearing
+ # of that bit in the mac rewrite
+ #
+ self.vapi.cli("clear trace")
+ tx = self.create_stream_ip4(self.pg0, "1.1.1.1", "232.255.255.255")
+ self.pg0.add_stream(tx)
+
+ self.pg_enable_capture(self.pg_interfaces)
+ self.pg_start()
+
+ # We expect replications on Pg1 only
+ self.verify_capture_ip4(self.pg1, tx)
+
+ # no replications on Pg0, Pg2 not Pg3
+ self.pg0.assert_nothing_captured(
+ remark="IP multicast packets forwarded on PG0")
+ self.pg2.assert_nothing_captured(
+ remark="IP multicast packets forwarded on PG2")
+ self.pg3.assert_nothing_captured(
+ remark="IP multicast packets forwarded on PG3")
+
+ #
+ # a stream that matches the route for (*,232.1.1.1)
+ #
+ self.vapi.cli("clear trace")
+ tx = self.create_stream_ip4(self.pg0, "1.1.1.2", "232.1.1.1")
+ self.pg0.add_stream(tx)
+
+ self.pg_enable_capture(self.pg_interfaces)
+ self.pg_start()
+
+ # We expect replications on Pg1, 2, 3.
+ self.verify_capture_ip4(self.pg1, tx)
+ self.verify_capture_ip4(self.pg2, tx)
+ self.verify_capture_ip4(self.pg3, tx)
+ self.verify_capture_ip4(self.pg4, tx)
+ self.verify_capture_ip4(self.pg5, tx)
+ self.verify_capture_ip4(self.pg6, tx)
+ self.verify_capture_ip4(self.pg7, tx)
+
+ route_232_1_1_1.remove_vpp_config()
+ route_1_1_1_1_232_1_1_1.remove_vpp_config()
+ route_232.remove_vpp_config()
+
+ def test_ip6_mcast(self):
+ """ IPv6 Multicast Replication """
+
+ #
+ # a stream that matches the default route. gets dropped.
+ #
+ self.vapi.cli("clear trace")
+ tx = self.create_stream_ip6(self.pg0, "2001::1", "ff01::1")
+ self.pg0.add_stream(tx)
+
+ self.pg_enable_capture(self.pg_interfaces)
+ self.pg_start()
+
+ self.pg0.assert_nothing_captured(
+ remark="IPv6 multicast packets forwarded on default route")
+
+ #
+ # A (*,G).
+ # one accepting interface, pg0, 3 forwarding interfaces
+ #
+ route_ff01_1 = VppIpMRoute(
+ self,
+ "::",
+ "ff01::1", 128,
+ MRouteEntryFlags.MFIB_ENTRY_FLAG_NONE,
+ [VppMRoutePath(self.pg0.sw_if_index,
+ MRouteItfFlags.MFIB_ITF_FLAG_ACCEPT),
+ VppMRoutePath(self.pg1.sw_if_index,
+ MRouteItfFlags.MFIB_ITF_FLAG_FORWARD),
+ VppMRoutePath(self.pg2.sw_if_index,
+ MRouteItfFlags.MFIB_ITF_FLAG_FORWARD),
+ VppMRoutePath(self.pg3.sw_if_index,
+ MRouteItfFlags.MFIB_ITF_FLAG_FORWARD)],
+ is_ip6=1)
+ route_ff01_1.add_vpp_config()
+
+ #
+ # An (S,G).
+ # one accepting interface, pg0, 2 forwarding interfaces
+ #
+ route_2001_ff01_1 = VppIpMRoute(
+ self,
+ "2001::1",
+ "ff01::1", 256,
+ MRouteEntryFlags.MFIB_ENTRY_FLAG_NONE,
+ [VppMRoutePath(self.pg0.sw_if_index,
+ MRouteItfFlags.MFIB_ITF_FLAG_ACCEPT),
+ VppMRoutePath(self.pg1.sw_if_index,
+ MRouteItfFlags.MFIB_ITF_FLAG_FORWARD),
+ VppMRoutePath(self.pg2.sw_if_index,
+ MRouteItfFlags.MFIB_ITF_FLAG_FORWARD)],
+ is_ip6=1)
+ route_2001_ff01_1.add_vpp_config()
+
+ #
+ # An (*,G/m).
+ # one accepting interface, pg0, 1 forwarding interface
+ #
+ route_ff01 = VppIpMRoute(
+ self,
+ "::",
+ "ff01::", 16,
+ MRouteEntryFlags.MFIB_ENTRY_FLAG_NONE,
+ [VppMRoutePath(self.pg0.sw_if_index,
+ MRouteItfFlags.MFIB_ITF_FLAG_ACCEPT),
+ VppMRoutePath(self.pg1.sw_if_index,
+ MRouteItfFlags.MFIB_ITF_FLAG_FORWARD)],
+ is_ip6=1)
+ route_ff01.add_vpp_config()
+
+ #
+ # a stream that matches the route for (*, ff01::/16)
+ #
+ self.vapi.cli("clear trace")
+ tx = self.create_stream_ip6(self.pg0, "2002::1", "ff01:2::255")
+ self.pg0.add_stream(tx)
+
+ self.pg_enable_capture(self.pg_interfaces)
+ self.pg_start()
+
+ # We expect replications on Pg1
+ self.verify_capture_ip6(self.pg1, tx)
+
+ # no replications on Pg0, Pg3
+ self.pg0.assert_nothing_captured(
+ remark="IP multicast packets forwarded on PG0")
+ self.pg2.assert_nothing_captured(
+ remark="IP multicast packets forwarded on PG2")
+ self.pg3.assert_nothing_captured(
+ remark="IP multicast packets forwarded on PG3")
+
+ #
+ # Bounce the interface and it should still work
+ #
+ self.pg1.admin_down()
+ self.pg0.add_stream(tx)
+ self.pg_enable_capture(self.pg_interfaces)
+ self.pg_start()
+ self.pg1.assert_nothing_captured(
+ remark="IP multicast packets forwarded on down PG1")
+
+ self.pg1.admin_up()
+ self.pg0.add_stream(tx)
+ self.pg_enable_capture(self.pg_interfaces)
+ self.pg_start()
+ self.verify_capture_ip6(self.pg1, tx)
+
+ #
+ # a stream that matches the route for (*,ff01::1)
+ #
+ self.vapi.cli("clear trace")
+ tx = self.create_stream_ip6(self.pg0, "2002::2", "ff01::1")
+ self.pg0.add_stream(tx)
+
+ self.pg_enable_capture(self.pg_interfaces)
+ self.pg_start()
+
+ # We expect replications on Pg1, 2, 3.
+ self.verify_capture_ip6(self.pg1, tx)
+ self.verify_capture_ip6(self.pg2, tx)
+ self.verify_capture_ip6(self.pg3, tx)
+
+ # no replications on Pg0
+ self.pg0.assert_nothing_captured(
+ remark="IPv6 multicast packets forwarded on PG0")
+
+ #
+ # a stream that matches the route for (2001::1, ff00::1)
+ #
+ self.vapi.cli("clear trace")
+ tx = self.create_stream_ip6(self.pg0, "2001::1", "ff01::1")
+ self.pg0.add_stream(tx)
+
+ self.pg_enable_capture(self.pg_interfaces)
+ self.pg_start()
+
+ # We expect replications on Pg1, 2,
+ self.verify_capture_ip6(self.pg1, tx)
+ self.verify_capture_ip6(self.pg2, tx)
+
+ # no replications on Pg0, Pg3
+ self.pg0.assert_nothing_captured(
+ remark="IP multicast packets forwarded on PG0")
+ self.pg3.assert_nothing_captured(
+ remark="IP multicast packets forwarded on PG3")
+
+ route_ff01.remove_vpp_config()
+ route_ff01_1.remove_vpp_config()
+ route_2001_ff01_1.remove_vpp_config()
+
+ def _mcast_connected_send_stream(self, dst_ip):
+ self.vapi.cli("clear trace")
+ tx = self.create_stream_ip4(self.pg0,
+ self.pg0.remote_ip4,
+ dst_ip)
+ self.pg0.add_stream(tx)
+
+ self.pg_enable_capture(self.pg_interfaces)
+ self.pg_start()
+
+ # We expect replications on Pg1.
+ self.verify_capture_ip4(self.pg1, tx)
+
+ return tx
+
+ def test_ip_mcast_connected(self):
+ """ IP Multicast Connected Source check """
+
+ #
+ # A (*,G).
+ # one accepting interface, pg0, 1 forwarding interfaces
+ #
+ route_232_1_1_1 = VppIpMRoute(
+ self,
+ "0.0.0.0",
+ "232.1.1.1", 32,
+ MRouteEntryFlags.MFIB_ENTRY_FLAG_NONE,
+ [VppMRoutePath(self.pg0.sw_if_index,
+ MRouteItfFlags.MFIB_ITF_FLAG_ACCEPT),
+ VppMRoutePath(self.pg1.sw_if_index,
+ MRouteItfFlags.MFIB_ITF_FLAG_FORWARD)])
+
+ route_232_1_1_1.add_vpp_config()
+ route_232_1_1_1.update_entry_flags(
+ MRouteEntryFlags.MFIB_ENTRY_FLAG_CONNECTED)
+
+ #
+ # Now the (*,G) is present, send from connected source
+ #
+ tx = self._mcast_connected_send_stream("232.1.1.1")
+
+ #
+ # Constrct a representation of the signal we expect on pg0
+ #
+ signal_232_1_1_1_itf_0 = VppMFibSignal(self,
+ route_232_1_1_1,
+ self.pg0.sw_if_index,
+ tx[0])
+
+ #
+ # read the only expected signal
+ #
+ signals = self.vapi.mfib_signal_dump()
+
+ self.assertEqual(1, len(signals))
+
+ signal_232_1_1_1_itf_0.compare(signals[0])
+
+ #
+ # reading the signal allows for the generation of another
+ # so send more packets and expect the next signal
+ #
+ tx = self._mcast_connected_send_stream("232.1.1.1")
+
+ signals = self.vapi.mfib_signal_dump()
+ self.assertEqual(1, len(signals))
+ signal_232_1_1_1_itf_0.compare(signals[0])
+
+ #
+ # A Second entry with connected check
+ # one accepting interface, pg0, 1 forwarding interfaces
+ #
+ route_232_1_1_2 = VppIpMRoute(
+ self,
+ "0.0.0.0",
+ "232.1.1.2", 32,
+ MRouteEntryFlags.MFIB_ENTRY_FLAG_NONE,
+ [VppMRoutePath(self.pg0.sw_if_index,
+ MRouteItfFlags.MFIB_ITF_FLAG_ACCEPT),
+ VppMRoutePath(self.pg1.sw_if_index,
+ MRouteItfFlags.MFIB_ITF_FLAG_FORWARD)])
+
+ route_232_1_1_2.add_vpp_config()
+ route_232_1_1_2.update_entry_flags(
+ MRouteEntryFlags.MFIB_ENTRY_FLAG_CONNECTED)
+
+ #
+ # Send traffic to both entries. One read should net us two signals
+ #
+ signal_232_1_1_2_itf_0 = VppMFibSignal(self,
+ route_232_1_1_2,
+ self.pg0.sw_if_index,
+ tx[0])
+ tx = self._mcast_connected_send_stream("232.1.1.1")
+ tx2 = self._mcast_connected_send_stream("232.1.1.2")
+
+ #
+ # read the only expected signal
+ #
+ signals = self.vapi.mfib_signal_dump()
+
+ self.assertEqual(2, len(signals))
+
+ signal_232_1_1_1_itf_0.compare(signals[1])
+ signal_232_1_1_2_itf_0.compare(signals[0])
+
+ route_232_1_1_1.remove_vpp_config()
+ route_232_1_1_2.remove_vpp_config()
+
+ def test_ip_mcast_signal(self):
+ """ IP Multicast Signal """
+
+ #
+ # A (*,G).
+ # one accepting interface, pg0, 1 forwarding interfaces
+ #
+ route_232_1_1_1 = VppIpMRoute(
+ self,
+ "0.0.0.0",
+ "232.1.1.1", 32,
+ MRouteEntryFlags.MFIB_ENTRY_FLAG_NONE,
+ [VppMRoutePath(self.pg0.sw_if_index,
+ MRouteItfFlags.MFIB_ITF_FLAG_ACCEPT),
+ VppMRoutePath(self.pg1.sw_if_index,
+ MRouteItfFlags.MFIB_ITF_FLAG_FORWARD)])
+
+ route_232_1_1_1.add_vpp_config()
+ route_232_1_1_1.update_entry_flags(
+ MRouteEntryFlags.MFIB_ENTRY_FLAG_SIGNAL)
+
+ #
+ # Now the (*,G) is present, send from connected source
+ #
+ tx = self._mcast_connected_send_stream("232.1.1.1")
+
+ #
+ # Constrct a representation of the signal we expect on pg0
+ #
+ signal_232_1_1_1_itf_0 = VppMFibSignal(self,
+ route_232_1_1_1,
+ self.pg0.sw_if_index,
+ tx[0])
+
+ #
+ # read the only expected signal
+ #
+ signals = self.vapi.mfib_signal_dump()
+
+ self.assertEqual(1, len(signals))
+
+ signal_232_1_1_1_itf_0.compare(signals[0])
+
+ #
+ # reading the signal allows for the generation of another
+ # so send more packets and expect the next signal
+ #
+ tx = self._mcast_connected_send_stream("232.1.1.1")
+
+ signals = self.vapi.mfib_signal_dump()
+ self.assertEqual(1, len(signals))
+ signal_232_1_1_1_itf_0.compare(signals[0])
+
+ #
+ # Set the negate-signal on the accepting interval - the signals
+ # should stop
+ #
+ route_232_1_1_1.update_path_flags(
+ self.pg0.sw_if_index,
+ (MRouteItfFlags.MFIB_ITF_FLAG_ACCEPT |
+ MRouteItfFlags.MFIB_ITF_FLAG_NEGATE_SIGNAL))
+
+ self.vapi.cli("clear trace")
+ tx = self._mcast_connected_send_stream("232.1.1.1")
+
+ signals = self.vapi.mfib_signal_dump()
+ self.assertEqual(0, len(signals))
+
+ #
+ # Clear the SIGNAL flag on the entry and the signals should
+ # come back since the interface is still NEGATE-SIGNAL
+ #
+ route_232_1_1_1.update_entry_flags(
+ MRouteEntryFlags.MFIB_ENTRY_FLAG_NONE)
+
+ tx = self._mcast_connected_send_stream("232.1.1.1")
+
+ signals = self.vapi.mfib_signal_dump()
+ self.assertEqual(1, len(signals))
+ signal_232_1_1_1_itf_0.compare(signals[0])
+
+ #
+ # Lastly remove the NEGATE-SIGNAL from the interface and the
+ # signals should stop
+ #
+ route_232_1_1_1.update_path_flags(self.pg0.sw_if_index,
+ MRouteItfFlags.MFIB_ITF_FLAG_ACCEPT)
+
+ tx = self._mcast_connected_send_stream("232.1.1.1")
+ signals = self.vapi.mfib_signal_dump()
+ self.assertEqual(0, len(signals))
+
+ #
+ # Cleanup
+ #
+ route_232_1_1_1.remove_vpp_config()
+
+ def test_ip_mcast_vrf(self):
+ """ IP Multicast Replication in non-default table"""
+
+ #
+ # An (S,G).
+ # one accepting interface, pg0, 2 forwarding interfaces
+ #
+ route_1_1_1_1_232_1_1_1 = VppIpMRoute(
+ self,
+ "1.1.1.1",
+ "232.1.1.1", 64,
+ MRouteEntryFlags.MFIB_ENTRY_FLAG_NONE,
+ [VppMRoutePath(self.pg8.sw_if_index,
+ MRouteItfFlags.MFIB_ITF_FLAG_ACCEPT),
+ VppMRoutePath(self.pg1.sw_if_index,
+ MRouteItfFlags.MFIB_ITF_FLAG_FORWARD),
+ VppMRoutePath(self.pg2.sw_if_index,
+ MRouteItfFlags.MFIB_ITF_FLAG_FORWARD)],
+ table_id=10)
+ route_1_1_1_1_232_1_1_1.add_vpp_config()
+
+ #
+ # a stream that matches the route for (1.1.1.1,232.1.1.1)
+ # small packets
+ #
+ self.vapi.cli("clear trace")
+ tx = self.create_stream_ip4(self.pg8, "1.1.1.1", "232.1.1.1")
+ self.pg8.add_stream(tx)
+
+ self.pg_enable_capture(self.pg_interfaces)
+ self.pg_start()
+
+ # We expect replications on Pg1 & 2
+ self.verify_capture_ip4(self.pg1, tx)
+ self.verify_capture_ip4(self.pg2, tx)
+
+ def test_ip6_mcast_vrf(self):
+ """ IPv6 Multicast Replication in non-default table"""
+
+ #
+ # An (S,G).
+ # one accepting interface, pg0, 2 forwarding interfaces
+ #
+ route_2001_ff01_1 = VppIpMRoute(
+ self,
+ "2001::1",
+ "ff01::1", 256,
+ MRouteEntryFlags.MFIB_ENTRY_FLAG_NONE,
+ [VppMRoutePath(self.pg8.sw_if_index,
+ MRouteItfFlags.MFIB_ITF_FLAG_ACCEPT),
+ VppMRoutePath(self.pg1.sw_if_index,
+ MRouteItfFlags.MFIB_ITF_FLAG_FORWARD),
+ VppMRoutePath(self.pg2.sw_if_index,
+ MRouteItfFlags.MFIB_ITF_FLAG_FORWARD)],
+ table_id=10,
+ is_ip6=1)
+ route_2001_ff01_1.add_vpp_config()
+
+ #
+ # a stream that matches the route for (2001::1, ff00::1)
+ #
+ self.vapi.cli("clear trace")
+ tx = self.create_stream_ip6(self.pg8, "2001::1", "ff01::1")
+ self.pg8.add_stream(tx)
+
+ self.pg_enable_capture(self.pg_interfaces)
+ self.pg_start()
+
+ # We expect replications on Pg1, 2,
+ self.verify_capture_ip6(self.pg1, tx)
+ self.verify_capture_ip6(self.pg2, tx)
+
+if __name__ == '__main__':
+ unittest.main(testRunner=VppTestRunner)
diff --git a/test/test_jvpp.py b/test/test_jvpp.py
new file mode 100644
index 00000000..2497ff68
--- /dev/null
+++ b/test/test_jvpp.py
@@ -0,0 +1,135 @@
+#!/usr/bin/env python
+
+import os
+import subprocess
+
+from framework import VppTestCase
+
+# Api files path
+API_FILES_PATH = "vpp/vpp-api/java"
+
+# Registry jar file name prefix
+REGISTRY_JAR_PREFIX = "jvpp-registry"
+
+
+class TestJVpp(VppTestCase):
+ """ JVPP Core Test Case """
+
+ def invoke_for_jvpp_core(self, api_jar_name, test_class_name):
+ self.jvpp_connection_test(api_jar_name=api_jar_name,
+ test_class_name=test_class_name,
+ timeout=10)
+
+ def test_vpp_core_callback_api(self):
+ """ JVPP Core Callback Api Test Case """
+ self.invoke_for_jvpp_core(api_jar_name="jvpp-core",
+ test_class_name="io.fd.vpp.jvpp.core.test."
+ "CallbackApiTest")
+
+ def test_vpp_core_future_api(self):
+ """JVPP Core Future Api Test Case"""
+ self.invoke_for_jvpp_core(api_jar_name="jvpp-core",
+ test_class_name="io.fd.vpp.jvpp.core.test."
+ "FutureApiTest")
+
+ def test_vpp_acl_callback_api(self):
+ """ JVPP Acl Callback Api Test Case """
+ self.invoke_for_jvpp_core(api_jar_name="jvpp-acl",
+ test_class_name="io.fd.vpp.jvpp.acl.test."
+ "CallbackApiTest")
+
+ def test_vpp_acl_future_api(self):
+ """JVPP Acl Future Api Test Case"""
+ self.invoke_for_jvpp_core(api_jar_name="jvpp-acl",
+ test_class_name="io.fd.vpp.jvpp.acl.test."
+ "FutureApiTest")
+
+ def test_vpp_ioamexport_callback_api(self):
+ """ JVPP Ioamexport Callback Api Test Case """
+ self.invoke_for_jvpp_core(api_jar_name="jvpp-ioamexport",
+ test_class_name="io.fd.vpp.jvpp.ioamexport."
+ "test.CallbackApiTest")
+
+ def test_vpp_ioamexport_future_api(self):
+ """JVPP Ioamexport Future Api Test Case"""
+ self.invoke_for_jvpp_core(api_jar_name="jvpp-ioamexport",
+ test_class_name="io.fd.vpp.jvpp.ioamexport."
+ "test.FutureApiTest")
+
+ def test_vpp_ioampot_callback_api(self):
+ """ JVPP Ioampot Callback Api Test Case """
+ self.invoke_for_jvpp_core(api_jar_name="jvpp-ioampot",
+ test_class_name="io.fd.vpp.jvpp.ioampot."
+ "test.CallbackApiTest")
+
+ def test_vpp_ioampot_future_api(self):
+ """JVPP Ioampot Future Api Test Case"""
+ self.invoke_for_jvpp_core(api_jar_name="jvpp-ioampot",
+ test_class_name="io.fd.vpp.jvpp.ioampot."
+ "test.FutureApiTest")
+
+ def test_vpp_ioamtrace_callback_api(self):
+ """ JVPP Ioamtrace Callback Api Test Case """
+ self.invoke_for_jvpp_core(api_jar_name="jvpp-ioamtrace",
+ test_class_name="io.fd.vpp.jvpp.ioamtrace."
+ "test.CallbackApiTest")
+
+ def test_vpp_ioamtrace_future_api(self):
+ """JVPP Ioamtrace Future Api Test Case"""
+ self.invoke_for_jvpp_core(api_jar_name="jvpp-ioamtrace",
+ test_class_name="io.fd.vpp.jvpp.ioamtrace."
+ "test.FutureApiTest")
+
+ def test_vpp_snat_callback_api(self):
+ """ JVPP Snat Callback Api Test Case """
+ self.invoke_for_jvpp_core(api_jar_name="jvpp-nat",
+ test_class_name="io.fd.vpp.jvpp.nat.test."
+ "CallbackApiTest")
+
+ def test_vpp_snat_future_api(self):
+ """JVPP Snat Future Api Test Case"""
+ self.invoke_for_jvpp_core(api_jar_name="jvpp-nat",
+ test_class_name="io.fd.vpp.jvpp.nat.test."
+ "FutureApiTest")
+
+ def full_jar_name(self, install_dir, jar_name, version):
+ return os.path.join(install_dir, API_FILES_PATH,
+ "{0}-{1}.jar".format(jar_name, version))
+
+ def jvpp_connection_test(self, api_jar_name, test_class_name, timeout):
+ install_dir = os.getenv('VPP_TEST_BUILD_DIR')
+ self.logger.info("Install directory : {0}".format(install_dir))
+
+ version_reply = self.vapi.show_version()
+ version = version_reply.version.split("-")[0]
+ registry_jar_path = self.full_jar_name(install_dir,
+ REGISTRY_JAR_PREFIX, version)
+ self.logger.info("JVpp Registry jar path : {0}"
+ .format(registry_jar_path))
+
+ api_jar_path = self.full_jar_name(install_dir, api_jar_name, version)
+ self.logger.info("Api jar path : {0}".format(api_jar_path))
+
+ # passes shm prefix as parameter to create connection with same value
+ command = ["java", "-cp",
+ "{0}:{1}".format(registry_jar_path, api_jar_path),
+ test_class_name, "/{0}-vpe-api".format(self.shm_prefix)]
+ self.logger.info("Test Command : {0}, Timeout : {1}".
+ format(command, timeout))
+
+ self.process = subprocess.Popen(command, shell=False,
+ stdout=subprocess.PIPE,
+ stderr=subprocess.PIPE, bufsize=1,
+ universal_newlines=True)
+
+ out, err = self.process.communicate()
+ self.logger.info("Process output : {0}{1}".format(os.linesep, out))
+ self.logger.info("Process error output : {0}{1}"
+ .format(os.linesep, err))
+ self.assert_equal(self.process.returncode, 0, "process return code")
+
+ def tearDown(self):
+ self.logger.info("Tearing down jvpp test")
+ super(TestJVpp, self).tearDown()
+ if hasattr(self, 'process') and self.process.poll() is None:
+ self.process.kill()
diff --git a/test/test_l2_fib.py b/test/test_l2_fib.py
new file mode 100644
index 00000000..52bf9c86
--- /dev/null
+++ b/test/test_l2_fib.py
@@ -0,0 +1,542 @@
+#!/usr/bin/env python
+"""L2 FIB Test Case HLD:
+
+**config 1**
+ - add 4 pg-l2 interfaces
+ - configure them into l2bd
+ - configure 100 MAC entries in L2 fib - 25 MACs per interface
+ - L2 MAC learning and unknown unicast flooding disabled in l2bd
+ - configure 100 MAC entries in L2 fib - 25 MACs per interface
+
+**test 1**
+ - send L2 MAC frames between all 4 pg-l2 interfaces for all of 100 MAC \
+ entries in the FIB
+
+**verify 1**
+ - all packets received correctly
+
+**config 2**
+ - delete 12 MAC entries - 3 MACs per interface
+
+**test 2a**
+ - send L2 MAC frames between all 4 pg-l2 interfaces for non-deleted MAC \
+ entries
+
+**verify 2a**
+ - all packets received correctly
+
+**test 2b**
+ - send L2 MAC frames between all 4 pg-l2 interfaces for all of 12 deleted \
+ MAC entries
+
+**verify 2b**
+ - no packet received on all 4 pg-l2 interfaces
+
+**config 3**
+ - configure new 100 MAC entries in L2 fib - 25 MACs per interface
+
+**test 3**
+ - send L2 MAC frames between all 4 pg-l2 interfaces for all of 188 MAC \
+ entries in the FIB
+
+**verify 3**
+ - all packets received correctly
+
+**config 4**
+ - delete 160 MAC entries, 40 MACs per interface
+
+**test 4a**
+ - send L2 MAC frames between all 4 pg-l2 interfaces for all of 28 \
+ non-deleted MAC entries
+
+**verify 4a**
+ - all packets received correctly
+
+**test 4b**
+ - try send L2 MAC frames between all 4 pg-l2 interfaces for all of 172 \
+ deleted MAC entries
+
+**verify 4b**
+ - no packet received on all 4 pg-l2 interfaces
+"""
+
+import unittest
+import random
+
+from scapy.packet import Raw
+from scapy.layers.l2 import Ether
+from scapy.layers.inet import IP, UDP
+
+from framework import VppTestCase, VppTestRunner
+from util import Host, ppp
+
+
+class TestL2fib(VppTestCase):
+ """ L2 FIB Test Case """
+
+ @classmethod
+ def bd_ifs(cls, bd_id):
+ return range((bd_id - 1) * cls.n_ifs_per_bd, bd_id * cls.n_ifs_per_bd)
+
+ @classmethod
+ def setUpClass(cls):
+ """
+ Perform standard class setup (defined by class method setUpClass in
+ class VppTestCase) before running the test case, set test case related
+ variables and configure VPP.
+
+ :var int bd_id: Bridge domain ID.
+ """
+ super(TestL2fib, cls).setUpClass()
+
+ try:
+ n_brs = cls.n_brs = range(1, 3)
+ cls.n_ifs_per_bd = 4
+ n_ifs = range(cls.n_ifs_per_bd * len(cls.n_brs))
+ # Create 4 pg interfaces
+ cls.create_pg_interfaces(n_ifs)
+
+ cls.flows = dict()
+ for bd_id in n_brs:
+ # Packet flows mapping pg0 -> pg1, pg2, pg3 etc.
+ ifs = cls.bd_ifs(bd_id)
+ for j in ifs:
+ cls.flows[cls.pg_interfaces[j]] = [
+ cls.pg_interfaces[x] for x in ifs if x != j]
+
+ # Packet sizes
+ cls.pg_if_packet_sizes = [64, 512, 1518, 9018]
+
+ for bd_id in n_brs:
+ # Create BD with MAC learning and unknown unicast flooding
+ # disabled and put interfaces to this BD
+ cls.vapi.bridge_domain_add_del(
+ bd_id=bd_id, uu_flood=0, learn=0)
+ ifs = [cls.pg_interfaces[i] for i in cls.bd_ifs(bd_id)]
+ for pg_if in ifs:
+ cls.vapi.sw_interface_set_l2_bridge(pg_if.sw_if_index,
+ bd_id=bd_id)
+
+ # Set up all interfaces
+ for i in cls.pg_interfaces:
+ i.admin_up()
+
+ # Mapping between packet-generator index and lists of test hosts
+ cls.hosts = dict()
+ cls.learned_hosts = dict()
+ cls.fib_hosts = dict()
+ cls.deleted_hosts = dict()
+ for pg_if in cls.pg_interfaces:
+ swif = pg_if.sw_if_index
+ cls.hosts[swif] = []
+ cls.learned_hosts[swif] = []
+ cls.fib_hosts[swif] = []
+ cls.deleted_hosts[swif] = []
+
+ except Exception:
+ super(TestL2fib, cls).tearDownClass()
+ raise
+
+ def setUp(self):
+ super(TestL2fib, self).setUp()
+ self.reset_packet_infos()
+
+ def tearDown(self):
+ """
+ Show various debug prints after each test.
+ """
+ super(TestL2fib, self).tearDown()
+ if not self.vpp_dead:
+ self.logger.info(self.vapi.ppcli("show l2fib verbose"))
+ for bd_id in self.n_brs:
+ self.logger.info(self.vapi.ppcli("show bridge-domain %s detail"
+ % bd_id))
+
+ def create_hosts(self, n_hosts_per_if, subnet):
+ """
+ Create required number of host MAC addresses and distribute them among
+ interfaces. Create host IPv4 address for every host MAC address.
+
+ :param int n_hosts_per_if: Number of per interface hosts to
+ create MAC/IPv4 addresses for.
+ """
+
+ for pg_if in self.pg_interfaces:
+ swif = pg_if.sw_if_index
+
+ def mac(j): return "00:00:%02x:ff:%02x:%02x" % (subnet, swif, j)
+
+ def ip(j): return "172.%02u.1%02x.%u" % (subnet, swif, j)
+
+ def h(j): return Host(mac(j), ip(j))
+ self.hosts[swif] = [h(j) for j in range(n_hosts_per_if)]
+
+ def learn_hosts(self, bd_id, n_hosts_per_if):
+ """
+ Create L2 MAC packet stream with host MAC addresses per interface to
+ let the bridge domain learn these MAC addresses.
+
+ :param int bd_id: BD to teach
+ :param int n_hosts_per_if: number of hosts
+ """
+ self.vapi.bridge_flags(bd_id, 1, 1)
+ ifs = [self.pg_interfaces[i] for i in self.bd_ifs(bd_id)]
+ for pg_if in ifs:
+ swif = pg_if.sw_if_index
+ hosts = self.hosts[swif]
+ lhosts = self.learned_hosts[swif]
+ packets = []
+ for j in range(n_hosts_per_if):
+ host = hosts.pop()
+ lhosts.append(host)
+ packet = (Ether(dst="ff:ff:ff:ff:ff:ff", src=host.mac))
+ packets.append(packet)
+ pg_if.add_stream(packets)
+ self.logger.info("Sending broadcast eth frames for MAC learning")
+ self.pg_start()
+
+ def config_l2_fib_entries(self, bd_id, n_hosts_per_if):
+ """
+ Config required number of L2 FIB entries.
+
+ :param int bd_id: BD's id
+ :param int count: Number of L2 FIB entries to be created.
+ :param int start: Starting index of the host list. (Default value = 0)
+ """
+ ifs = [self.pg_interfaces[i] for i in self.bd_ifs(bd_id)]
+ for pg_if in ifs:
+ swif = pg_if.sw_if_index
+ hosts = self.hosts[swif]
+ fhosts = self.fib_hosts[swif]
+ for j in range(n_hosts_per_if):
+ host = hosts.pop()
+ self.vapi.l2fib_add_del(
+ host.mac, bd_id, swif, static_mac=1)
+ fhosts.append(host)
+ # del hosts[0]
+ self.logger.info("Configure %d L2 FIB entries .." %
+ len(self.pg_interfaces) * n_hosts_per_if)
+ self.logger.info(self.vapi.ppcli("show l2fib"))
+
+ def delete_l2_fib_entry(self, bd_id, n_hosts_per_if):
+ """
+ Delete required number of L2 FIB entries.
+
+ :param int count: Number of L2 FIB entries to be created.
+ """
+ ifs = [self.pg_interfaces[i] for i in self.bd_ifs(bd_id)]
+ for pg_if in ifs:
+ swif = pg_if.sw_if_index
+ hosts = self.fib_hosts[swif]
+ dhosts = self.deleted_hosts[swif]
+ for j in range(n_hosts_per_if):
+ host = hosts.pop()
+ self.vapi.l2fib_add_del(
+ host.mac, bd_id, swif, is_add=0)
+ dhosts.append(host)
+ self.logger.info(self.vapi.ppcli("show l2fib"))
+
+ def flush_int(self, swif):
+ """
+ Flush swif L2 FIB entries.
+
+ :param int swif: sw if index.
+ """
+ flushed = dict()
+ self.vapi.l2fib_flush_int(swif)
+ self.deleted_hosts[swif] = self.learned_hosts[swif] + \
+ self.deleted_hosts[swif]
+ flushed[swif] = self.learned_hosts[swif]
+ self.learned_hosts[swif] = []
+ self.logger.info(self.vapi.ppcli("show l2fib"))
+ return flushed
+
+ def flush_bd(self, bd_id):
+ """
+ Flush bd_id L2 FIB entries.
+
+ :param int bd_id: Bridge Domain id.
+ """
+ self.vapi.l2fib_flush_bd(bd_id)
+ flushed = dict()
+ ifs = [self.pg_interfaces[i] for i in self.bd_ifs(bd_id)]
+ for pg_if in ifs:
+ swif = pg_if.sw_if_index
+ self.deleted_hosts[swif] = self.learned_hosts[swif] + \
+ self.deleted_hosts[swif]
+ flushed[swif] = self.learned_hosts[swif]
+ self.learned_hosts[swif] = []
+ self.logger.info(self.vapi.ppcli("show l2fib"))
+ return flushed
+
+ def flush_all(self):
+ """
+ Flush All L2 FIB entries.
+ """
+ self.vapi.l2fib_flush_all()
+ flushed = dict()
+ for pg_if in self.pg_interfaces:
+ swif = pg_if.sw_if_index
+ self.deleted_hosts[swif] = self.learned_hosts[swif] + \
+ self.deleted_hosts[swif]
+ flushed[swif] = self.learned_hosts[swif]
+ self.learned_hosts[swif] = []
+ self.logger.info(self.vapi.ppcli("show l2fib"))
+ return flushed
+
+ def create_stream(self, src_if, packet_sizes, if_src_hosts=None,
+ if_dst_hosts=None):
+ """
+ Create input packet stream for defined interface using hosts or
+ deleted_hosts list.
+
+ :param object src_if: Interface to create packet stream for.
+ :param list packet_sizes: List of required packet sizes.
+ :param boolean deleted: Set to True if deleted_hosts list required.
+ :return: Stream of packets.
+ """
+ if not if_src_hosts:
+ if_src_hosts = self.fib_hosts
+ if not if_dst_hosts:
+ if_dst_hosts = self.fib_hosts
+ src_hosts = if_src_hosts[src_if.sw_if_index]
+ if not src_hosts:
+ return []
+ pkts = []
+ for dst_if in self.flows[src_if]:
+ dst_swif = dst_if.sw_if_index
+ if dst_swif not in if_dst_hosts:
+ continue
+ dst_hosts = if_dst_hosts[dst_swif]
+ for i in range(0, len(dst_hosts)):
+ dst_host = dst_hosts[i]
+ src_host = random.choice(src_hosts)
+ pkt_info = self.create_packet_info(src_if, dst_if)
+ payload = self.info_to_payload(pkt_info)
+ p = (Ether(dst=dst_host.mac, src=src_host.mac) /
+ IP(src=src_host.ip4, dst=dst_host.ip4) /
+ UDP(sport=1234, dport=1234) /
+ Raw(payload))
+ pkt_info.data = p.copy()
+ size = random.choice(packet_sizes)
+ self.extend_packet(p, size)
+ pkts.append(p)
+ return pkts
+
+ def verify_capture(self, pg_if, capture):
+ """
+ Verify captured input packet stream for defined interface.
+
+ :param object pg_if: Interface to verify captured packet stream for.
+ :param list capture: Captured packet stream.
+ """
+ last_info = dict()
+ for i in self.pg_interfaces:
+ last_info[i.sw_if_index] = None
+ dst_sw_if_index = pg_if.sw_if_index
+ for packet in capture:
+ payload_info = self.payload_to_info(str(packet[Raw]))
+ try:
+ ip = packet[IP]
+ udp = packet[UDP]
+ packet_index = payload_info.index
+ self.assertEqual(payload_info.dst, dst_sw_if_index)
+ self.logger.debug("Got packet on port %s: src=%u (id=%u)" %
+ (pg_if.name, payload_info.src, packet_index))
+ next_info = self.get_next_packet_info_for_interface2(
+ payload_info.src, dst_sw_if_index,
+ last_info[payload_info.src])
+ last_info[payload_info.src] = next_info
+ self.assertTrue(next_info is not None)
+ self.assertEqual(packet_index, next_info.index)
+ saved_packet = next_info.data
+ # Check standard fields
+ self.assertEqual(ip.src, saved_packet[IP].src)
+ self.assertEqual(ip.dst, saved_packet[IP].dst)
+ self.assertEqual(udp.sport, saved_packet[UDP].sport)
+ self.assertEqual(udp.dport, saved_packet[UDP].dport)
+ except:
+ self.logger.error(ppp("Unexpected or invalid packet:", packet))
+ raise
+ for i in self.pg_interfaces:
+ remaining_packet = self.get_next_packet_info_for_interface2(
+ i, dst_sw_if_index, last_info[i.sw_if_index])
+ self.assertTrue(
+ remaining_packet is None,
+ "Port %u: Packet expected from source %u didn't arrive" %
+ (dst_sw_if_index, i.sw_if_index))
+
+ def run_verify_test(self, bd_id, dst_hosts=None):
+ # Test
+ # Create incoming packet streams for packet-generator interfaces
+ if not dst_hosts:
+ dst_hosts = self.fib_hosts
+ self.reset_packet_infos()
+ ifs = [self.pg_interfaces[i] for i in self.bd_ifs(bd_id)]
+ for i in ifs:
+ pkts = self.create_stream(
+ i, self.pg_if_packet_sizes, if_dst_hosts=dst_hosts)
+ i.add_stream(pkts)
+
+ self.vapi.bridge_flags(bd_id, 0, 1)
+ # Enable packet capture and start packet sending
+ self.pg_enable_capture(ifs)
+ self.pg_start()
+
+ # Verify
+ # Verify outgoing packet streams per packet-generator interface
+ for i in ifs:
+ if not dst_hosts[i.sw_if_index]:
+ continue
+ capture = i.get_capture()
+ self.logger.info("Verifying capture on interface %s" % i.name)
+ self.verify_capture(i, capture)
+
+ def run_verify_negat_test(self, bd_id, dst_hosts=None):
+ # Test
+ # Create incoming packet streams for packet-generator interfaces for
+ # deleted MAC addresses
+ if not dst_hosts:
+ dst_hosts = self.deleted_hosts
+ self.reset_packet_infos()
+ ifs = [self.pg_interfaces[i] for i in self.bd_ifs(bd_id)]
+ for i in ifs:
+ pkts = self.create_stream(
+ i, self.pg_if_packet_sizes, if_dst_hosts=dst_hosts)
+ if pkts:
+ i.add_stream(pkts)
+
+ self.vapi.bridge_flags(bd_id, 0, 1)
+ # Enable packet capture and start packet sending
+ self.pg_enable_capture(ifs)
+ self.pg_start()
+
+ # Verify
+ # Verify outgoing packet streams per packet-generator interface
+ timeout = 1
+ for i in ifs:
+ i.get_capture(0, timeout=timeout)
+ i.assert_nothing_captured(remark="outgoing interface")
+ timeout = 0.1
+
+ def test_l2_fib_01(self):
+ """ L2 FIB test 1 - program 100 MAC addresses
+ """
+ # Config 1
+ # Create test host entries
+ self.create_hosts(100, subnet=17)
+
+ # Add first 100 MAC entries to L2 FIB
+ self.config_l2_fib_entries(bd_id=1, n_hosts_per_if=100)
+
+ # Test 1
+ self.run_verify_test(bd_id=1)
+
+ def test_l2_fib_02(self):
+ """ L2 FIB test 2 - delete 12 MAC entries
+ """
+ # Config 2
+ # Delete 12 MAC entries per interface from L2 FIB
+ self.delete_l2_fib_entry(bd_id=1, n_hosts_per_if=12)
+
+ # Test 2a
+ self.run_verify_test(bd_id=1)
+
+ # Verify 2a
+ self.run_verify_negat_test(bd_id=1)
+
+ def test_l2_fib_03(self):
+ """ L2 FIB test 3 - program new 100 MAC addresses
+ """
+ # Config 3
+ # Create new test host entries
+ self.create_hosts(100, subnet=22)
+
+ # Add new 100 MAC entries to L2 FIB
+ self.config_l2_fib_entries(bd_id=1, n_hosts_per_if=100)
+
+ # Test 3
+ self.run_verify_test(bd_id=1)
+
+ def test_l2_fib_04(self):
+ """ L2 FIB test 4 - delete 160 MAC entries
+ """
+ # Config 4
+ # Delete 160 MAC entries per interface from L2 FIB
+ self.delete_l2_fib_entry(bd_id=1, n_hosts_per_if=160)
+
+ # Test 4a
+ self.run_verify_negat_test(bd_id=1)
+
+ def test_l2_fib_05(self):
+ """ L2 FIB test 5 - Program 10 new MAC entries, learn 10
+ """
+ self.create_hosts(20, subnet=35)
+
+ self.learn_hosts(bd_id=1, n_hosts_per_if=10)
+ self.learn_hosts(bd_id=2, n_hosts_per_if=10)
+ self.config_l2_fib_entries(bd_id=1, n_hosts_per_if=10)
+ self.config_l2_fib_entries(bd_id=2, n_hosts_per_if=10)
+ self.run_verify_test(bd_id=1, dst_hosts=self.learned_hosts)
+ self.run_verify_test(bd_id=2, dst_hosts=self.learned_hosts)
+
+ def test_l2_fib_06(self):
+ """ L2 FIB test 6 - flush first interface
+ """
+ self.create_hosts(20, subnet=36)
+
+ self.learn_hosts(bd_id=1, n_hosts_per_if=10)
+ self.learn_hosts(bd_id=2, n_hosts_per_if=10)
+ self.config_l2_fib_entries(bd_id=1, n_hosts_per_if=10)
+ self.config_l2_fib_entries(bd_id=2, n_hosts_per_if=10)
+ flushed = self.flush_int(self.pg_interfaces[0].sw_if_index)
+ self.run_verify_test(bd_id=1, dst_hosts=self.learned_hosts)
+ self.run_verify_negat_test(bd_id=1, dst_hosts=flushed)
+
+ def test_l2_fib_07(self):
+ """ L2 FIB test 7 - flush bd_id
+ """
+ self.create_hosts(20, subnet=37)
+
+ self.learn_hosts(bd_id=1, n_hosts_per_if=10)
+ self.learn_hosts(bd_id=2, n_hosts_per_if=10)
+ self.config_l2_fib_entries(bd_id=1, n_hosts_per_if=10)
+ self.config_l2_fib_entries(bd_id=2, n_hosts_per_if=10)
+ flushed = self.flush_bd(bd_id=1)
+ self.run_verify_negat_test(bd_id=1, dst_hosts=flushed)
+ self.run_verify_test(bd_id=2, dst_hosts=self.learned_hosts)
+
+ def test_l2_fib_08(self):
+ """ L2 FIB test 8 - flush all
+ """
+ self.create_hosts(20, subnet=38)
+
+ self.learn_hosts(bd_id=1, n_hosts_per_if=10)
+ self.learn_hosts(bd_id=2, n_hosts_per_if=10)
+ self.config_l2_fib_entries(bd_id=1, n_hosts_per_if=10)
+ self.config_l2_fib_entries(bd_id=2, n_hosts_per_if=10)
+ flushed = self.flush_all()
+ self.run_verify_negat_test(bd_id=1, dst_hosts=flushed)
+ self.run_verify_negat_test(bd_id=2, dst_hosts=flushed)
+
+ def test_l2_fib_09(self):
+ """ L2 FIB test 9 - mac learning events
+ """
+ self.create_hosts(10, subnet=39)
+
+ self.vapi.want_macs_learn_events()
+ self.learn_hosts(bd_id=1, n_hosts_per_if=10)
+
+ self.sleep(1)
+ self.logger.info(self.vapi.ppcli("show l2fib"))
+ evs = self.vapi.collect_events()
+ learned_macs = {
+ e.mac[i].mac_addr for e in evs for i in range(e.n_macs)}
+ macs = {h.bin_mac for swif_hs in self.learned_hosts.itervalues()
+ for h in swif_hs}
+ self.vapi.want_macs_learn_events(enable_disable=0)
+ self.assertEqual(len(learned_macs ^ macs), 0)
+
+
+if __name__ == '__main__':
+ unittest.main(testRunner=VppTestRunner)
diff --git a/test/test_l2bd.py b/test/test_l2bd.py
new file mode 100644
index 00000000..30708a46
--- /dev/null
+++ b/test/test_l2bd.py
@@ -0,0 +1,285 @@
+#!/usr/bin/env python
+
+import unittest
+import random
+
+from scapy.packet import Raw
+from scapy.layers.l2 import Ether, Dot1Q
+from scapy.layers.inet import IP, UDP
+
+from framework import VppTestCase, VppTestRunner
+from util import Host, ppp
+from vpp_sub_interface import VppDot1QSubint, VppDot1ADSubint
+
+
+class TestL2bd(VppTestCase):
+ """ L2BD Test Case """
+
+ @classmethod
+ def setUpClass(cls):
+ """
+ Perform standard class setup (defined by class method setUpClass in
+ class VppTestCase) before running the test case, set test case related
+ variables and configure VPP.
+
+ :var int bd_id: Bridge domain ID.
+ :var int mac_entries_count: Number of MAC entries for bridge-domain to
+ learn.
+ :var int dot1q_tag: VLAN tag for dot1q sub-interface.
+ :var int dot1ad_sub_id: SubID of dot1ad sub-interface.
+ :var int dot1ad_outer_tag: VLAN S-tag for dot1ad sub-interface.
+ :var int dot1ad_inner_tag: VLAN C-tag for dot1ad sub-interface.
+ :var int sl_pkts_per_burst: Number of packets in burst for single-loop
+ test.
+ :var int dl_pkts_per_burst: Number of packets in burst for dual-loop
+ test.
+ """
+ super(TestL2bd, cls).setUpClass()
+
+ # Test variables
+ cls.bd_id = 1
+ cls.mac_entries_count = 100
+ # cls.dot1q_sub_id = 100
+ cls.dot1q_tag = 100
+ cls.dot1ad_sub_id = 20
+ cls.dot1ad_outer_tag = 200
+ cls.dot1ad_inner_tag = 300
+ cls.sl_pkts_per_burst = 2
+ cls.dl_pkts_per_burst = 257
+
+ try:
+ # create 3 pg interfaces
+ cls.create_pg_interfaces(range(3))
+
+ # create 2 sub-interfaces for pg1 and pg2
+ cls.sub_interfaces = [
+ VppDot1QSubint(cls, cls.pg1, cls.dot1q_tag),
+ VppDot1ADSubint(cls, cls.pg2, cls.dot1ad_sub_id,
+ cls.dot1ad_outer_tag, cls.dot1ad_inner_tag)]
+
+ # packet flows mapping pg0 -> pg1, pg2, etc.
+ cls.flows = dict()
+ cls.flows[cls.pg0] = [cls.pg1, cls.pg2]
+ cls.flows[cls.pg1] = [cls.pg0, cls.pg2]
+ cls.flows[cls.pg2] = [cls.pg0, cls.pg1]
+
+ # packet sizes
+ cls.pg_if_packet_sizes = [64, 512, 1518, 9018]
+ cls.sub_if_packet_sizes = [64, 512, 1518 + 4, 9018 + 4]
+
+ cls.interfaces = list(cls.pg_interfaces)
+ cls.interfaces.extend(cls.sub_interfaces)
+
+ # Create BD with MAC learning enabled and put interfaces and
+ # sub-interfaces to this BD
+ for pg_if in cls.pg_interfaces:
+ sw_if_index = pg_if.sub_if.sw_if_index \
+ if hasattr(pg_if, 'sub_if') else pg_if.sw_if_index
+ cls.vapi.sw_interface_set_l2_bridge(sw_if_index,
+ bd_id=cls.bd_id)
+
+ # setup all interfaces
+ for i in cls.interfaces:
+ i.admin_up()
+
+ # mapping between packet-generator index and lists of test hosts
+ cls.hosts_by_pg_idx = dict()
+
+ # create test host entries and inject packets to learn MAC entries
+ # in the bridge-domain
+ cls.create_hosts_and_learn(cls.mac_entries_count)
+ cls.logger.info(cls.vapi.ppcli("show l2fib"))
+
+ except Exception:
+ super(TestL2bd, cls).tearDownClass()
+ raise
+
+ def setUp(self):
+ """
+ Clear trace and packet infos before running each test.
+ """
+ super(TestL2bd, self).setUp()
+ self.reset_packet_infos()
+
+ def tearDown(self):
+ """
+ Show various debug prints after each test.
+ """
+ super(TestL2bd, self).tearDown()
+ if not self.vpp_dead:
+ self.logger.info(self.vapi.ppcli("show l2fib verbose"))
+ self.logger.info(self.vapi.ppcli("show bridge-domain %s detail" %
+ self.bd_id))
+
+ @classmethod
+ def create_hosts_and_learn(cls, count):
+ """
+ Create required number of host MAC addresses and distribute them among
+ interfaces. Create host IPv4 address for every host MAC address. Create
+ L2 MAC packet stream with host MAC addresses per interface to let
+ the bridge domain learn these MAC addresses.
+
+ :param count: Integer number of hosts to create MAC/IPv4 addresses for.
+ """
+ n_int = len(cls.pg_interfaces)
+ macs_per_if = count / n_int
+ i = -1
+ for pg_if in cls.pg_interfaces:
+ i += 1
+ start_nr = macs_per_if * i
+ end_nr = count if i == (n_int - 1) else macs_per_if * (i + 1)
+ cls.hosts_by_pg_idx[pg_if.sw_if_index] = []
+ hosts = cls.hosts_by_pg_idx[pg_if.sw_if_index]
+ packets = []
+ for j in range(start_nr, end_nr):
+ host = Host(
+ "00:00:00:ff:%02x:%02x" % (pg_if.sw_if_index, j),
+ "172.17.1%02x.%u" % (pg_if.sw_if_index, j))
+ packet = (Ether(dst="ff:ff:ff:ff:ff:ff", src=host.mac))
+ hosts.append(host)
+ if hasattr(pg_if, 'sub_if'):
+ packet = pg_if.sub_if.add_dot1_layer(packet)
+ packets.append(packet)
+ pg_if.add_stream(packets)
+ cls.logger.info("Sending broadcast eth frames for MAC learning")
+ cls.pg_start()
+
+ def create_stream(self, src_if, packet_sizes, packets_per_burst):
+ """
+ Create input packet stream for defined interface.
+
+ :param object src_if: Interface to create packet stream for.
+ :param list packet_sizes: List of required packet sizes.
+ :param int packets_per_burst: Number of packets in burst.
+ :return: Stream of packets.
+ """
+ pkts = []
+ for i in range(0, packets_per_burst):
+ dst_if = self.flows[src_if][i % 2]
+ dst_host = random.choice(self.hosts_by_pg_idx[dst_if.sw_if_index])
+ src_host = random.choice(self.hosts_by_pg_idx[src_if.sw_if_index])
+ pkt_info = self.create_packet_info(src_if, dst_if)
+ payload = self.info_to_payload(pkt_info)
+ p = (Ether(dst=dst_host.mac, src=src_host.mac) /
+ IP(src=src_host.ip4, dst=dst_host.ip4) /
+ UDP(sport=1234, dport=1234) /
+ Raw(payload))
+ pkt_info.data = p.copy()
+ if hasattr(src_if, 'sub_if'):
+ p = src_if.sub_if.add_dot1_layer(p)
+ size = random.choice(packet_sizes)
+ self.extend_packet(p, size)
+ pkts.append(p)
+ return pkts
+
+ def verify_capture(self, pg_if, capture):
+ """
+ Verify captured input packet stream for defined interface.
+
+ :param object pg_if: Interface to verify captured packet stream for.
+ :param list capture: Captured packet stream.
+ """
+ last_info = dict()
+ for i in self.pg_interfaces:
+ last_info[i.sw_if_index] = None
+ dst_sw_if_index = pg_if.sw_if_index
+ for packet in capture:
+ payload_info = self.payload_to_info(str(packet[Raw]))
+ src_sw_if_index = payload_info.src
+ src_if = None
+ for ifc in self.pg_interfaces:
+ if ifc != pg_if:
+ if ifc.sw_if_index == src_sw_if_index:
+ src_if = ifc
+ break
+ if hasattr(src_if, 'sub_if'):
+ # Check VLAN tags and Ethernet header
+ packet = src_if.sub_if.remove_dot1_layer(packet)
+ self.assertTrue(Dot1Q not in packet)
+ try:
+ ip = packet[IP]
+ udp = packet[UDP]
+ packet_index = payload_info.index
+ self.assertEqual(payload_info.dst, dst_sw_if_index)
+ self.logger.debug("Got packet on port %s: src=%u (id=%u)" %
+ (pg_if.name, payload_info.src, packet_index))
+ next_info = self.get_next_packet_info_for_interface2(
+ payload_info.src, dst_sw_if_index,
+ last_info[payload_info.src])
+ last_info[payload_info.src] = next_info
+ self.assertTrue(next_info is not None)
+ self.assertEqual(packet_index, next_info.index)
+ saved_packet = next_info.data
+ # Check standard fields
+ self.assertEqual(ip.src, saved_packet[IP].src)
+ self.assertEqual(ip.dst, saved_packet[IP].dst)
+ self.assertEqual(udp.sport, saved_packet[UDP].sport)
+ self.assertEqual(udp.dport, saved_packet[UDP].dport)
+ except:
+ self.logger.error(ppp("Unexpected or invalid packet:", packet))
+ raise
+ for i in self.pg_interfaces:
+ remaining_packet = self.get_next_packet_info_for_interface2(
+ i, dst_sw_if_index, last_info[i.sw_if_index])
+ self.assertTrue(
+ remaining_packet is None,
+ "Port %u: Packet expected from source %u didn't arrive" %
+ (dst_sw_if_index, i.sw_if_index))
+
+ def run_l2bd_test(self, pkts_per_burst):
+ """ L2BD MAC learning test """
+
+ # Create incoming packet streams for packet-generator interfaces
+ for i in self.pg_interfaces:
+ packet_sizes = self.sub_if_packet_sizes if hasattr(i, 'sub_if') \
+ else self.pg_if_packet_sizes
+ pkts = self.create_stream(i, packet_sizes, pkts_per_burst)
+ i.add_stream(pkts)
+
+ # Enable packet capture and start packet sending
+ self.pg_enable_capture(self.pg_interfaces)
+ self.pg_start()
+
+ # Verify outgoing packet streams per packet-generator interface
+ for i in self.pg_interfaces:
+ capture = i.get_capture()
+ self.logger.info("Verifying capture on interface %s" % i.name)
+ self.verify_capture(i, capture)
+
+ def test_l2bd_sl(self):
+ """ L2BD MAC learning single-loop test
+
+ Test scenario:
+ 1.config
+ MAC learning enabled
+ learn 100 MAC enries
+ 3 interfaces: untagged, dot1q, dot1ad (dot1q used instead of
+ dot1ad in the first version)
+
+ 2.sending l2 eth pkts between 3 interface
+ 64B, 512B, 1518B, 9200B (ether_size)
+ burst of 2 pkts per interface
+ """
+
+ self.run_l2bd_test(self.sl_pkts_per_burst)
+
+ def test_l2bd_dl(self):
+ """ L2BD MAC learning dual-loop test
+
+ Test scenario:
+ 1.config
+ MAC learning enabled
+ learn 100 MAC enries
+ 3 interfaces: untagged, dot1q, dot1ad (dot1q used instead of
+ dot1ad in the first version)
+
+ 2.sending l2 eth pkts between 3 interface
+ 64B, 512B, 1518B, 9200B (ether_size)
+ burst of 257 pkts per interface
+ """
+
+ self.run_l2bd_test(self.dl_pkts_per_burst)
+
+
+if __name__ == '__main__':
+ unittest.main(testRunner=VppTestRunner)
diff --git a/test/test_l2bd_arp_term.py b/test/test_l2bd_arp_term.py
new file mode 100644
index 00000000..20cc537e
--- /dev/null
+++ b/test/test_l2bd_arp_term.py
@@ -0,0 +1,492 @@
+#!/usr/bin/env python
+""" L2BD ARP term Test """
+
+import unittest
+import random
+import copy
+
+from socket import AF_INET, AF_INET6
+
+from scapy.packet import Raw
+from scapy.layers.l2 import Ether, ARP
+from scapy.layers.inet import IP
+from scapy.utils import inet_pton, inet_ntop
+from scapy.utils6 import in6_getnsma, in6_getnsmac, in6_ptop, in6_islladdr, \
+ in6_mactoifaceid, in6_ismaddr
+from scapy.layers.inet6 import IPv6, UDP, ICMPv6ND_NS, ICMPv6ND_RS, \
+ ICMPv6ND_RA, ICMPv6NDOptSrcLLAddr, getmacbyip6, ICMPv6MRD_Solicitation, \
+ ICMPv6NDOptMTU, ICMPv6NDOptSrcLLAddr, ICMPv6NDOptPrefixInfo, \
+ ICMPv6ND_NA, ICMPv6NDOptDstLLAddr, ICMPv6DestUnreach, icmp6types
+
+from framework import VppTestCase, VppTestRunner
+from util import Host, ppp, mactobinary
+
+
+class TestL2bdArpTerm(VppTestCase):
+ """ L2BD arp termination Test Case """
+
+ @classmethod
+ def setUpClass(cls):
+ """
+ Perform standard class setup (defined by class method setUpClass in
+ class VppTestCase) before running the test case, set test case related
+ variables and configure VPP.
+ """
+ super(TestL2bdArpTerm, cls).setUpClass()
+
+ try:
+ # Create pg interfaces
+ n_bd = 1
+ cls.ifs_per_bd = ifs_per_bd = 3
+ n_ifs = n_bd * ifs_per_bd
+ cls.create_pg_interfaces(range(n_ifs))
+
+ # Set up all interfaces
+ for i in cls.pg_interfaces:
+ i.admin_up()
+
+ cls.hosts = set()
+
+ except Exception:
+ super(TestL2bdArpTerm, cls).tearDownClass()
+ raise
+
+ def setUp(self):
+ """
+ Clear trace and packet infos before running each test.
+ """
+ self.reset_packet_infos()
+ super(TestL2bdArpTerm, self).setUp()
+
+ def tearDown(self):
+ """
+ Show various debug prints after each test.
+ """
+ super(TestL2bdArpTerm, self).tearDown()
+ if not self.vpp_dead:
+ self.logger.info(self.vapi.ppcli("show l2fib verbose"))
+ self.logger.info(self.vapi.ppcli("show bridge-domain 1 detail"))
+
+ def add_del_arp_term_hosts(self, entries, bd_id=1, is_add=1, is_ipv6=0):
+ for e in entries:
+ ip = e.ip4n if is_ipv6 == 0 else e.ip6n
+ self.vapi.bd_ip_mac_add_del(bd_id=bd_id,
+ mac=e.bin_mac,
+ ip=ip,
+ is_ipv6=is_ipv6,
+ is_add=is_add)
+
+ @classmethod
+ def mac_list(cls, b6_range):
+ return ["00:00:ca:fe:00:%02x" % b6 for b6 in b6_range]
+
+ @classmethod
+ def ip4_host(cls, subnet, host, mac):
+ return Host(mac=mac,
+ ip4="172.17.1%02u.%u" % (subnet, host))
+
+ @classmethod
+ def ip4_hosts(cls, subnet, start, mac_list):
+ return {cls.ip4_host(subnet, start + j, mac_list[j])
+ for j in range(len(mac_list))}
+
+ @classmethod
+ def ip6_host(cls, subnet, host, mac):
+ return Host(mac=mac,
+ ip6="fd01:%x::%x" % (subnet, host))
+
+ @classmethod
+ def ip6_hosts(cls, subnet, start, mac_list):
+ return {cls.ip6_host(subnet, start + j, mac_list[j])
+ for j in range(len(mac_list))}
+
+ @classmethod
+ def bd_swifs(cls, b):
+ n = cls.ifs_per_bd
+ start = (b - 1) * n
+ return [cls.pg_interfaces[j] for j in range(start, start + n)]
+
+ def bd_add_del(self, bd_id=1, is_add=1):
+ if is_add:
+ self.vapi.bridge_domain_add_del(bd_id=bd_id, is_add=is_add)
+ for swif in self.bd_swifs(bd_id):
+ swif_idx = swif.sw_if_index
+ self.vapi.sw_interface_set_l2_bridge(
+ swif_idx, bd_id=bd_id, enable=is_add)
+ if not is_add:
+ self.vapi.bridge_domain_add_del(bd_id=bd_id, is_add=is_add)
+
+ @classmethod
+ def arp_req(cls, src_host, host):
+ return (Ether(dst="ff:ff:ff:ff:ff:ff", src=src_host.mac) /
+ ARP(op="who-has",
+ hwsrc=src_host.bin_mac,
+ pdst=host.ip4,
+ psrc=src_host.ip4))
+
+ @classmethod
+ def arp_reqs(cls, src_host, entries):
+ return [cls.arp_req(src_host, e) for e in entries]
+
+ @classmethod
+ def garp_req(cls, host):
+ return cls.arp_req(host, host)
+
+ @classmethod
+ def garp_reqs(cls, entries):
+ return [cls.garp_req(e) for e in entries]
+
+ def arp_resp_host(self, src_host, arp_resp):
+ ether = arp_resp[Ether]
+ self.assertEqual(ether.dst, src_host.mac)
+
+ arp = arp_resp[ARP]
+ self.assertEqual(arp.hwtype, 1)
+ self.assertEqual(arp.ptype, 0x800)
+ self.assertEqual(arp.hwlen, 6)
+ self.assertEqual(arp.plen, 4)
+ arp_opts = {"who-has": 1, "is-at": 2}
+ self.assertEqual(arp.op, arp_opts["is-at"])
+ self.assertEqual(arp.hwdst, src_host.mac)
+ self.assertEqual(arp.pdst, src_host.ip4)
+ return Host(mac=arp.hwsrc, ip4=arp.psrc)
+
+ def arp_resp_hosts(self, src_host, pkts):
+ return {self.arp_resp_host(src_host, p) for p in pkts}
+
+ def inttoip4(self, ip):
+ o1 = int(ip / 16777216) % 256
+ o2 = int(ip / 65536) % 256
+ o3 = int(ip / 256) % 256
+ o4 = int(ip) % 256
+ return '%(o1)s.%(o2)s.%(o3)s.%(o4)s' % locals()
+
+ def arp_event_host(self, e):
+ return Host(mac=':'.join(['%02x' % ord(char) for char in e.new_mac]),
+ ip4=self.inttoip4(e.address))
+
+ def arp_event_hosts(self, evs):
+ return {self.arp_event_host(e) for e in evs}
+
+ def nd_event_host(self, e):
+ return Host(mac=':'.join(['%02x' % ord(char) for char in e.new_mac]),
+ ip6=inet_ntop(AF_INET6, e.address))
+
+ def nd_event_hosts(self, evs):
+ return {self.nd_event_host(e) for e in evs}
+
+ @classmethod
+ def ns_req(cls, src_host, host):
+ nsma = in6_getnsma(inet_pton(AF_INET6, "fd10::ffff"))
+ d = inet_ntop(AF_INET6, nsma)
+ return (Ether(dst="ff:ff:ff:ff:ff:ff", src=src_host.mac) /
+ IPv6(dst=d, src=src_host.ip6) /
+ ICMPv6ND_NS(tgt=host.ip6) /
+ ICMPv6NDOptSrcLLAddr(lladdr=src_host.mac))
+
+ @classmethod
+ def ns_reqs_dst(cls, entries, dst_host):
+ return [cls.ns_req(e, dst_host) for e in entries]
+
+ @classmethod
+ def ns_reqs_src(cls, src_host, entries):
+ return [cls.ns_req(src_host, e) for e in entries]
+
+ def na_resp_host(self, src_host, rx):
+ self.assertEqual(rx[Ether].dst, src_host.mac)
+ self.assertEqual(in6_ptop(rx[IPv6].dst),
+ in6_ptop(src_host.ip6))
+
+ self.assertTrue(rx.haslayer(ICMPv6ND_NA))
+ self.assertTrue(rx.haslayer(ICMPv6NDOptDstLLAddr))
+
+ na = rx[ICMPv6ND_NA]
+ return Host(mac=na.lladdr, ip6=na.tgt)
+
+ def na_resp_hosts(self, src_host, pkts):
+ return {self.na_resp_host(src_host, p) for p in pkts}
+
+ def set_bd_flags(self, bd_id, **args):
+ """
+ Enable/disable defined feature(s) of the bridge domain.
+
+ :param int bd_id: Bridge domain ID.
+ :param list args: List of feature/status pairs. Allowed features: \
+ learn, forward, flood, uu_flood and arp_term. Status False means \
+ disable, status True means enable the feature.
+ :raise: ValueError in case of unknown feature in the input.
+ """
+ for flag in args:
+ if flag == "learn":
+ feature_bitmap = 1 << 0
+ elif flag == "forward":
+ feature_bitmap = 1 << 1
+ elif flag == "flood":
+ feature_bitmap = 1 << 2
+ elif flag == "uu_flood":
+ feature_bitmap = 1 << 3
+ elif flag == "arp_term":
+ feature_bitmap = 1 << 4
+ else:
+ raise ValueError("Unknown feature used: %s" % flag)
+ is_set = 1 if args[flag] else 0
+ self.vapi.bridge_flags(bd_id, is_set, feature_bitmap)
+ self.logger.info("Bridge domain ID %d updated" % bd_id)
+
+ def verify_arp(self, src_host, req_hosts, resp_hosts, bd_id=1):
+ reqs = self.arp_reqs(src_host, req_hosts)
+
+ for swif in self.bd_swifs(bd_id):
+ swif.add_stream(reqs)
+
+ self.pg_enable_capture(self.pg_interfaces)
+ self.pg_start()
+
+ for swif in self.bd_swifs(bd_id):
+ resp_pkts = swif.get_capture(len(resp_hosts))
+ resps = self.arp_resp_hosts(src_host, resp_pkts)
+ self.assertEqual(len(resps ^ resp_hosts), 0)
+
+ def verify_nd(self, src_host, req_hosts, resp_hosts, bd_id=1):
+ reqs = self.ns_reqs_src(src_host, req_hosts)
+
+ for swif in self.bd_swifs(bd_id):
+ swif.add_stream(reqs)
+
+ self.pg_enable_capture(self.pg_interfaces)
+ self.pg_start()
+
+ for swif in self.bd_swifs(bd_id):
+ resp_pkts = swif.get_capture(len(resp_hosts))
+ resps = self.na_resp_hosts(src_host, resp_pkts)
+ self.assertEqual(len(resps ^ resp_hosts), 0)
+
+ def test_l2bd_arp_term_01(self):
+ """ L2BD arp term - add 5 hosts, verify arp responses
+ """
+ src_host = self.ip4_host(50, 50, "00:00:11:22:33:44")
+ self.bd_add_del(1, is_add=1)
+ self.set_bd_flags(1, arp_term=True, flood=False,
+ uu_flood=False, learn=False)
+ macs = self.mac_list(range(1, 5))
+ hosts = self.ip4_hosts(4, 1, macs)
+ self.add_del_arp_term_hosts(hosts, is_add=1)
+ self.verify_arp(src_host, hosts, hosts)
+ type(self).hosts = hosts
+
+ def test_l2bd_arp_term_02(self):
+ """ L2BD arp term - delete 3 hosts, verify arp responses
+ """
+ src_host = self.ip4_host(50, 50, "00:00:11:22:33:44")
+ macs = self.mac_list(range(1, 3))
+ deleted = self.ip4_hosts(4, 1, macs)
+ self.add_del_arp_term_hosts(deleted, is_add=0)
+ remaining = self.hosts - deleted
+ self.verify_arp(src_host, self.hosts, remaining)
+ type(self).hosts = remaining
+ self.bd_add_del(1, is_add=0)
+
+ def test_l2bd_arp_term_03(self):
+ """ L2BD arp term - recreate BD1, readd 3 hosts, verify arp responses
+ """
+ src_host = self.ip4_host(50, 50, "00:00:11:22:33:44")
+ self.bd_add_del(1, is_add=1)
+ self.set_bd_flags(1, arp_term=True, flood=False,
+ uu_flood=False, learn=False)
+ macs = self.mac_list(range(1, 3))
+ readded = self.ip4_hosts(4, 1, macs)
+ self.add_del_arp_term_hosts(readded, is_add=1)
+ self.verify_arp(src_host, self.hosts | readded, readded)
+ type(self).hosts = readded
+
+ def test_l2bd_arp_term_04(self):
+ """ L2BD arp term - 2 IP4 addrs per host
+ """
+ src_host = self.ip4_host(50, 50, "00:00:11:22:33:44")
+ macs = self.mac_list(range(1, 3))
+ sub5_hosts = self.ip4_hosts(5, 1, macs)
+ self.add_del_arp_term_hosts(sub5_hosts, is_add=1)
+ hosts = self.hosts | sub5_hosts
+ self.verify_arp(src_host, hosts, hosts)
+ type(self).hosts = hosts
+ self.bd_add_del(1, is_add=0)
+
+ def test_l2bd_arp_term_05(self):
+ """ L2BD arp term - create and update 10 IP4-mac pairs
+ """
+ src_host = self.ip4_host(50, 50, "00:00:11:22:33:44")
+ self.bd_add_del(1, is_add=1)
+ self.set_bd_flags(1, arp_term=True, flood=False,
+ uu_flood=False, learn=False)
+ macs1 = self.mac_list(range(10, 20))
+ hosts1 = self.ip4_hosts(5, 1, macs1)
+ self.add_del_arp_term_hosts(hosts1, is_add=1)
+ self.verify_arp(src_host, hosts1, hosts1)
+ macs2 = self.mac_list(range(20, 30))
+ hosts2 = self.ip4_hosts(5, 1, macs2)
+ self.add_del_arp_term_hosts(hosts2, is_add=1)
+ self.verify_arp(src_host, hosts1, hosts2)
+ self.bd_add_del(1, is_add=0)
+
+ def test_l2bd_arp_term_06(self):
+ """ L2BD arp/ND term - hosts with both ip4/ip6
+ """
+ src_host4 = self.ip4_host(50, 50, "00:00:11:22:33:44")
+ src_host6 = self.ip6_host(50, 50, "00:00:11:22:33:44")
+ self.bd_add_del(1, is_add=1)
+ # enable flood to make sure requests are not flooded
+ self.set_bd_flags(1, arp_term=True, flood=True,
+ uu_flood=False, learn=False)
+ macs = self.mac_list(range(10, 20))
+ hosts6 = self.ip6_hosts(5, 1, macs)
+ hosts4 = self.ip4_hosts(5, 1, macs)
+ self.add_del_arp_term_hosts(hosts4, is_add=1)
+ self.add_del_arp_term_hosts(hosts6, is_add=1, is_ipv6=1)
+ self.verify_arp(src_host4, hosts4, hosts4)
+ self.verify_nd(src_host6, hosts6, hosts6)
+ self.bd_add_del(1, is_add=0)
+
+ def test_l2bd_arp_term_07(self):
+ """ L2BD ND term - Add and Del hosts, verify ND replies
+ """
+ src_host6 = self.ip6_host(50, 50, "00:00:11:22:33:44")
+ self.bd_add_del(1, is_add=1)
+ self.set_bd_flags(1, arp_term=True, flood=False,
+ uu_flood=False, learn=False)
+ macs = self.mac_list(range(10, 20))
+ hosts6 = self.ip6_hosts(5, 1, macs)
+ self.add_del_arp_term_hosts(hosts6, is_add=1, is_ipv6=1)
+ self.verify_nd(src_host6, hosts6, hosts6)
+ del_macs = self.mac_list(range(10, 15))
+ deleted = self.ip6_hosts(5, 1, del_macs)
+ self.add_del_arp_term_hosts(deleted, is_add=0, is_ipv6=1)
+ self.verify_nd(src_host6, hosts6, hosts6 - deleted)
+ self.bd_add_del(1, is_add=0)
+
+ def test_l2bd_arp_term_08(self):
+ """ L2BD ND term - Add and update IP+mac, verify ND replies
+ """
+ src_host = self.ip6_host(50, 50, "00:00:11:22:33:44")
+ self.bd_add_del(1, is_add=1)
+ self.set_bd_flags(1, arp_term=True, flood=False,
+ uu_flood=False, learn=False)
+ macs1 = self.mac_list(range(10, 20))
+ hosts = self.ip6_hosts(5, 1, macs1)
+ self.add_del_arp_term_hosts(hosts, is_add=1, is_ipv6=1)
+ self.verify_nd(src_host, hosts, hosts)
+ macs2 = self.mac_list(range(20, 30))
+ updated = self.ip6_hosts(5, 1, macs2)
+ self.add_del_arp_term_hosts(updated, is_add=1, is_ipv6=1)
+ self.verify_nd(src_host, hosts, updated)
+ self.bd_add_del(1, is_add=0)
+
+ def test_l2bd_arp_term_09(self):
+ """ L2BD arp term - send garps, verify arp event reports
+ """
+ self.vapi.want_ip4_arp_events()
+ self.bd_add_del(1, is_add=1)
+ self.set_bd_flags(1, arp_term=True, flood=False,
+ uu_flood=False, learn=False)
+ macs = self.mac_list(range(90, 95))
+ hosts = self.ip4_hosts(5, 1, macs)
+
+ garps = self.garp_reqs(hosts)
+ self.bd_swifs(1)[0].add_stream(garps)
+
+ self.pg_enable_capture(self.pg_interfaces)
+ self.pg_start()
+ evs = [self.vapi.wait_for_event(1, "ip4_arp_event")
+ for i in range(len(hosts))]
+ ev_hosts = self.arp_event_hosts(evs)
+ self.assertEqual(len(ev_hosts ^ hosts), 0)
+
+ def test_l2bd_arp_term_10(self):
+ """ L2BD arp term - send duplicate garps, verify suppression
+ """
+ macs = self.mac_list(range(70, 71))
+ hosts = self.ip4_hosts(6, 1, macs)
+
+ """ send the packet 5 times expect one event
+ """
+ garps = self.garp_reqs(hosts) * 5
+ self.bd_swifs(1)[0].add_stream(garps)
+
+ self.pg_enable_capture(self.pg_interfaces)
+ self.pg_start()
+ evs = [self.vapi.wait_for_event(1, "ip4_arp_event")
+ for i in range(len(hosts))]
+ ev_hosts = self.arp_event_hosts(evs)
+ self.assertEqual(len(ev_hosts ^ hosts), 0)
+
+ def test_l2bd_arp_term_11(self):
+ """ L2BD arp term - disable ip4 arp events,send garps, verify no events
+ """
+ self.vapi.want_ip4_arp_events(enable_disable=0)
+ macs = self.mac_list(range(90, 95))
+ hosts = self.ip4_hosts(5, 1, macs)
+
+ garps = self.garp_reqs(hosts)
+ self.bd_swifs(1)[0].add_stream(garps)
+
+ self.pg_enable_capture(self.pg_interfaces)
+ self.pg_start()
+ self.sleep(1)
+ self.assertEqual(len(self.vapi.collect_events()), 0)
+ self.bd_add_del(1, is_add=0)
+
+ def test_l2bd_arp_term_12(self):
+ """ L2BD ND term - send NS packets verify reports
+ """
+ self.vapi.want_ip6_nd_events(address=inet_pton(AF_INET6, "::0"))
+ dst_host = self.ip6_host(50, 50, "00:00:11:22:33:44")
+ self.bd_add_del(1, is_add=1)
+ self.set_bd_flags(1, arp_term=True, flood=False,
+ uu_flood=False, learn=False)
+ macs = self.mac_list(range(10, 15))
+ hosts = self.ip6_hosts(5, 1, macs)
+ reqs = self.ns_reqs_dst(hosts, dst_host)
+ self.bd_swifs(1)[0].add_stream(reqs)
+
+ self.pg_enable_capture(self.pg_interfaces)
+ self.pg_start()
+ evs = [self.vapi.wait_for_event(2, "ip6_nd_event")
+ for i in range(len(hosts))]
+ ev_hosts = self.nd_event_hosts(evs)
+ self.assertEqual(len(ev_hosts ^ hosts), 0)
+
+ def test_l2bd_arp_term_13(self):
+ """ L2BD ND term - send duplicate ns, verify suppression
+ """
+ dst_host = self.ip6_host(50, 50, "00:00:11:22:33:44")
+ macs = self.mac_list(range(10, 11))
+ hosts = self.ip6_hosts(5, 1, macs)
+ reqs = self.ns_reqs_dst(hosts, dst_host) * 5
+ self.bd_swifs(1)[0].add_stream(reqs)
+
+ self.pg_enable_capture(self.pg_interfaces)
+ self.pg_start()
+ evs = [self.vapi.wait_for_event(2, "ip6_nd_event")
+ for i in range(len(hosts))]
+ ev_hosts = self.nd_event_hosts(evs)
+ self.assertEqual(len(ev_hosts ^ hosts), 0)
+
+ def test_l2bd_arp_term_14(self):
+ """ L2BD ND term - disable ip4 arp events,send ns, verify no events
+ """
+ self.vapi.want_ip6_nd_events(enable_disable=0,
+ address=inet_pton(AF_INET6, "::0"))
+ dst_host = self.ip6_host(50, 50, "00:00:11:22:33:44")
+ macs = self.mac_list(range(10, 15))
+ hosts = self.ip6_hosts(5, 1, macs)
+ reqs = self.ns_reqs_dst(hosts, dst_host)
+ self.bd_swifs(1)[0].add_stream(reqs)
+
+ self.pg_enable_capture(self.pg_interfaces)
+ self.pg_start()
+ self.sleep(1)
+ self.assertEqual(len(self.vapi.collect_events()), 0)
+ self.bd_add_del(1, is_add=0)
+
+
+if __name__ == '__main__':
+ unittest.main(testRunner=VppTestRunner)
diff --git a/test/test_l2bd_multi_instance.py b/test/test_l2bd_multi_instance.py
new file mode 100644
index 00000000..04ba570c
--- /dev/null
+++ b/test/test_l2bd_multi_instance.py
@@ -0,0 +1,479 @@
+#!/usr/bin/env python
+"""L2BD Multi-instance Test Case HLD:
+
+**NOTES:**
+ - higher number of pg-l2 interfaces causes problems => only 15 pg-l2 \
+ interfaces in 5 bridge domains are tested
+ - jumbo packets in configuration with 14 l2-pg interfaces leads to \
+ problems too
+
+**config 1**
+ - add 15 pg-l2 interfaces
+ - configure one host per pg-l2 interface
+ - configure 5 bridge domains (BD)
+ - add 3 pg-l2 interfaces per BD
+
+**test 1**
+ - send L2 MAC frames between all pg-l2 interfaces of all BDs
+
+**verify 1**
+ - check BD data by parsing output of bridge_domain_dump API command
+ - all packets received correctly
+
+**config 2**
+ - update data of 5 BD
+ - disable learning, forwarding, flooding and uu_flooding for BD1
+ - disable forwarding for BD2
+ - disable flooding for BD3
+ - disable uu_flooding for BD4
+ - disable learning for BD5
+
+**verify 2**
+ - check BD data by parsing output of bridge_domain_dump API command
+
+**config 3**
+ - delete 2 BDs
+
+**test 3**
+ - send L2 MAC frames between all pg-l2 interfaces of all BDs
+ - send L2 MAC frames between all pg-l2 interfaces formerly assigned to \
+ deleted BDs
+
+**verify 3**
+ - check BD data by parsing output of bridge_domain_dump API command
+ - all packets received correctly on all 3 pg-l2 interfaces assigned to BDs
+ - no packet received on all 3 pg-l2 interfaces of all deleted BDs
+
+**config 4**
+ - add 2 BDs
+ - add 3 pg-l2 interfaces per BD
+
+**test 4**
+ - send L2 MAC frames between all pg-l2 interfaces of all BDs
+
+**verify 4**
+ - check BD data by parsing output of bridge_domain_dump API command
+ - all packets received correctly
+
+**config 5**
+ - delete 5 BDs
+
+**verify 5**
+ - check BD data by parsing output of bridge_domain_dump API command
+"""
+
+import unittest
+import random
+
+from scapy.packet import Raw
+from scapy.layers.l2 import Ether
+from scapy.layers.inet import IP, UDP
+
+from framework import VppTestCase, VppTestRunner, running_extended_tests
+from util import Host, ppp
+
+
+class TestL2bdMultiInst(VppTestCase):
+ """ L2BD Multi-instance Test Case """
+
+ @classmethod
+ def setUpClass(cls):
+ """
+ Perform standard class setup (defined by class method setUpClass in
+ class VppTestCase) before running the test case, set test case related
+ variables and configure VPP.
+ """
+ super(TestL2bdMultiInst, cls).setUpClass()
+
+ try:
+ # Create pg interfaces
+ n_bd = 5
+ cls.ifs_per_bd = ifs_per_bd = 3
+ n_ifs = n_bd * ifs_per_bd
+ cls.create_pg_interfaces(range(n_ifs))
+
+ # Packet flows mapping pg0 -> pg1, pg2 etc.
+ cls.flows = dict()
+ for b in range(n_bd):
+ bd_ifs = cls.bd_if_range(b + 1)
+ for j in bd_ifs:
+ cls.flows[cls.pg_interfaces[j]] = [
+ cls.pg_interfaces[x] for x in bd_ifs if x != j]
+ assert(
+ len(cls.flows[cls.pg_interfaces[j]]) == ifs_per_bd - 1)
+
+ # Mapping between packet-generator index and lists of test hosts
+ cls.hosts_by_pg_idx = dict()
+
+ # Create test host entries
+ cls.create_hosts(5)
+
+ # Packet sizes - jumbo packet (9018 bytes) skipped
+ cls.pg_if_packet_sizes = [64, 512, 1518]
+
+ # Set up all interfaces
+ for i in cls.pg_interfaces:
+ i.admin_up()
+
+ # Create list of BDs
+ cls.bd_list = list()
+
+ # Create list of deleted BDs
+ cls.bd_deleted_list = list()
+
+ # Create list of pg_interfaces in BDs
+ cls.pg_in_bd = list()
+
+ except Exception:
+ super(TestL2bdMultiInst, cls).tearDownClass()
+ raise
+
+ def setUp(self):
+ """
+ Clear trace and packet infos before running each test.
+ """
+ self.reset_packet_infos()
+ super(TestL2bdMultiInst, self).setUp()
+
+ def tearDown(self):
+ """
+ Show various debug prints after each test.
+ """
+ super(TestL2bdMultiInst, self).tearDown()
+ if not self.vpp_dead:
+ self.logger.info(self.vapi.ppcli("show l2fib verbose"))
+ self.logger.info(self.vapi.ppcli("show bridge-domain"))
+
+ @classmethod
+ def create_hosts(cls, hosts_per_if):
+ """
+ Create required number of host MAC addresses and distribute them
+ among interfaces. Create host IPv4 address for every host MAC
+ address.
+
+ :param int hosts_per_if: Number of hosts per if to create MAC/IPv4
+ addresses for.
+ """
+ c = hosts_per_if
+ assert(not cls.hosts_by_pg_idx)
+ for i in range(len(cls.pg_interfaces)):
+ pg_idx = cls.pg_interfaces[i].sw_if_index
+ cls.hosts_by_pg_idx[pg_idx] = [Host(
+ "00:00:00:ff:%02x:%02x" % (pg_idx, j + 1),
+ "172.17.1%02u.%u" % (pg_idx, j + 1)) for j in range(c)]
+
+ @classmethod
+ def bd_if_range(cls, b):
+ n = cls.ifs_per_bd
+ start = (b - 1) * n
+ return range(start, start + n)
+
+ def create_bd_and_mac_learn(self, count, start=1):
+ """
+ Create required number of bridge domains with MAC learning enabled,
+ put 3 l2-pg interfaces to every bridge domain and send MAC learning
+ packets.
+
+ :param int count: Number of bridge domains to be created.
+ :param int start: Starting number of the bridge domain ID.
+ (Default value = 1)
+ """
+ for b in range(start, start + count):
+ self.vapi.bridge_domain_add_del(bd_id=b)
+ self.logger.info("Bridge domain ID %d created" % b)
+ if self.bd_list.count(b) == 0:
+ self.bd_list.append(b)
+ if self.bd_deleted_list.count(b) == 1:
+ self.bd_deleted_list.remove(b)
+ for j in self.bd_if_range(b):
+ pg_if = self.pg_interfaces[j]
+ self.vapi.sw_interface_set_l2_bridge(pg_if.sw_if_index,
+ bd_id=b)
+ self.logger.info("pg-interface %s added to bridge domain ID %d"
+ % (pg_if.name, b))
+ self.pg_in_bd.append(pg_if)
+ hosts = self.hosts_by_pg_idx[pg_if.sw_if_index]
+ packets = [Ether(dst="ff:ff:ff:ff:ff:ff", src=host.mac)
+ for host in hosts]
+ pg_if.add_stream(packets)
+ self.logger.info("Sending broadcast eth frames for MAC learning")
+ self.pg_start()
+ self.logger.info(self.vapi.ppcli("show bridge-domain"))
+ self.logger.info(self.vapi.ppcli("show l2fib"))
+
+ def delete_bd(self, count, start=1):
+ """
+ Delete required number of bridge domains.
+
+ :param int count: Number of bridge domains to be created.
+ :param int start: Starting number of the bridge domain ID.
+ (Default value = 1)
+ """
+ for b in range(start, start + count):
+ for j in self.bd_if_range(b):
+ pg_if = self.pg_interfaces[j]
+ self.vapi.sw_interface_set_l2_bridge(pg_if.sw_if_index,
+ bd_id=b, enable=0)
+ self.pg_in_bd.remove(pg_if)
+ self.vapi.bridge_domain_add_del(bd_id=b, is_add=0)
+ self.bd_list.remove(b)
+ self.bd_deleted_list.append(b)
+ self.logger.info("Bridge domain ID %d deleted" % b)
+
+ def create_stream(self, src_if):
+ """
+ Create input packet stream for defined interface using hosts list.
+
+ :param object src_if: Interface to create packet stream for.
+ :param list packet_sizes: List of required packet sizes.
+ :return: Stream of packets.
+ """
+ packet_sizes = self.pg_if_packet_sizes
+ pkts = []
+ src_hosts = self.hosts_by_pg_idx[src_if.sw_if_index]
+ for dst_if in self.flows[src_if]:
+ dst_hosts = self.hosts_by_pg_idx[dst_if.sw_if_index]
+ for dst_host in dst_hosts:
+ pkt_info = self.create_packet_info(src_if, dst_if)
+ payload = self.info_to_payload(pkt_info)
+ src_host = random.choice(src_hosts)
+ p = (Ether(dst=dst_host.mac, src=src_host.mac) /
+ IP(src=src_host.ip4, dst=dst_host.ip4) /
+ UDP(sport=1234, dport=1234) /
+ Raw(payload))
+ pkt_info.data = p.copy()
+ size = random.choice(packet_sizes)
+ self.extend_packet(p, size)
+ pkts.append(p)
+ self.logger.debug("Input stream created for port %s. Length: %u pkt(s)"
+ % (src_if.name, len(pkts)))
+ return pkts
+
+ def verify_capture(self, dst_if):
+ """
+ Verify captured input packet stream for defined interface.
+
+ :param object dst_if: Interface to verify captured packet stream for.
+ """
+ last_info = dict()
+ for i in self.flows[dst_if]:
+ last_info[i.sw_if_index] = None
+ dst = dst_if.sw_if_index
+ for packet in dst_if.get_capture():
+ try:
+ ip = packet[IP]
+ udp = packet[UDP]
+ info = self.payload_to_info(str(packet[Raw]))
+ self.assertEqual(info.dst, dst)
+ self.logger.debug("Got packet on port %s: src=%u (id=%u)" %
+ (dst_if.name, info.src, info.index))
+ last_info[info.src] = self.get_next_packet_info_for_interface2(
+ info.src, dst, last_info[info.src])
+ pkt_info = last_info[info.src]
+ self.assertTrue(pkt_info is not None)
+ self.assertEqual(info.index, pkt_info.index)
+ # Check standard fields against saved data in pkt
+ saved = pkt_info.data
+ self.assertEqual(ip.src, saved[IP].src)
+ self.assertEqual(ip.dst, saved[IP].dst)
+ self.assertEqual(udp.sport, saved[UDP].sport)
+ self.assertEqual(udp.dport, saved[UDP].dport)
+ except:
+ self.logger.error(ppp("Unexpected or invalid packet:", packet))
+ raise
+ s = ""
+ remaining = 0
+ for src in self.flows[dst_if]:
+ remaining_packet = self.get_next_packet_info_for_interface2(
+ src.sw_if_index, dst, last_info[src.sw_if_index])
+ if remaining_packet is None:
+ s += "Port %u: Packet expected from source %u didn't arrive\n"\
+ % (dst, src.sw_if_index)
+ remaining += 1
+ self.assertNotEqual(0, remaining, s)
+
+ def set_bd_flags(self, bd_id, **args):
+ """
+ Enable/disable defined feature(s) of the bridge domain.
+
+ :param int bd_id: Bridge domain ID.
+ :param list args: List of feature/status pairs. Allowed features: \
+ learn, forward, flood, uu_flood and arp_term. Status False means \
+ disable, status True means enable the feature.
+ :raise: ValueError in case of unknown feature in the input.
+ """
+ for flag in args:
+ if flag == "learn":
+ feature_bitmap = 1 << 0
+ elif flag == "forward":
+ feature_bitmap = 1 << 1
+ elif flag == "flood":
+ feature_bitmap = 1 << 2
+ elif flag == "uu_flood":
+ feature_bitmap = 1 << 3
+ elif flag == "arp_term":
+ feature_bitmap = 1 << 4
+ else:
+ raise ValueError("Unknown feature used: %s" % flag)
+ is_set = 1 if args[flag] else 0
+ self.vapi.bridge_flags(bd_id, is_set, feature_bitmap)
+ self.logger.info("Bridge domain ID %d updated" % bd_id)
+
+ def verify_bd(self, bd_id, **args):
+ """
+ Check if the bridge domain is configured and verify expected status
+ of listed features.
+
+ :param int bd_id: Bridge domain ID.
+ :param list args: List of feature/status pairs. Allowed features: \
+ learn, forward, flood, uu_flood and arp_term. Status False means \
+ disable, status True means enable the feature.
+ :return: 1 if bridge domain is configured, otherwise return 0.
+ :raise: ValueError in case of unknown feature in the input.
+ """
+ bd_dump = self.vapi.bridge_domain_dump(bd_id)
+ if len(bd_dump) == 0:
+ self.logger.info("Bridge domain ID %d is not configured" % bd_id)
+ return 0
+ else:
+ bd_dump = bd_dump[0]
+ if len(args) > 0:
+ for flag in args:
+ expected_status = 1 if args[flag] else 0
+ if flag == "learn":
+ flag_status = bd_dump[6]
+ elif flag == "forward":
+ flag_status = bd_dump[5]
+ elif flag == "flood":
+ flag_status = bd_dump[3]
+ elif flag == "uu_flood":
+ flag_status = bd_dump[4]
+ elif flag == "arp_term":
+ flag_status = bd_dump[7]
+ else:
+ raise ValueError("Unknown feature used: %s" % flag)
+ self.assertEqual(expected_status, flag_status)
+ return 1
+
+ def run_verify_test(self):
+ """
+ Create packet streams for all configured l2-pg interfaces, send all \
+ prepared packet streams and verify that:
+ - all packets received correctly on all pg-l2 interfaces assigned
+ to bridge domains
+ - no packet received on all pg-l2 interfaces not assigned to
+ bridge domains
+
+ :raise RuntimeError: if no packet captured on l2-pg interface assigned
+ to the bridge domain or if any packet is captured
+ on l2-pg interface not assigned to the bridge
+ domain.
+ """
+ # Test
+ # Create incoming packet streams for packet-generator interfaces
+ # for pg_if in self.pg_interfaces:
+ assert(len(self._packet_count_for_dst_if_idx) == 0)
+ for pg_if in self.pg_in_bd:
+ pkts = self.create_stream(pg_if)
+ pg_if.add_stream(pkts)
+
+ # Enable packet capture and start packet sending
+ self.pg_enable_capture(self.pg_in_bd)
+ self.pg_start()
+
+ # Verify
+ # Verify outgoing packet streams per packet-generator interface
+ for pg_if in self.pg_in_bd:
+ self.verify_capture(pg_if)
+
+ def test_l2bd_inst_01(self):
+ """ L2BD Multi-instance test 1 - create 5 BDs
+ """
+ # Config 1
+ # Create 5 BDs, put interfaces to these BDs and send MAC learning
+ # packets
+ self.create_bd_and_mac_learn(5)
+
+ # Verify 1
+ for bd_id in self.bd_list:
+ self.assertEqual(self.verify_bd(bd_id), 1)
+
+ # Test 1
+ # self.vapi.cli("clear trace")
+ self.run_verify_test()
+
+ def test_l2bd_inst_02(self):
+ """ L2BD Multi-instance test 2 - update data of 5 BDs
+ """
+ # Config 2
+ # Update data of 5 BDs (disable learn, forward, flood, uu-flood)
+ self.set_bd_flags(self.bd_list[0], learn=False, forward=False,
+ flood=False, uu_flood=False)
+ self.set_bd_flags(self.bd_list[1], forward=False)
+ self.set_bd_flags(self.bd_list[2], flood=False)
+ self.set_bd_flags(self.bd_list[3], uu_flood=False)
+ self.set_bd_flags(self.bd_list[4], learn=False)
+
+ # Verify 2
+ # Skipping check of uu_flood as it is not returned by
+ # bridge_domain_dump api command
+ self.verify_bd(self.bd_list[0], learn=False, forward=False,
+ flood=False, uu_flood=False)
+ self.verify_bd(self.bd_list[1], learn=True, forward=False,
+ flood=True, uu_flood=True)
+ self.verify_bd(self.bd_list[2], learn=True, forward=True,
+ flood=False, uu_flood=True)
+ self.verify_bd(self.bd_list[3], learn=True, forward=True,
+ flood=True, uu_flood=False)
+ self.verify_bd(self.bd_list[4], learn=False, forward=True,
+ flood=True, uu_flood=True)
+
+ def test_l2bd_inst_03(self):
+ """ L2BD Multi-instance test 3 - delete 2 BDs
+ """
+ # Config 3
+ # Delete 2 BDs
+ self.delete_bd(2)
+
+ # Verify 3
+ for bd_id in self.bd_deleted_list:
+ self.assertEqual(self.verify_bd(bd_id), 0)
+ for bd_id in self.bd_list:
+ self.assertEqual(self.verify_bd(bd_id), 1)
+
+ # Test 3
+ self.run_verify_test()
+
+ def test_l2bd_inst_04(self):
+ """ L2BD Multi-instance test 4 - add 2 BDs
+ """
+ # Config 4
+ # Create 5 BDs, put interfaces to these BDs and send MAC learning
+ # packets
+ self.create_bd_and_mac_learn(2)
+
+ # Verify 4
+ for bd_id in self.bd_list:
+ self.assertEqual(self.verify_bd(bd_id), 1)
+
+ # Test 4
+ # self.vapi.cli("clear trace")
+ self.run_verify_test()
+
+ @unittest.skipUnless(running_extended_tests(), "part of extended tests")
+ def test_l2bd_inst_05(self):
+ """ L2BD Multi-instance test 5 - delete 5 BDs
+ """
+ # Config 5
+ # Delete 5 BDs
+ self.delete_bd(5)
+
+ # Verify 5
+ for bd_id in self.bd_deleted_list:
+ self.assertEqual(self.verify_bd(bd_id), 0)
+ for bd_id in self.bd_list:
+ self.assertEqual(self.verify_bd(bd_id), 1)
+
+
+if __name__ == '__main__':
+ unittest.main(testRunner=VppTestRunner)
diff --git a/test/test_l2xc.py b/test/test_l2xc.py
new file mode 100644
index 00000000..2ec4af92
--- /dev/null
+++ b/test/test_l2xc.py
@@ -0,0 +1,226 @@
+#!/usr/bin/env python
+
+import unittest
+import random
+
+from scapy.packet import Raw
+from scapy.layers.l2 import Ether
+from scapy.layers.inet import IP, UDP
+
+from framework import VppTestCase, VppTestRunner
+from util import Host, ppp
+
+
+class TestL2xc(VppTestCase):
+ """ L2XC Test Case """
+
+ @classmethod
+ def setUpClass(cls):
+ """
+ Perform standard class setup (defined by class method setUpClass in
+ class VppTestCase) before running the test case, set test case related
+ variables and configure VPP.
+
+ :var int hosts_nr: Number of hosts to be created.
+ :var int dl_pkts_per_burst: Number of packets in burst for dual-loop
+ test.
+ :var int sl_pkts_per_burst: Number of packets in burst for single-loop
+ test.
+ """
+ super(TestL2xc, cls).setUpClass()
+
+ # Test variables
+ cls.hosts_nr = 10
+ cls.dl_pkts_per_burst = 257
+ cls.sl_pkts_per_burst = 2
+
+ try:
+ # create 4 pg interfaces
+ cls.create_pg_interfaces(range(4))
+
+ # packet flows mapping pg0 -> pg1, pg2 -> pg3, etc.
+ cls.flows = dict()
+ cls.flows[cls.pg0] = [cls.pg1]
+ cls.flows[cls.pg1] = [cls.pg0]
+ cls.flows[cls.pg2] = [cls.pg3]
+ cls.flows[cls.pg3] = [cls.pg2]
+
+ # packet sizes
+ cls.pg_if_packet_sizes = [64, 512, 1518, 9018]
+
+ cls.interfaces = list(cls.pg_interfaces)
+
+ # Create bi-directional cross-connects between pg0 and pg1
+ cls.vapi.sw_interface_set_l2_xconnect(
+ cls.pg0.sw_if_index, cls.pg1.sw_if_index, enable=1)
+ cls.vapi.sw_interface_set_l2_xconnect(
+ cls.pg1.sw_if_index, cls.pg0.sw_if_index, enable=1)
+
+ # Create bi-directional cross-connects between pg2 and pg3
+ cls.vapi.sw_interface_set_l2_xconnect(
+ cls.pg2.sw_if_index, cls.pg3.sw_if_index, enable=1)
+ cls.vapi.sw_interface_set_l2_xconnect(
+ cls.pg3.sw_if_index, cls.pg2.sw_if_index, enable=1)
+
+ # mapping between packet-generator index and lists of test hosts
+ cls.hosts_by_pg_idx = dict()
+
+ # Create host MAC and IPv4 lists
+ cls.create_host_lists(cls.hosts_nr)
+
+ # setup all interfaces
+ for i in cls.interfaces:
+ i.admin_up()
+
+ except Exception:
+ super(TestL2xc, cls).tearDownClass()
+ raise
+
+ def setUp(self):
+ super(TestL2xc, self).setUp()
+ self.reset_packet_infos()
+
+ def tearDown(self):
+ """
+ Show various debug prints after each test.
+ """
+ super(TestL2xc, self).tearDown()
+ if not self.vpp_dead:
+ self.logger.info(self.vapi.ppcli("show l2patch"))
+
+ @classmethod
+ def create_host_lists(cls, count):
+ """
+ Method to create required number of MAC and IPv4 addresses.
+ Create required number of host MAC addresses and distribute them among
+ interfaces. Create host IPv4 address for every host MAC address too.
+
+ :param count: Number of hosts to create MAC and IPv4 addresses for.
+ """
+ for pg_if in cls.pg_interfaces:
+ cls.hosts_by_pg_idx[pg_if.sw_if_index] = []
+ hosts = cls.hosts_by_pg_idx[pg_if.sw_if_index]
+ for j in range(0, count):
+ host = Host(
+ "00:00:00:ff:%02x:%02x" % (pg_if.sw_if_index, j),
+ "172.17.1%02x.%u" % (pg_if.sw_if_index, j))
+ hosts.append(host)
+
+ def create_stream(self, src_if, packet_sizes, packets_per_burst):
+ """
+ Create input packet stream for defined interface.
+
+ :param object src_if: Interface to create packet stream for.
+ :param list packet_sizes: List of required packet sizes.
+ :param int packets_per_burst: Number of packets in burst.
+ :return: Stream of packets.
+ """
+ pkts = []
+ for i in range(0, packets_per_burst):
+ dst_if = self.flows[src_if][0]
+ dst_host = random.choice(self.hosts_by_pg_idx[dst_if.sw_if_index])
+ src_host = random.choice(self.hosts_by_pg_idx[src_if.sw_if_index])
+ pkt_info = self.create_packet_info(src_if, dst_if)
+ payload = self.info_to_payload(pkt_info)
+ p = (Ether(dst=dst_host.mac, src=src_host.mac) /
+ IP(src=src_host.ip4, dst=dst_host.ip4) /
+ UDP(sport=1234, dport=1234) /
+ Raw(payload))
+ pkt_info.data = p.copy()
+ size = random.choice(packet_sizes)
+ self.extend_packet(p, size)
+ pkts.append(p)
+ return pkts
+
+ def verify_capture(self, pg_if, capture):
+ """
+ Verify captured input packet stream for defined interface.
+
+ :param object pg_if: Interface to verify captured packet stream for.
+ :param list capture: Captured packet stream.
+ """
+ last_info = dict()
+ for i in self.interfaces:
+ last_info[i.sw_if_index] = None
+ dst_sw_if_index = pg_if.sw_if_index
+ for packet in capture:
+ try:
+ ip = packet[IP]
+ udp = packet[UDP]
+ payload_info = self.payload_to_info(str(packet[Raw]))
+ packet_index = payload_info.index
+ self.assertEqual(payload_info.dst, dst_sw_if_index)
+ self.logger.debug("Got packet on port %s: src=%u (id=%u)" %
+ (pg_if.name, payload_info.src, packet_index))
+ next_info = self.get_next_packet_info_for_interface2(
+ payload_info.src, dst_sw_if_index,
+ last_info[payload_info.src])
+ last_info[payload_info.src] = next_info
+ self.assertTrue(next_info is not None)
+ self.assertEqual(packet_index, next_info.index)
+ saved_packet = next_info.data
+ # Check standard fields
+ self.assertEqual(ip.src, saved_packet[IP].src)
+ self.assertEqual(ip.dst, saved_packet[IP].dst)
+ self.assertEqual(udp.sport, saved_packet[UDP].sport)
+ self.assertEqual(udp.dport, saved_packet[UDP].dport)
+ except:
+ self.logger.error(ppp("Unexpected or invalid packet:", packet))
+ raise
+ for i in self.interfaces:
+ remaining_packet = self.get_next_packet_info_for_interface2(
+ i, dst_sw_if_index, last_info[i.sw_if_index])
+ self.assertTrue(remaining_packet is None,
+ "Port %u: Packet expected from source %u didn't"
+ " arrive" % (dst_sw_if_index, i.sw_if_index))
+
+ def run_l2xc_test(self, pkts_per_burst):
+ """ L2XC test """
+
+ # Create incoming packet streams for packet-generator interfaces
+ for i in self.interfaces:
+ pkts = self.create_stream(i, self.pg_if_packet_sizes,
+ pkts_per_burst)
+ i.add_stream(pkts)
+
+ # Enable packet capturing and start packet sending
+ self.pg_enable_capture(self.pg_interfaces)
+ self.pg_start()
+
+ # Verify outgoing packet streams per packet-generator interface
+ for i in self.pg_interfaces:
+ capture = i.get_capture()
+ self.logger.info("Verifying capture on interface %s" % i.name)
+ self.verify_capture(i, capture)
+
+ def test_l2xc_sl(self):
+ """ L2XC single-loop test
+
+ Test scenario:
+ 1. config
+ 2 pairs of 2 interfaces, l2xconnected
+
+ 2. sending l2 eth packets between 4 interfaces
+ 64B, 512B, 1518B, 9018B (ether_size)
+ burst of 2 packets per interface
+ """
+
+ self.run_l2xc_test(self.sl_pkts_per_burst)
+
+ def test_l2xc_dl(self):
+ """ L2XC dual-loop test
+
+ Test scenario:
+ 1. config
+ 2 pairs of 2 interfaces, l2xconnected
+
+ 2. sending l2 eth packets between 4 interfaces
+ 64B, 512B, 1518B, 9018B (ether_size)
+ burst of 257 packets per interface
+ """
+
+ self.run_l2xc_test(self.dl_pkts_per_burst)
+
+
+if __name__ == '__main__':
+ unittest.main(testRunner=VppTestRunner)
diff --git a/test/test_l2xc_multi_instance.py b/test/test_l2xc_multi_instance.py
new file mode 100644
index 00000000..bb26f959
--- /dev/null
+++ b/test/test_l2xc_multi_instance.py
@@ -0,0 +1,348 @@
+#!/usr/bin/env python
+"""L2XC Multi-instance Test Case HLD:
+
+**NOTES:**
+ - higher number (more than 15) of pg-l2 interfaces causes problems => only
+ 14 pg-l2 interfaces and 10 cross-connects are tested
+ - jumbo packets in configuration with 14 l2-pg interfaces leads to
+ problems too
+
+**config 1**
+ - add 14 pg-l2 interfaces
+ - add 10 cross-connects (two cross-connects per pair of l2-pg interfaces)
+
+**test 1**
+ - send L2 MAC frames between all pairs of pg-l2 interfaces
+
+**verify 1**
+ - all packets received correctly in case of cross-connected l2-pg
+ interfaces
+ - no packet received in case of not cross-connected l2-pg interfaces
+
+**config 2**
+ - delete 4 cross-connects
+
+**test 2**
+ - send L2 MAC frames between all pairs of pg-l2 interfaces
+
+**verify 2**
+ - all packets received correctly in case of cross-connected l2-pg
+ interfaces
+ - no packet received in case of not cross-connected l2-pg interfaces
+
+**config 3**
+ - add new 4 cross-connects
+
+**test 3**
+ - send L2 MAC frames between all pairs of pg-l2 interfaces
+
+**verify 3**
+ - all packets received correctly in case of cross-connected l2-pg
+ interfaces
+ - no packet received in case of not cross-connected l2-pg interfaces
+
+**config 4**
+ - delete 10 cross-connects
+
+**test 4**
+ - send L2 MAC frames between all pairs of pg-l2 interfaces
+
+**verify 4**
+ - no packet received on all of l2-pg interfaces (no cross-connect created)
+"""
+
+import unittest
+import random
+
+from scapy.packet import Raw
+from scapy.layers.l2 import Ether
+from scapy.layers.inet import IP, UDP
+
+from framework import VppTestCase, VppTestRunner
+from util import Host, ppp
+
+
+class TestL2xcMultiInst(VppTestCase):
+ """ L2XC Multi-instance Test Case """
+
+ @classmethod
+ def setUpClass(cls):
+ """
+ Perform standard class setup (defined by class method setUpClass in
+ class VppTestCase) before running the test case, set test case related
+ variables and configure VPP.
+ """
+ super(TestL2xcMultiInst, cls).setUpClass()
+
+ try:
+ # Create pg interfaces
+ cls.create_pg_interfaces(range(14))
+
+ # Packet flows mapping pg0 -> pg1 etc.
+ cls.flows = dict()
+ for i in range(len(cls.pg_interfaces)):
+ delta = 1 if i % 2 == 0 else -1
+ cls.flows[cls.pg_interfaces[i]] =\
+ [cls.pg_interfaces[i + delta]]
+
+ # Mapping between packet-generator index and lists of test hosts
+ cls.hosts_by_pg_idx = dict()
+ for pg_if in cls.pg_interfaces:
+ cls.hosts_by_pg_idx[pg_if.sw_if_index] = []
+
+ # Create test host entries
+ cls.create_hosts(70)
+
+ # Packet sizes - jumbo packet (9018 bytes) skipped
+ cls.pg_if_packet_sizes = [64, 512, 1518]
+
+ # Set up all interfaces
+ for i in cls.pg_interfaces:
+ i.admin_up()
+
+ # Create list of x-connected pg_interfaces
+ cls.pg_in_xc = list()
+
+ # Create list of not x-connected pg_interfaces
+ cls.pg_not_in_xc = list()
+ for pg_if in cls.pg_interfaces:
+ cls.pg_not_in_xc.append(pg_if)
+
+ except Exception:
+ super(TestL2xcMultiInst, cls).tearDownClass()
+ raise
+
+ def setUp(self):
+ """
+ Clear trace and packet infos before running each test.
+ """
+ super(TestL2xcMultiInst, self).setUp()
+ self.reset_packet_infos()
+
+ def tearDown(self):
+ """
+ Show various debug prints after each test.
+ """
+ super(TestL2xcMultiInst, self).tearDown()
+ if not self.vpp_dead:
+ self.logger.info(self.vapi.ppcli("show l2patch"))
+
+ @classmethod
+ def create_hosts(cls, count):
+ """
+ Create required number of host MAC addresses and distribute them among
+ interfaces. Create host IPv4 address for every host MAC address.
+
+ :param int count: Number of hosts to create MAC/IPv4 addresses for.
+ """
+ n_int = len(cls.pg_interfaces)
+ macs_per_if = count / n_int
+ i = -1
+ for pg_if in cls.pg_interfaces:
+ i += 1
+ start_nr = macs_per_if * i
+ end_nr = count if i == (n_int - 1) else macs_per_if * (i + 1)
+ hosts = cls.hosts_by_pg_idx[pg_if.sw_if_index]
+ for j in range(start_nr, end_nr):
+ host = Host(
+ "00:00:00:ff:%02x:%02x" % (pg_if.sw_if_index, j),
+ "172.17.1%02u.%u" % (pg_if.sw_if_index, j))
+ hosts.append(host)
+
+ def create_xconnects(self, count, start=0):
+ """
+ Create required number of cross-connects (always two cross-connects per
+ pair of packet-generator interfaces).
+
+ :param int count: Number of cross-connects to be created.
+ :param int start: Starting index of packet-generator interfaces. \
+ (Default value = 0)
+ """
+ for i in range(count):
+ rx_if = self.pg_interfaces[i + start]
+ delta = 1 if i % 2 == 0 else -1
+ tx_if = self.pg_interfaces[i + start + delta]
+ self.vapi.sw_interface_set_l2_xconnect(rx_if.sw_if_index,
+ tx_if.sw_if_index, 1)
+ self.logger.info("Cross-connect from %s to %s created"
+ % (tx_if.name, rx_if.name))
+ if self.pg_in_xc.count(rx_if) == 0:
+ self.pg_in_xc.append(rx_if)
+ if self.pg_not_in_xc.count(rx_if) == 1:
+ self.pg_not_in_xc.remove(rx_if)
+
+ def delete_xconnects(self, count, start=0):
+ """
+ Delete required number of cross-connects (always two cross-connects per
+ pair of packet-generator interfaces).
+
+ :param int count: Number of cross-connects to be deleted.
+ :param int start: Starting index of packet-generator interfaces. \
+ (Default value = 0)
+ """
+ for i in range(count):
+ rx_if = self.pg_interfaces[i + start]
+ delta = 1 if i % 2 == 0 else -1
+ tx_if = self.pg_interfaces[i + start + delta]
+ self.vapi.sw_interface_set_l2_xconnect(rx_if.sw_if_index,
+ tx_if.sw_if_index, 0)
+ self.logger.info("Cross-connect from %s to %s deleted"
+ % (tx_if.name, rx_if.name))
+ if self.pg_not_in_xc.count(rx_if) == 0:
+ self.pg_not_in_xc.append(rx_if)
+ if self.pg_in_xc.count(rx_if) == 1:
+ self.pg_in_xc.remove(rx_if)
+
+ def create_stream(self, src_if, packet_sizes):
+ """
+ Create input packet stream for defined interface using hosts list.
+
+ :param object src_if: Interface to create packet stream for.
+ :param list packet_sizes: List of required packet sizes.
+ :return: Stream of packets.
+ """
+ pkts = []
+ src_hosts = self.hosts_by_pg_idx[src_if.sw_if_index]
+ for dst_if in self.flows[src_if]:
+ dst_hosts = self.hosts_by_pg_idx[dst_if.sw_if_index]
+ n_int = len(dst_hosts)
+ for i in range(0, n_int):
+ dst_host = dst_hosts[i]
+ src_host = random.choice(src_hosts)
+ pkt_info = self.create_packet_info(src_if, dst_if)
+ payload = self.info_to_payload(pkt_info)
+ p = (Ether(dst=dst_host.mac, src=src_host.mac) /
+ IP(src=src_host.ip4, dst=dst_host.ip4) /
+ UDP(sport=1234, dport=1234) /
+ Raw(payload))
+ pkt_info.data = p.copy()
+ size = random.choice(packet_sizes)
+ self.extend_packet(p, size)
+ pkts.append(p)
+ self.logger.debug("Input stream created for port %s. Length: %u pkt(s)"
+ % (src_if.name, len(pkts)))
+ return pkts
+
+ def verify_capture(self, pg_if, capture):
+ """
+ Verify captured input packet stream for defined interface.
+
+ :param object pg_if: Interface to verify captured packet stream for.
+ :param list capture: Captured packet stream.
+ """
+ last_info = dict()
+ for i in self.pg_interfaces:
+ last_info[i.sw_if_index] = None
+ dst_sw_if_index = pg_if.sw_if_index
+ for packet in capture:
+ payload_info = self.payload_to_info(str(packet[Raw]))
+ try:
+ ip = packet[IP]
+ udp = packet[UDP]
+ packet_index = payload_info.index
+ self.assertEqual(payload_info.dst, dst_sw_if_index)
+ self.logger.debug("Got packet on port %s: src=%u (id=%u)" %
+ (pg_if.name, payload_info.src, packet_index))
+ next_info = self.get_next_packet_info_for_interface2(
+ payload_info.src, dst_sw_if_index,
+ last_info[payload_info.src])
+ last_info[payload_info.src] = next_info
+ self.assertTrue(next_info is not None)
+ self.assertEqual(packet_index, next_info.index)
+ saved_packet = next_info.data
+ # Check standard fields
+ self.assertEqual(ip.src, saved_packet[IP].src)
+ self.assertEqual(ip.dst, saved_packet[IP].dst)
+ self.assertEqual(udp.sport, saved_packet[UDP].sport)
+ self.assertEqual(udp.dport, saved_packet[UDP].dport)
+ except:
+ self.logger.error(ppp("Unexpected or invalid packet:", packet))
+ raise
+ for i in self.pg_interfaces:
+ remaining_packet = self.get_next_packet_info_for_interface2(
+ i, dst_sw_if_index, last_info[i.sw_if_index])
+ self.assertTrue(
+ remaining_packet is None,
+ "Port %u: Packet expected from source %u didn't arrive" %
+ (dst_sw_if_index, i.sw_if_index))
+
+ def run_verify_test(self):
+ """
+ Create packet streams for all configured l2-pg interfaces, send all \
+ prepared packet streams and verify that:
+ - all packets received correctly on all pg-l2 interfaces assigned
+ to cross-connects
+ - no packet received on all pg-l2 interfaces not assigned to
+ cross-connects
+
+ :raise RuntimeError: if no packet captured on l2-pg interface assigned
+ to the cross-connect or if any packet is captured
+ on l2-pg interface not assigned to the
+ cross-connect.
+ """
+ # Test
+ # Create incoming packet streams for packet-generator interfaces
+ for pg_if in self.pg_interfaces:
+ pkts = self.create_stream(pg_if, self.pg_if_packet_sizes)
+ pg_if.add_stream(pkts)
+
+ # Enable packet capture and start packet sending
+ self.pg_enable_capture(self.pg_interfaces)
+ self.pg_start()
+
+ # Verify
+ # Verify outgoing packet streams per packet-generator interface
+ for pg_if in self.pg_interfaces:
+ if pg_if in self.pg_in_xc:
+ capture = pg_if.get_capture(
+ remark="interface is a cross-connect sink")
+ self.verify_capture(pg_if, capture)
+ elif pg_if in self.pg_not_in_xc:
+ pg_if.assert_nothing_captured(
+ remark="interface is not a cross-connect sink")
+ else:
+ raise Exception("Unexpected interface: %s" % pg_if.name)
+
+ def test_l2xc_inst_01(self):
+ """ L2XC Multi-instance test 1 - create 10 cross-connects
+ """
+ # Config 1
+ # Create 10 cross-connects
+ self.create_xconnects(10)
+
+ # Test 1
+ self.run_verify_test()
+
+ def test_l2xc_inst_02(self):
+ """ L2XC Multi-instance test 2 - delete 4 cross-connects
+ """
+ # Config 2
+ # Delete 4 cross-connects
+ self.delete_xconnects(4)
+
+ # Test 2
+ self.run_verify_test()
+
+ def test_l2xc_inst_03(self):
+ """ L2BD Multi-instance 3 - add new 4 cross-connects
+ """
+ # Config 3
+ # Add new 4 cross-connects
+ self.create_xconnects(4, start=10)
+
+ # Test 3
+ self.run_verify_test()
+
+ def test_l2xc_inst_04(self):
+ """ L2XC Multi-instance test 4 - delete 10 cross-connects
+ """
+ # Config 4
+ # Delete 10 cross-connects
+ self.delete_xconnects(10, start=4)
+
+ # Test 4
+ self.run_verify_test()
+
+
+if __name__ == '__main__':
+ unittest.main(testRunner=VppTestRunner)
diff --git a/test/test_lb.py b/test/test_lb.py
new file mode 100644
index 00000000..e5802d99
--- /dev/null
+++ b/test/test_lb.py
@@ -0,0 +1,216 @@
+import socket
+
+from scapy.layers.inet import IP, UDP
+from scapy.layers.inet6 import IPv6
+from scapy.layers.l2 import Ether, GRE
+from scapy.packet import Raw
+
+from framework import VppTestCase
+from util import ppp
+
+""" TestLB is a subclass of VPPTestCase classes.
+
+ TestLB class defines Load Balancer test cases for:
+ - IP4 to GRE4 encap
+ - IP4 to GRE6 encap
+ - IP6 to GRE4 encap
+ - IP6 to GRE6 encap
+
+ As stated in comments below, GRE has issues with IPv6.
+ All test cases involving IPv6 are executed, but
+ received packets are not parsed and checked.
+
+"""
+
+
+class TestLB(VppTestCase):
+ """ Load Balancer Test Case """
+
+ @classmethod
+ def setUpClass(cls):
+ super(TestLB, cls).setUpClass()
+
+ cls.ass = range(5)
+ cls.packets = range(100)
+
+ try:
+ cls.create_pg_interfaces(range(2))
+ cls.interfaces = list(cls.pg_interfaces)
+
+ for i in cls.interfaces:
+ i.admin_up()
+ i.config_ip4()
+ i.config_ip6()
+ i.disable_ipv6_ra()
+ i.resolve_arp()
+ i.resolve_ndp()
+ dst4 = socket.inet_pton(socket.AF_INET, "10.0.0.0")
+ dst6 = socket.inet_pton(socket.AF_INET6, "2002::")
+ cls.vapi.ip_add_del_route(dst4, 24, cls.pg1.remote_ip4n)
+ cls.vapi.ip_add_del_route(dst6, 16, cls.pg1.remote_ip6n, is_ipv6=1)
+ cls.vapi.cli("lb conf ip4-src-address 39.40.41.42")
+ cls.vapi.cli("lb conf ip6-src-address 2004::1")
+ except Exception:
+ super(TestLB, cls).tearDownClass()
+ raise
+
+ def tearDown(self):
+ super(TestLB, self).tearDown()
+ if not self.vpp_dead:
+ self.logger.info(self.vapi.cli("show lb vip verbose"))
+
+ def getIPv4Flow(self, id):
+ return (IP(dst="90.0.%u.%u" % (id / 255, id % 255),
+ src="40.0.%u.%u" % (id / 255, id % 255)) /
+ UDP(sport=10000 + id, dport=20000 + id))
+
+ def getIPv6Flow(self, id):
+ return (IPv6(dst="2001::%u" % (id), src="fd00:f00d:ffff::%u" % (id)) /
+ UDP(sport=10000 + id, dport=20000 + id))
+
+ def generatePackets(self, src_if, isv4):
+ self.reset_packet_infos()
+ pkts = []
+ for pktid in self.packets:
+ info = self.create_packet_info(src_if, self.pg1)
+ payload = self.info_to_payload(info)
+ ip = self.getIPv4Flow(pktid) if isv4 else self.getIPv6Flow(pktid)
+ packet = (Ether(dst=src_if.local_mac, src=src_if.remote_mac) /
+ ip /
+ Raw(payload))
+ self.extend_packet(packet, 128)
+ info.data = packet.copy()
+ pkts.append(packet)
+ return pkts
+
+ def checkInner(self, gre, isv4):
+ IPver = IP if isv4 else IPv6
+ self.assertEqual(gre.proto, 0x0800 if isv4 else 0x86DD)
+ self.assertEqual(gre.flags, 0)
+ self.assertEqual(gre.version, 0)
+ inner = IPver(str(gre.payload))
+ payload_info = self.payload_to_info(str(inner[Raw]))
+ self.info = self.packet_infos[payload_info.index]
+ self.assertEqual(payload_info.src, self.pg0.sw_if_index)
+ self.assertEqual(str(inner), str(self.info.data[IPver]))
+
+ def checkCapture(self, gre4, isv4):
+ self.pg0.assert_nothing_captured()
+ out = self.pg1.get_capture(len(self.packets))
+
+ load = [0] * len(self.ass)
+ self.info = None
+ for p in out:
+ try:
+ asid = 0
+ gre = None
+ if gre4:
+ ip = p[IP]
+ asid = int(ip.dst.split(".")[3])
+ self.assertEqual(ip.version, 4)
+ self.assertEqual(ip.flags, 0)
+ self.assertEqual(ip.src, "39.40.41.42")
+ self.assertEqual(ip.dst, "10.0.0.%u" % asid)
+ self.assertEqual(ip.proto, 47)
+ self.assertEqual(len(ip.options), 0)
+ self.assertGreaterEqual(ip.ttl, 64)
+ gre = p[GRE]
+ else:
+ ip = p[IPv6]
+ asid = ip.dst.split(":")
+ asid = asid[len(asid) - 1]
+ asid = 0 if asid == "" else int(asid)
+ self.assertEqual(ip.version, 6)
+ self.assertEqual(ip.tc, 0)
+ self.assertEqual(ip.fl, 0)
+ self.assertEqual(ip.src, "2004::1")
+ self.assertEqual(
+ socket.inet_pton(socket.AF_INET6, ip.dst),
+ socket.inet_pton(socket.AF_INET6, "2002::%u" % asid)
+ )
+ self.assertEqual(ip.nh, 47)
+ self.assertGreaterEqual(ip.hlim, 64)
+ # self.assertEqual(len(ip.options), 0)
+ gre = GRE(str(p[IPv6].payload))
+ self.checkInner(gre, isv4)
+ load[asid] += 1
+ except:
+ self.logger.error(ppp("Unexpected or invalid packet:", p))
+ raise
+
+ # This is just to roughly check that the balancing algorithm
+ # is not completly biased.
+ for asid in self.ass:
+ if load[asid] < len(self.packets) / (len(self.ass) * 2):
+ self.log(
+ "ASS is not balanced: load[%d] = %d" % (asid, load[asid]))
+ raise Exception("Load Balancer algorithm is biased")
+
+ def test_lb_ip4_gre4(self):
+ """ Load Balancer IP4 GRE4 """
+ try:
+ self.vapi.cli("lb vip 90.0.0.0/8 encap gre4")
+ for asid in self.ass:
+ self.vapi.cli("lb as 90.0.0.0/8 10.0.0.%u" % (asid))
+
+ self.pg0.add_stream(self.generatePackets(self.pg0, isv4=True))
+ self.pg_enable_capture(self.pg_interfaces)
+ self.pg_start()
+ self.checkCapture(gre4=True, isv4=True)
+
+ finally:
+ for asid in self.ass:
+ self.vapi.cli("lb as 90.0.0.0/8 10.0.0.%u del" % (asid))
+ self.vapi.cli("lb vip 90.0.0.0/8 encap gre4 del")
+
+ def test_lb_ip6_gre4(self):
+ """ Load Balancer IP6 GRE4 """
+
+ try:
+ self.vapi.cli("lb vip 2001::/16 encap gre4")
+ for asid in self.ass:
+ self.vapi.cli("lb as 2001::/16 10.0.0.%u" % (asid))
+
+ self.pg0.add_stream(self.generatePackets(self.pg0, isv4=False))
+ self.pg_enable_capture(self.pg_interfaces)
+ self.pg_start()
+
+ self.checkCapture(gre4=True, isv4=False)
+ finally:
+ for asid in self.ass:
+ self.vapi.cli("lb as 2001::/16 10.0.0.%u del" % (asid))
+ self.vapi.cli("lb vip 2001::/16 encap gre4 del")
+
+ def test_lb_ip4_gre6(self):
+ """ Load Balancer IP4 GRE6 """
+ try:
+ self.vapi.cli("lb vip 90.0.0.0/8 encap gre6")
+ for asid in self.ass:
+ self.vapi.cli("lb as 90.0.0.0/8 2002::%u" % (asid))
+
+ self.pg0.add_stream(self.generatePackets(self.pg0, isv4=True))
+ self.pg_enable_capture(self.pg_interfaces)
+ self.pg_start()
+
+ self.checkCapture(gre4=False, isv4=True)
+ finally:
+ for asid in self.ass:
+ self.vapi.cli("lb as 90.0.0.0/8 2002::%u" % (asid))
+ self.vapi.cli("lb vip 90.0.0.0/8 encap gre6 del")
+
+ def test_lb_ip6_gre6(self):
+ """ Load Balancer IP6 GRE6 """
+ try:
+ self.vapi.cli("lb vip 2001::/16 encap gre6")
+ for asid in self.ass:
+ self.vapi.cli("lb as 2001::/16 2002::%u" % (asid))
+
+ self.pg0.add_stream(self.generatePackets(self.pg0, isv4=False))
+ self.pg_enable_capture(self.pg_interfaces)
+ self.pg_start()
+
+ self.checkCapture(gre4=False, isv4=False)
+ finally:
+ for asid in self.ass:
+ self.vapi.cli("lb as 2001::/16 2002::%u del" % (asid))
+ self.vapi.cli("lb vip 2001::/16 encap gre6 del")
diff --git a/test/test_lisp.py b/test/test_lisp.py
new file mode 100644
index 00000000..cfe8e0af
--- /dev/null
+++ b/test/test_lisp.py
@@ -0,0 +1,166 @@
+#!/usr/bin/env python
+import unittest
+
+from scapy.packet import Raw
+from scapy.layers.inet import IP, UDP, Ether
+from py_lispnetworking.lisp import LISP_GPE_Header
+
+from util import ppp, ForeignAddressFactory
+from framework import VppTestCase, VppTestRunner
+from lisp import *
+
+
+class Driver(object):
+
+ config_order = ['locator-sets',
+ 'locators',
+ 'local-mappings',
+ 'remote-mappings',
+ 'adjacencies']
+
+ """ Basic class for data driven testing """
+ def __init__(self, test, test_cases):
+ self._test_cases = test_cases
+ self._test = test
+
+ @property
+ def test_cases(self):
+ return self._test_cases
+
+ @property
+ def test(self):
+ return self._test
+
+ def create_packet(self, src_if, dst_if, deid, payload=''):
+ """
+ Create IPv4 packet
+
+ param: src_if
+ param: dst_if
+ """
+ packet = (Ether(dst=src_if.local_mac, src=src_if.remote_mac) /
+ IP(src=src_if.remote_ip4, dst=deid) /
+ Raw(payload))
+ return packet
+
+ @abstractmethod
+ def run(self):
+ """ testing procedure """
+ pass
+
+
+class SimpleDriver(Driver):
+ """ Implements simple test procedure """
+ def __init__(self, test, test_cases):
+ super(SimpleDriver, self).__init__(test, test_cases)
+
+ def verify_capture(self, src_loc, dst_loc, capture):
+ """
+ Verify captured packet
+
+ :param src_loc: source locator address
+ :param dst_loc: destination locator address
+ :param capture: list of captured packets
+ """
+ self.test.assertEqual(len(capture), 1, "Unexpected number of "
+ "packets! Expected 1 but {} received"
+ .format(len(capture)))
+ packet = capture[0]
+ try:
+ ip_hdr = packet[IP]
+ # assert the values match
+ self.test.assertEqual(ip_hdr.src, src_loc, "IP source address")
+ self.test.assertEqual(ip_hdr.dst, dst_loc,
+ "IP destination address")
+ gpe_hdr = packet[LISP_GPE_Header]
+ self.test.assertEqual(gpe_hdr.next_proto, 1,
+ "next_proto is not ipv4!")
+ ih = gpe_hdr[IP]
+ self.test.assertEqual(ih.src, self.test.pg0.remote_ip4,
+ "unexpected source EID!")
+ self.test.assertEqual(ih.dst, self.test.deid_ip4,
+ "unexpected dest EID!")
+ except:
+ self.test.logger.error(ppp("Unexpected or invalid packet:",
+ packet))
+ raise
+
+ def configure_tc(self, tc):
+ for config_item in self.config_order:
+ for vpp_object in tc[config_item]:
+ vpp_object.add_vpp_config()
+
+ def run(self, dest):
+ """ Send traffic for each test case and verify that it
+ is encapsulated """
+ for tc in enumerate(self.test_cases):
+ self.test.logger.info('Running {}'.format(tc[1]['name']))
+ self.configure_tc(tc[1])
+
+ packet = self.create_packet(self.test.pg0, self.test.pg1, dest,
+ 'data')
+ self.test.pg0.add_stream(packet)
+ self.test.pg0.enable_capture()
+ self.test.pg1.enable_capture()
+ self.test.pg_start()
+ capture = self.test.pg1.get_capture(1)
+ self.verify_capture(self.test.pg1.local_ip4,
+ self.test.pg1.remote_ip4, capture)
+ self.test.pg0.assert_nothing_captured()
+
+
+class TestLisp(VppTestCase):
+ """ Basic LISP test """
+
+ @classmethod
+ def setUpClass(cls):
+ super(TestLisp, cls).setUpClass()
+ cls.faf = ForeignAddressFactory()
+ cls.create_pg_interfaces(range(2)) # create pg0 and pg1
+ for i in cls.pg_interfaces:
+ i.admin_up() # put the interface upsrc_if
+ i.config_ip4() # configure IPv4 address on the interface
+ i.resolve_arp() # resolve ARP, so that we know VPP MAC
+
+ def setUp(self):
+ super(TestLisp, self).setUp()
+ self.vapi.lisp_enable_disable(is_enabled=1)
+
+ def test_lisp_basic_encap(self):
+ """Test case for basic encapsulation"""
+
+ self.deid_ip4_net = self.faf.net
+ self.deid_ip4 = self.faf.get_ip4()
+ self.seid_ip4 = '{}/{}'.format(self.pg0.local_ip4, 32)
+ self.rloc_ip4 = self.pg1.remote_ip4n
+
+ test_cases = [
+ {
+ 'name': 'basic ip4 over ip4',
+ 'locator-sets': [VppLispLocatorSet(self, 'ls-4o4')],
+ 'locators': [
+ VppLispLocator(self, self.pg1.sw_if_index, 'ls-4o4')
+ ],
+ 'local-mappings': [
+ VppLocalMapping(self, self.seid_ip4, 'ls-4o4')
+ ],
+ 'remote-mappings': [
+ VppRemoteMapping(self, self.deid_ip4_net,
+ [{
+ "is_ip4": 1,
+ "priority": 1,
+ "weight": 1,
+ "addr": self.rloc_ip4
+ }])
+ ],
+ 'adjacencies': [
+ VppLispAdjacency(self, self.seid_ip4, self.deid_ip4_net)
+ ]
+ }
+ ]
+ self.test_driver = SimpleDriver(self, test_cases)
+ self.test_driver.run(self.deid_ip4)
+
+
+if __name__ == '__main__':
+ unittest.main(testRunner=VppTestRunner)
diff --git a/test/test_map.py b/test/test_map.py
new file mode 100644
index 00000000..bbf4aec2
--- /dev/null
+++ b/test/test_map.py
@@ -0,0 +1,173 @@
+#!/usr/bin/env python
+
+import unittest
+import socket
+
+from framework import VppTestCase, VppTestRunner
+from vpp_ip_route import VppIpRoute, VppRoutePath, DpoProto
+
+from scapy.layers.l2 import Ether, Raw
+from scapy.layers.inet import IP, UDP, ICMP
+from scapy.layers.inet6 import IPv6
+
+
+class TestMAP(VppTestCase):
+ """ MAP Test Case """
+
+ def setUp(self):
+ super(TestMAP, self).setUp()
+
+ # create 2 pg interfaces
+ self.create_pg_interfaces(range(4))
+
+ # pg0 is 'inside' IPv4
+ self.pg0.admin_up()
+ self.pg0.config_ip4()
+ self.pg0.resolve_arp()
+
+ # pg1 is 'outside' IPv6
+ self.pg1.admin_up()
+ self.pg1.config_ip6()
+ self.pg1.generate_remote_hosts(4)
+ self.pg1.configure_ipv6_neighbors()
+
+ def tearDown(self):
+ super(TestMAP, self).tearDown()
+ for i in self.pg_interfaces:
+ i.unconfig_ip4()
+ i.unconfig_ip6()
+ i.admin_down()
+
+ def send_and_assert_no_replies(self, intf, pkts, remark):
+ intf.add_stream(pkts)
+ self.pg_enable_capture(self.pg_interfaces)
+ self.pg_start()
+ for i in self.pg_interfaces:
+ i.assert_nothing_captured(remark=remark)
+
+ def send_and_assert_encapped(self, tx, ip6_src, ip6_dst, dmac=None):
+ if not dmac:
+ dmac = self.pg1.remote_mac
+
+ self.pg0.add_stream(tx)
+
+ self.pg_enable_capture(self.pg_interfaces)
+ self.pg_start()
+
+ rx = self.pg1.get_capture(1)
+ rx = rx[0]
+
+ self.assertEqual(rx[Ether].dst, dmac)
+ self.assertEqual(rx[IP].src, tx[IP].src)
+ self.assertEqual(rx[IPv6].src, ip6_src)
+ self.assertEqual(rx[IPv6].dst, ip6_dst)
+
+ def test_map_e(self):
+ """ MAP-E """
+
+ #
+ # Add a route to the MAP-BR
+ #
+ map_br_pfx = "2001::"
+ map_br_pfx_len = 64
+ map_route = VppIpRoute(self,
+ map_br_pfx,
+ map_br_pfx_len,
+ [VppRoutePath(self.pg1.remote_ip6,
+ self.pg1.sw_if_index,
+ proto=DpoProto.DPO_PROTO_IP6)],
+ is_ip6=1)
+ map_route.add_vpp_config()
+
+ #
+ # Add a domain that maps from pg0 to pg1
+ #
+ map_dst = socket.inet_pton(socket.AF_INET6, map_br_pfx)
+ map_src = "3001::1"
+ map_src_n = socket.inet_pton(socket.AF_INET6, map_src)
+ client_pfx = socket.inet_pton(socket.AF_INET, "192.168.0.0")
+
+ self.vapi.map_add_domain(map_dst,
+ map_br_pfx_len,
+ map_src_n,
+ 128,
+ client_pfx,
+ 16)
+
+ #
+ # Fire in a v4 packet that will be encapped to the BR
+ #
+ v4 = (Ether(dst=self.pg0.local_mac, src=self.pg0.remote_mac) /
+ IP(src=self.pg0.remote_ip4, dst='192.168.1.1') /
+ UDP(sport=20000, dport=10000) /
+ Raw('\xa5' * 100))
+
+ self.send_and_assert_encapped(v4, map_src, "2001::c0a8:0:0")
+
+ #
+ # Fire in a V6 encapped packet.
+ # expect a decapped packet on the inside ip4 link
+ #
+ p = (Ether(dst=self.pg1.local_mac, src=self.pg1.remote_mac) /
+ IPv6(dst=map_src, src="2001::1") /
+ IP(dst=self.pg0.remote_ip4, src='192.168.1.1') /
+ UDP(sport=20000, dport=10000) /
+ Raw('\xa5' * 100))
+
+ self.pg1.add_stream(p)
+
+ self.pg_enable_capture(self.pg_interfaces)
+ self.pg_start()
+
+ rx = self.pg0.get_capture(1)
+ rx = rx[0]
+
+ self.assertFalse(rx.haslayer(IPv6))
+ self.assertEqual(rx[IP].src, p[IP].src)
+ self.assertEqual(rx[IP].dst, p[IP].dst)
+
+ #
+ # Pre-resolve. No API for this!!
+ #
+ self.vapi.ppcli("map params pre-resolve ip6-nh 4001::1")
+
+ self.send_and_assert_no_replies(self.pg0, v4,
+ "resovled via default route")
+
+ #
+ # Add a route to 4001::1. Expect the encapped traffic to be
+ # sent via that routes next-hop
+ #
+ pre_res_route = VppIpRoute(
+ self, "4001::1", 128,
+ [VppRoutePath(self.pg1.remote_hosts[2].ip6,
+ self.pg1.sw_if_index,
+ proto=DpoProto.DPO_PROTO_IP6)],
+ is_ip6=1)
+ pre_res_route.add_vpp_config()
+
+ self.send_and_assert_encapped(v4, map_src,
+ "2001::c0a8:0:0",
+ dmac=self.pg1.remote_hosts[2].mac)
+
+ #
+ # change the route to the pre-solved next-hop
+ #
+ pre_res_route.modify([VppRoutePath(self.pg1.remote_hosts[3].ip6,
+ self.pg1.sw_if_index,
+ proto=DpoProto.DPO_PROTO_IP6)])
+ pre_res_route.add_vpp_config()
+
+ self.send_and_assert_encapped(v4, map_src,
+ "2001::c0a8:0:0",
+ dmac=self.pg1.remote_hosts[3].mac)
+
+ #
+ # cleanup. The test infra's object registry will ensure
+ # the route is really gone and thus that the unresolve worked.
+ #
+ pre_res_route.remove_vpp_config()
+ self.vapi.ppcli("map params pre-resolve del ip6-nh 4001::1")
+
+if __name__ == '__main__':
+ unittest.main(testRunner=VppTestRunner)
diff --git a/test/test_mpls.py b/test/test_mpls.py
new file mode 100644
index 00000000..460a32d1
--- /dev/null
+++ b/test/test_mpls.py
@@ -0,0 +1,1771 @@
+#!/usr/bin/env python
+
+import unittest
+import socket
+
+from framework import VppTestCase, VppTestRunner
+from vpp_ip_route import VppIpRoute, VppRoutePath, VppMplsRoute, \
+ VppMplsIpBind, VppIpMRoute, VppMRoutePath, \
+ MRouteItfFlags, MRouteEntryFlags, DpoProto, VppIpTable, VppMplsTable
+from vpp_mpls_tunnel_interface import VppMPLSTunnelInterface
+
+from scapy.packet import Raw
+from scapy.layers.l2 import Ether
+from scapy.layers.inet import IP, UDP, ICMP
+from scapy.layers.inet6 import IPv6
+from scapy.contrib.mpls import MPLS
+
+
+def verify_filter(capture, sent):
+ if not len(capture) == len(sent):
+ # filter out any IPv6 RAs from the capture
+ for p in capture:
+ if p.haslayer(IPv6):
+ capture.remove(p)
+ return capture
+
+
+def verify_mpls_stack(tst, rx, mpls_labels, ttl=255, num=0):
+ # the rx'd packet has the MPLS label popped
+ eth = rx[Ether]
+ tst.assertEqual(eth.type, 0x8847)
+
+ rx_mpls = rx[MPLS]
+
+ for ii in range(len(mpls_labels)):
+ tst.assertEqual(rx_mpls.label, mpls_labels[ii])
+ tst.assertEqual(rx_mpls.cos, 0)
+ if ii == num:
+ tst.assertEqual(rx_mpls.ttl, ttl)
+ else:
+ tst.assertEqual(rx_mpls.ttl, 255)
+ if ii == len(mpls_labels) - 1:
+ tst.assertEqual(rx_mpls.s, 1)
+ else:
+ # not end of stack
+ tst.assertEqual(rx_mpls.s, 0)
+ # pop the label to expose the next
+ rx_mpls = rx_mpls[MPLS].payload
+
+
+class TestMPLS(VppTestCase):
+ """ MPLS Test Case """
+
+ def setUp(self):
+ super(TestMPLS, self).setUp()
+
+ # create 2 pg interfaces
+ self.create_pg_interfaces(range(4))
+
+ # setup both interfaces
+ # assign them different tables.
+ table_id = 0
+ self.tables = []
+
+ tbl = VppMplsTable(self, 0)
+ tbl.add_vpp_config()
+ self.tables.append(tbl)
+
+ for i in self.pg_interfaces:
+ i.admin_up()
+
+ if table_id != 0:
+ tbl = VppIpTable(self, table_id)
+ tbl.add_vpp_config()
+ self.tables.append(tbl)
+ tbl = VppIpTable(self, table_id, is_ip6=1)
+ tbl.add_vpp_config()
+ self.tables.append(tbl)
+
+ i.set_table_ip4(table_id)
+ i.set_table_ip6(table_id)
+ i.config_ip4()
+ i.resolve_arp()
+ i.config_ip6()
+ i.resolve_ndp()
+ i.enable_mpls()
+ table_id += 1
+
+ def tearDown(self):
+ for i in self.pg_interfaces:
+ i.unconfig_ip4()
+ i.unconfig_ip6()
+ i.ip6_disable()
+ i.set_table_ip4(0)
+ i.set_table_ip6(0)
+ i.disable_mpls()
+ i.admin_down()
+ super(TestMPLS, self).tearDown()
+
+ # the default of 64 matches the IP packet TTL default
+ def create_stream_labelled_ip4(
+ self,
+ src_if,
+ mpls_labels,
+ mpls_ttl=255,
+ ping=0,
+ ip_itf=None,
+ dst_ip=None,
+ n=257):
+ self.reset_packet_infos()
+ pkts = []
+ for i in range(0, n):
+ info = self.create_packet_info(src_if, src_if)
+ payload = self.info_to_payload(info)
+ p = Ether(dst=src_if.local_mac, src=src_if.remote_mac)
+
+ for ii in range(len(mpls_labels)):
+ if ii == len(mpls_labels) - 1:
+ p = p / MPLS(label=mpls_labels[ii], ttl=mpls_ttl, s=1)
+ else:
+ p = p / MPLS(label=mpls_labels[ii], ttl=mpls_ttl, s=0)
+ if not ping:
+ if not dst_ip:
+ p = (p / IP(src=src_if.local_ip4, dst=src_if.remote_ip4) /
+ UDP(sport=1234, dport=1234) /
+ Raw(payload))
+ else:
+ p = (p / IP(src=src_if.local_ip4, dst=dst_ip) /
+ UDP(sport=1234, dport=1234) /
+ Raw(payload))
+ else:
+ p = (p / IP(src=ip_itf.remote_ip4,
+ dst=ip_itf.local_ip4) /
+ ICMP())
+
+ info.data = p.copy()
+ pkts.append(p)
+ return pkts
+
+ def create_stream_ip4(self, src_if, dst_ip):
+ self.reset_packet_infos()
+ pkts = []
+ for i in range(0, 257):
+ info = self.create_packet_info(src_if, src_if)
+ payload = self.info_to_payload(info)
+ p = (Ether(dst=src_if.local_mac, src=src_if.remote_mac) /
+ IP(src=src_if.remote_ip4, dst=dst_ip) /
+ UDP(sport=1234, dport=1234) /
+ Raw(payload))
+ info.data = p.copy()
+ pkts.append(p)
+ return pkts
+
+ def create_stream_labelled_ip6(self, src_if, mpls_label, mpls_ttl,
+ dst_ip=None):
+ if dst_ip is None:
+ dst_ip = src_if.remote_ip6
+ self.reset_packet_infos()
+ pkts = []
+ for i in range(0, 257):
+ info = self.create_packet_info(src_if, src_if)
+ payload = self.info_to_payload(info)
+ p = (Ether(dst=src_if.local_mac, src=src_if.remote_mac) /
+ MPLS(label=mpls_label, ttl=mpls_ttl) /
+ IPv6(src=src_if.remote_ip6, dst=dst_ip) /
+ UDP(sport=1234, dport=1234) /
+ Raw(payload))
+ info.data = p.copy()
+ pkts.append(p)
+ return pkts
+
+ def verify_capture_ip4(self, src_if, capture, sent, ping_resp=0):
+ try:
+ capture = verify_filter(capture, sent)
+
+ self.assertEqual(len(capture), len(sent))
+
+ for i in range(len(capture)):
+ tx = sent[i]
+ rx = capture[i]
+
+ # the rx'd packet has the MPLS label popped
+ eth = rx[Ether]
+ self.assertEqual(eth.type, 0x800)
+
+ tx_ip = tx[IP]
+ rx_ip = rx[IP]
+
+ if not ping_resp:
+ self.assertEqual(rx_ip.src, tx_ip.src)
+ self.assertEqual(rx_ip.dst, tx_ip.dst)
+ # IP processing post pop has decremented the TTL
+ self.assertEqual(rx_ip.ttl + 1, tx_ip.ttl)
+ else:
+ self.assertEqual(rx_ip.src, tx_ip.dst)
+ self.assertEqual(rx_ip.dst, tx_ip.src)
+
+ except:
+ raise
+
+ def verify_capture_labelled_ip4(self, src_if, capture, sent,
+ mpls_labels):
+ try:
+ capture = verify_filter(capture, sent)
+
+ self.assertEqual(len(capture), len(sent))
+
+ for i in range(len(capture)):
+ tx = sent[i]
+ rx = capture[i]
+ tx_ip = tx[IP]
+ rx_ip = rx[IP]
+
+ # the MPLS TTL is copied from the IP
+ verify_mpls_stack(self, rx, mpls_labels, rx_ip.ttl,
+ len(mpls_labels) - 1)
+
+ self.assertEqual(rx_ip.src, tx_ip.src)
+ self.assertEqual(rx_ip.dst, tx_ip.dst)
+ # IP processing post pop has decremented the TTL
+ self.assertEqual(rx_ip.ttl + 1, tx_ip.ttl)
+
+ except:
+ raise
+
+ def verify_capture_tunneled_ip4(self, src_if, capture, sent, mpls_labels,
+ ttl=255, top=None):
+ if top is None:
+ top = len(mpls_labels) - 1
+ try:
+ capture = verify_filter(capture, sent)
+
+ self.assertEqual(len(capture), len(sent))
+
+ for i in range(len(capture)):
+ tx = sent[i]
+ rx = capture[i]
+ tx_ip = tx[IP]
+ rx_ip = rx[IP]
+
+ # the MPLS TTL is 255 since it enters a new tunnel
+ verify_mpls_stack(self, rx, mpls_labels, ttl, top)
+
+ self.assertEqual(rx_ip.src, tx_ip.src)
+ self.assertEqual(rx_ip.dst, tx_ip.dst)
+ # IP processing post pop has decremented the TTL
+ self.assertEqual(rx_ip.ttl + 1, tx_ip.ttl)
+
+ except:
+ raise
+
+ def verify_capture_labelled(self, src_if, capture, sent,
+ mpls_labels, ttl=254, num=0):
+ try:
+ capture = verify_filter(capture, sent)
+
+ self.assertEqual(len(capture), len(sent))
+
+ for i in range(len(capture)):
+ rx = capture[i]
+ verify_mpls_stack(self, rx, mpls_labels, ttl, num)
+ except:
+ raise
+
+ def verify_capture_ip6(self, src_if, capture, sent):
+ try:
+ self.assertEqual(len(capture), len(sent))
+
+ for i in range(len(capture)):
+ tx = sent[i]
+ rx = capture[i]
+
+ # the rx'd packet has the MPLS label popped
+ eth = rx[Ether]
+ self.assertEqual(eth.type, 0x86DD)
+
+ tx_ip = tx[IPv6]
+ rx_ip = rx[IPv6]
+
+ self.assertEqual(rx_ip.src, tx_ip.src)
+ self.assertEqual(rx_ip.dst, tx_ip.dst)
+ # IP processing post pop has decremented the TTL
+ self.assertEqual(rx_ip.hlim + 1, tx_ip.hlim)
+
+ except:
+ raise
+
+ def send_and_assert_no_replies(self, intf, pkts, remark):
+ intf.add_stream(pkts)
+ self.pg_enable_capture(self.pg_interfaces)
+ self.pg_start()
+ for i in self.pg_interfaces:
+ i.assert_nothing_captured(remark=remark)
+
+ def test_swap(self):
+ """ MPLS label swap tests """
+
+ #
+ # A simple MPLS xconnect - eos label in label out
+ #
+ route_32_eos = VppMplsRoute(self, 32, 1,
+ [VppRoutePath(self.pg0.remote_ip4,
+ self.pg0.sw_if_index,
+ labels=[33])])
+ route_32_eos.add_vpp_config()
+
+ #
+ # a stream that matches the route for 10.0.0.1
+ # PG0 is in the default table
+ #
+ self.vapi.cli("clear trace")
+ tx = self.create_stream_labelled_ip4(self.pg0, [32])
+ self.pg0.add_stream(tx)
+
+ self.pg_enable_capture(self.pg_interfaces)
+ self.pg_start()
+
+ rx = self.pg0.get_capture()
+ self.verify_capture_labelled(self.pg0, rx, tx, [33])
+
+ #
+ # A simple MPLS xconnect - non-eos label in label out
+ #
+ route_32_neos = VppMplsRoute(self, 32, 0,
+ [VppRoutePath(self.pg0.remote_ip4,
+ self.pg0.sw_if_index,
+ labels=[33])])
+ route_32_neos.add_vpp_config()
+
+ #
+ # a stream that matches the route for 10.0.0.1
+ # PG0 is in the default table
+ #
+ self.vapi.cli("clear trace")
+ tx = self.create_stream_labelled_ip4(self.pg0, [32, 99])
+ self.pg0.add_stream(tx)
+
+ self.pg_enable_capture(self.pg_interfaces)
+ self.pg_start()
+
+ rx = self.pg0.get_capture()
+ self.verify_capture_labelled(self.pg0, rx, tx, [33, 99])
+
+ #
+ # An MPLS xconnect - EOS label in IP out
+ #
+ route_33_eos = VppMplsRoute(self, 33, 1,
+ [VppRoutePath(self.pg0.remote_ip4,
+ self.pg0.sw_if_index,
+ labels=[])])
+ route_33_eos.add_vpp_config()
+
+ self.vapi.cli("clear trace")
+ tx = self.create_stream_labelled_ip4(self.pg0, [33])
+ self.pg0.add_stream(tx)
+
+ self.pg_enable_capture(self.pg_interfaces)
+ self.pg_start()
+
+ rx = self.pg0.get_capture()
+ self.verify_capture_ip4(self.pg0, rx, tx)
+
+ #
+ # An MPLS xconnect - non-EOS label in IP out - an invalid configuration
+ # so this traffic should be dropped.
+ #
+ route_33_neos = VppMplsRoute(self, 33, 0,
+ [VppRoutePath(self.pg0.remote_ip4,
+ self.pg0.sw_if_index,
+ labels=[])])
+ route_33_neos.add_vpp_config()
+
+ self.vapi.cli("clear trace")
+ tx = self.create_stream_labelled_ip4(self.pg0, [33, 99])
+ self.pg0.add_stream(tx)
+
+ self.pg_enable_capture(self.pg_interfaces)
+ self.pg_start()
+ self.pg0.assert_nothing_captured(
+ remark="MPLS non-EOS packets popped and forwarded")
+
+ #
+ # A recursive EOS x-connect, which resolves through another x-connect
+ #
+ route_34_eos = VppMplsRoute(self, 34, 1,
+ [VppRoutePath("0.0.0.0",
+ 0xffffffff,
+ nh_via_label=32,
+ labels=[44, 45])])
+ route_34_eos.add_vpp_config()
+
+ tx = self.create_stream_labelled_ip4(self.pg0, [34])
+ self.pg0.add_stream(tx)
+
+ self.pg_enable_capture(self.pg_interfaces)
+ self.pg_start()
+
+ rx = self.pg0.get_capture()
+ self.verify_capture_labelled(self.pg0, rx, tx, [33, 44, 45], num=2)
+
+ #
+ # A recursive non-EOS x-connect, which resolves through another
+ # x-connect
+ #
+ route_34_neos = VppMplsRoute(self, 34, 0,
+ [VppRoutePath("0.0.0.0",
+ 0xffffffff,
+ nh_via_label=32,
+ labels=[44, 46])])
+ route_34_neos.add_vpp_config()
+
+ self.vapi.cli("clear trace")
+ tx = self.create_stream_labelled_ip4(self.pg0, [34, 99])
+ self.pg0.add_stream(tx)
+
+ self.pg_enable_capture(self.pg_interfaces)
+ self.pg_start()
+
+ rx = self.pg0.get_capture()
+ # it's the 2nd (counting from 0) label in the stack that is swapped
+ self.verify_capture_labelled(self.pg0, rx, tx, [33, 44, 46, 99], num=2)
+
+ #
+ # an recursive IP route that resolves through the recursive non-eos
+ # x-connect
+ #
+ ip_10_0_0_1 = VppIpRoute(self, "10.0.0.1", 32,
+ [VppRoutePath("0.0.0.0",
+ 0xffffffff,
+ nh_via_label=34,
+ labels=[55])])
+ ip_10_0_0_1.add_vpp_config()
+
+ self.vapi.cli("clear trace")
+ tx = self.create_stream_ip4(self.pg0, "10.0.0.1")
+ self.pg0.add_stream(tx)
+
+ self.pg_enable_capture(self.pg_interfaces)
+ self.pg_start()
+
+ rx = self.pg0.get_capture()
+ self.verify_capture_labelled_ip4(self.pg0, rx, tx, [33, 44, 46, 55])
+
+ ip_10_0_0_1.remove_vpp_config()
+ route_34_neos.remove_vpp_config()
+ route_34_eos.remove_vpp_config()
+ route_33_neos.remove_vpp_config()
+ route_33_eos.remove_vpp_config()
+ route_32_neos.remove_vpp_config()
+ route_32_eos.remove_vpp_config()
+
+ def test_bind(self):
+ """ MPLS Local Label Binding test """
+
+ #
+ # Add a non-recursive route with a single out label
+ #
+ route_10_0_0_1 = VppIpRoute(self, "10.0.0.1", 32,
+ [VppRoutePath(self.pg0.remote_ip4,
+ self.pg0.sw_if_index,
+ labels=[45])])
+ route_10_0_0_1.add_vpp_config()
+
+ # bind a local label to the route
+ binding = VppMplsIpBind(self, 44, "10.0.0.1", 32)
+ binding.add_vpp_config()
+
+ # non-EOS stream
+ self.vapi.cli("clear trace")
+ tx = self.create_stream_labelled_ip4(self.pg0, [44, 99])
+ self.pg0.add_stream(tx)
+
+ self.pg_enable_capture(self.pg_interfaces)
+ self.pg_start()
+
+ rx = self.pg0.get_capture()
+ self.verify_capture_labelled(self.pg0, rx, tx, [45, 99])
+
+ # EOS stream
+ self.vapi.cli("clear trace")
+ tx = self.create_stream_labelled_ip4(self.pg0, [44])
+ self.pg0.add_stream(tx)
+
+ self.pg_enable_capture(self.pg_interfaces)
+ self.pg_start()
+
+ rx = self.pg0.get_capture()
+ self.verify_capture_labelled(self.pg0, rx, tx, [45])
+
+ # IP stream
+ self.vapi.cli("clear trace")
+ tx = self.create_stream_ip4(self.pg0, "10.0.0.1")
+ self.pg0.add_stream(tx)
+
+ self.pg_enable_capture(self.pg_interfaces)
+ self.pg_start()
+
+ rx = self.pg0.get_capture()
+ self.verify_capture_labelled_ip4(self.pg0, rx, tx, [45])
+
+ #
+ # cleanup
+ #
+ binding.remove_vpp_config()
+ route_10_0_0_1.remove_vpp_config()
+
+ def test_imposition(self):
+ """ MPLS label imposition test """
+
+ #
+ # Add a non-recursive route with a single out label
+ #
+ route_10_0_0_1 = VppIpRoute(self, "10.0.0.1", 32,
+ [VppRoutePath(self.pg0.remote_ip4,
+ self.pg0.sw_if_index,
+ labels=[32])])
+ route_10_0_0_1.add_vpp_config()
+
+ #
+ # a stream that matches the route for 10.0.0.1
+ # PG0 is in the default table
+ #
+ self.vapi.cli("clear trace")
+ tx = self.create_stream_ip4(self.pg0, "10.0.0.1")
+ self.pg0.add_stream(tx)
+
+ self.pg_enable_capture(self.pg_interfaces)
+ self.pg_start()
+
+ rx = self.pg0.get_capture()
+ self.verify_capture_labelled_ip4(self.pg0, rx, tx, [32])
+
+ #
+ # Add a non-recursive route with a 3 out labels
+ #
+ route_10_0_0_2 = VppIpRoute(self, "10.0.0.2", 32,
+ [VppRoutePath(self.pg0.remote_ip4,
+ self.pg0.sw_if_index,
+ labels=[32, 33, 34])])
+ route_10_0_0_2.add_vpp_config()
+
+ #
+ # a stream that matches the route for 10.0.0.1
+ # PG0 is in the default table
+ #
+ self.vapi.cli("clear trace")
+ tx = self.create_stream_ip4(self.pg0, "10.0.0.2")
+ self.pg0.add_stream(tx)
+
+ self.pg_enable_capture(self.pg_interfaces)
+ self.pg_start()
+
+ rx = self.pg0.get_capture()
+ self.verify_capture_labelled_ip4(self.pg0, rx, tx, [32, 33, 34])
+
+ #
+ # add a recursive path, with output label, via the 1 label route
+ #
+ route_11_0_0_1 = VppIpRoute(self, "11.0.0.1", 32,
+ [VppRoutePath("10.0.0.1",
+ 0xffffffff,
+ labels=[44])])
+ route_11_0_0_1.add_vpp_config()
+
+ #
+ # a stream that matches the route for 11.0.0.1, should pick up
+ # the label stack for 11.0.0.1 and 10.0.0.1
+ #
+ self.vapi.cli("clear trace")
+ tx = self.create_stream_ip4(self.pg0, "11.0.0.1")
+ self.pg0.add_stream(tx)
+
+ self.pg_enable_capture(self.pg_interfaces)
+ self.pg_start()
+
+ rx = self.pg0.get_capture()
+ self.verify_capture_labelled_ip4(self.pg0, rx, tx, [32, 44])
+
+ #
+ # add a recursive path, with 2 labels, via the 3 label route
+ #
+ route_11_0_0_2 = VppIpRoute(self, "11.0.0.2", 32,
+ [VppRoutePath("10.0.0.2",
+ 0xffffffff,
+ labels=[44, 45])])
+ route_11_0_0_2.add_vpp_config()
+
+ #
+ # a stream that matches the route for 11.0.0.1, should pick up
+ # the label stack for 11.0.0.1 and 10.0.0.1
+ #
+ self.vapi.cli("clear trace")
+ tx = self.create_stream_ip4(self.pg0, "11.0.0.2")
+ self.pg0.add_stream(tx)
+
+ self.pg_enable_capture(self.pg_interfaces)
+ self.pg_start()
+
+ rx = self.pg0.get_capture()
+ self.verify_capture_labelled_ip4(
+ self.pg0, rx, tx, [32, 33, 34, 44, 45])
+
+ #
+ # cleanup
+ #
+ route_11_0_0_2.remove_vpp_config()
+ route_11_0_0_1.remove_vpp_config()
+ route_10_0_0_2.remove_vpp_config()
+ route_10_0_0_1.remove_vpp_config()
+
+ def test_tunnel(self):
+ """ MPLS Tunnel Tests """
+
+ #
+ # Create a tunnel with a single out label
+ #
+ mpls_tun = VppMPLSTunnelInterface(self,
+ [VppRoutePath(self.pg0.remote_ip4,
+ self.pg0.sw_if_index,
+ labels=[44, 46])])
+ mpls_tun.add_vpp_config()
+ mpls_tun.admin_up()
+
+ #
+ # add an unlabelled route through the new tunnel
+ #
+ route_10_0_0_3 = VppIpRoute(self, "10.0.0.3", 32,
+ [VppRoutePath("0.0.0.0",
+ mpls_tun._sw_if_index)])
+ route_10_0_0_3.add_vpp_config()
+
+ self.vapi.cli("clear trace")
+ tx = self.create_stream_ip4(self.pg0, "10.0.0.3")
+ self.pg0.add_stream(tx)
+
+ self.pg_enable_capture(self.pg_interfaces)
+ self.pg_start()
+
+ rx = self.pg0.get_capture()
+ self.verify_capture_tunneled_ip4(self.pg0, rx, tx, [44, 46])
+
+ #
+ # add a labelled route through the new tunnel
+ #
+ route_10_0_0_4 = VppIpRoute(self, "10.0.0.4", 32,
+ [VppRoutePath("0.0.0.0",
+ mpls_tun._sw_if_index,
+ labels=[33])])
+ route_10_0_0_4.add_vpp_config()
+
+ self.vapi.cli("clear trace")
+ tx = self.create_stream_ip4(self.pg0, "10.0.0.4")
+ self.pg0.add_stream(tx)
+
+ self.pg_enable_capture(self.pg_interfaces)
+ self.pg_start()
+
+ rx = self.pg0.get_capture()
+ self.verify_capture_tunneled_ip4(self.pg0, rx, tx, [44, 46, 33],
+ ttl=63, top=2)
+
+ def test_v4_exp_null(self):
+ """ MPLS V4 Explicit NULL test """
+
+ #
+ # The first test case has an MPLS TTL of 0
+ # all packet should be dropped
+ #
+ tx = self.create_stream_labelled_ip4(self.pg0, [0], 0)
+ self.pg0.add_stream(tx)
+
+ self.pg_enable_capture(self.pg_interfaces)
+ self.pg_start()
+
+ self.pg0.assert_nothing_captured(remark="MPLS TTL=0 packets forwarded")
+
+ #
+ # a stream with a non-zero MPLS TTL
+ # PG0 is in the default table
+ #
+ tx = self.create_stream_labelled_ip4(self.pg0, [0])
+ self.pg0.add_stream(tx)
+
+ self.pg_enable_capture(self.pg_interfaces)
+ self.pg_start()
+
+ rx = self.pg0.get_capture()
+ self.verify_capture_ip4(self.pg0, rx, tx)
+
+ #
+ # a stream with a non-zero MPLS TTL
+ # PG1 is in table 1
+ # we are ensuring the post-pop lookup occurs in the VRF table
+ #
+ self.vapi.cli("clear trace")
+ tx = self.create_stream_labelled_ip4(self.pg1, [0])
+ self.pg1.add_stream(tx)
+
+ self.pg_enable_capture(self.pg_interfaces)
+ self.pg_start()
+
+ rx = self.pg1.get_capture()
+ self.verify_capture_ip4(self.pg0, rx, tx)
+
+ def test_v6_exp_null(self):
+ """ MPLS V6 Explicit NULL test """
+
+ #
+ # a stream with a non-zero MPLS TTL
+ # PG0 is in the default table
+ #
+ self.vapi.cli("clear trace")
+ tx = self.create_stream_labelled_ip6(self.pg0, 2, 2)
+ self.pg0.add_stream(tx)
+
+ self.pg_enable_capture(self.pg_interfaces)
+ self.pg_start()
+
+ rx = self.pg0.get_capture()
+ self.verify_capture_ip6(self.pg0, rx, tx)
+
+ #
+ # a stream with a non-zero MPLS TTL
+ # PG1 is in table 1
+ # we are ensuring the post-pop lookup occurs in the VRF table
+ #
+ self.vapi.cli("clear trace")
+ tx = self.create_stream_labelled_ip6(self.pg1, 2, 2)
+ self.pg1.add_stream(tx)
+
+ self.pg_enable_capture(self.pg_interfaces)
+ self.pg_start()
+
+ rx = self.pg1.get_capture()
+ self.verify_capture_ip6(self.pg0, rx, tx)
+
+ def test_deag(self):
+ """ MPLS Deagg """
+
+ #
+ # A de-agg route - next-hop lookup in default table
+ #
+ route_34_eos = VppMplsRoute(self, 34, 1,
+ [VppRoutePath("0.0.0.0",
+ 0xffffffff,
+ nh_table_id=0)])
+ route_34_eos.add_vpp_config()
+
+ #
+ # ping an interface in the default table
+ # PG0 is in the default table
+ #
+ self.vapi.cli("clear trace")
+ tx = self.create_stream_labelled_ip4(self.pg0, [34], ping=1,
+ ip_itf=self.pg0)
+ self.pg0.add_stream(tx)
+
+ self.pg_enable_capture(self.pg_interfaces)
+ self.pg_start()
+
+ rx = self.pg0.get_capture()
+ self.verify_capture_ip4(self.pg0, rx, tx, ping_resp=1)
+
+ #
+ # A de-agg route - next-hop lookup in non-default table
+ #
+ route_35_eos = VppMplsRoute(self, 35, 1,
+ [VppRoutePath("0.0.0.0",
+ 0xffffffff,
+ nh_table_id=1)])
+ route_35_eos.add_vpp_config()
+
+ #
+ # ping an interface in the non-default table
+ # PG0 is in the default table. packet arrive labelled in the
+ # default table and egress unlabelled in the non-default
+ #
+ self.vapi.cli("clear trace")
+ tx = self.create_stream_labelled_ip4(
+ self.pg0, [35], ping=1, ip_itf=self.pg1)
+ self.pg0.add_stream(tx)
+
+ self.pg_enable_capture(self.pg_interfaces)
+ self.pg_start()
+
+ packet_count = self.get_packet_count_for_if_idx(self.pg0.sw_if_index)
+ rx = self.pg1.get_capture(packet_count)
+ self.verify_capture_ip4(self.pg1, rx, tx, ping_resp=1)
+
+ #
+ # Double pop
+ #
+ route_36_neos = VppMplsRoute(self, 36, 0,
+ [VppRoutePath("0.0.0.0",
+ 0xffffffff)])
+ route_36_neos.add_vpp_config()
+
+ self.vapi.cli("clear trace")
+ tx = self.create_stream_labelled_ip4(self.pg0, [36, 35],
+ ping=1, ip_itf=self.pg1)
+ self.pg0.add_stream(tx)
+
+ self.pg_enable_capture(self.pg_interfaces)
+ self.pg_start()
+
+ rx = self.pg1.get_capture(len(tx))
+ self.verify_capture_ip4(self.pg1, rx, tx, ping_resp=1)
+
+ route_36_neos.remove_vpp_config()
+ route_35_eos.remove_vpp_config()
+ route_34_eos.remove_vpp_config()
+
+ def test_interface_rx(self):
+ """ MPLS Interface Receive """
+
+ #
+ # Add a non-recursive route that will forward the traffic
+ # post-interface-rx
+ #
+ route_10_0_0_1 = VppIpRoute(self, "10.0.0.1", 32,
+ table_id=1,
+ paths=[VppRoutePath(self.pg1.remote_ip4,
+ self.pg1.sw_if_index)])
+ route_10_0_0_1.add_vpp_config()
+
+ #
+ # An interface receive label that maps traffic to RX on interface
+ # pg1
+ # by injecting the packet in on pg0, which is in table 0
+ # doing an interface-rx on pg1 and matching a route in table 1
+ # if the packet egresses, then we must have swapped to pg1
+ # so as to have matched the route in table 1
+ #
+ route_34_eos = VppMplsRoute(self, 34, 1,
+ [VppRoutePath("0.0.0.0",
+ self.pg1.sw_if_index,
+ is_interface_rx=1)])
+ route_34_eos.add_vpp_config()
+
+ #
+ # ping an interface in the default table
+ # PG0 is in the default table
+ #
+ self.vapi.cli("clear trace")
+ tx = self.create_stream_labelled_ip4(self.pg0, [34], n=257,
+ dst_ip="10.0.0.1")
+ self.pg0.add_stream(tx)
+
+ self.pg_enable_capture(self.pg_interfaces)
+ self.pg_start()
+
+ rx = self.pg1.get_capture(257)
+ self.verify_capture_ip4(self.pg1, rx, tx)
+
+ def test_mcast_mid_point(self):
+ """ MPLS Multicast Mid Point """
+
+ #
+ # Add a non-recursive route that will forward the traffic
+ # post-interface-rx
+ #
+ route_10_0_0_1 = VppIpRoute(self, "10.0.0.1", 32,
+ table_id=1,
+ paths=[VppRoutePath(self.pg1.remote_ip4,
+ self.pg1.sw_if_index)])
+ route_10_0_0_1.add_vpp_config()
+
+ #
+ # Add a mcast entry that replicate to pg2 and pg3
+ # and replicate to a interface-rx (like a bud node would)
+ #
+ route_3400_eos = VppMplsRoute(self, 3400, 1,
+ [VppRoutePath(self.pg2.remote_ip4,
+ self.pg2.sw_if_index,
+ labels=[3401]),
+ VppRoutePath(self.pg3.remote_ip4,
+ self.pg3.sw_if_index,
+ labels=[3402]),
+ VppRoutePath("0.0.0.0",
+ self.pg1.sw_if_index,
+ is_interface_rx=1)],
+ is_multicast=1)
+ route_3400_eos.add_vpp_config()
+
+ #
+ # ping an interface in the default table
+ # PG0 is in the default table
+ #
+ self.vapi.cli("clear trace")
+ tx = self.create_stream_labelled_ip4(self.pg0, [3400], n=257,
+ dst_ip="10.0.0.1")
+ self.pg0.add_stream(tx)
+
+ self.pg_enable_capture(self.pg_interfaces)
+ self.pg_start()
+
+ rx = self.pg1.get_capture(257)
+ self.verify_capture_ip4(self.pg1, rx, tx)
+
+ rx = self.pg2.get_capture(257)
+ self.verify_capture_labelled(self.pg2, rx, tx, [3401])
+ rx = self.pg3.get_capture(257)
+ self.verify_capture_labelled(self.pg3, rx, tx, [3402])
+
+ def test_mcast_head(self):
+ """ MPLS Multicast Head-end """
+
+ #
+ # Create a multicast tunnel with two replications
+ #
+ mpls_tun = VppMPLSTunnelInterface(self,
+ [VppRoutePath(self.pg2.remote_ip4,
+ self.pg2.sw_if_index,
+ labels=[42]),
+ VppRoutePath(self.pg3.remote_ip4,
+ self.pg3.sw_if_index,
+ labels=[43])],
+ is_multicast=1)
+ mpls_tun.add_vpp_config()
+ mpls_tun.admin_up()
+
+ #
+ # add an unlabelled route through the new tunnel
+ #
+ route_10_0_0_3 = VppIpRoute(self, "10.0.0.3", 32,
+ [VppRoutePath("0.0.0.0",
+ mpls_tun._sw_if_index)])
+ route_10_0_0_3.add_vpp_config()
+
+ self.vapi.cli("clear trace")
+ tx = self.create_stream_ip4(self.pg0, "10.0.0.3")
+ self.pg0.add_stream(tx)
+
+ self.pg_enable_capture(self.pg_interfaces)
+ self.pg_start()
+
+ rx = self.pg2.get_capture(257)
+ self.verify_capture_tunneled_ip4(self.pg0, rx, tx, [42])
+ rx = self.pg3.get_capture(257)
+ self.verify_capture_tunneled_ip4(self.pg0, rx, tx, [43])
+
+ #
+ # An an IP multicast route via the tunnel
+ # A (*,G).
+ # one accepting interface, pg0, 1 forwarding interface via the tunnel
+ #
+ route_232_1_1_1 = VppIpMRoute(
+ self,
+ "0.0.0.0",
+ "232.1.1.1", 32,
+ MRouteEntryFlags.MFIB_ENTRY_FLAG_NONE,
+ [VppMRoutePath(self.pg0.sw_if_index,
+ MRouteItfFlags.MFIB_ITF_FLAG_ACCEPT),
+ VppMRoutePath(mpls_tun._sw_if_index,
+ MRouteItfFlags.MFIB_ITF_FLAG_FORWARD)])
+ route_232_1_1_1.add_vpp_config()
+
+ self.vapi.cli("clear trace")
+ tx = self.create_stream_ip4(self.pg0, "232.1.1.1")
+ self.pg0.add_stream(tx)
+
+ self.pg_enable_capture(self.pg_interfaces)
+ self.pg_start()
+
+ rx = self.pg2.get_capture(257)
+ self.verify_capture_tunneled_ip4(self.pg0, rx, tx, [42])
+ rx = self.pg3.get_capture(257)
+ self.verify_capture_tunneled_ip4(self.pg0, rx, tx, [43])
+
+ def test_mcast_ip4_tail(self):
+ """ MPLS IPv4 Multicast Tail """
+
+ #
+ # Add a multicast route that will forward the traffic
+ # post-disposition
+ #
+ route_232_1_1_1 = VppIpMRoute(
+ self,
+ "0.0.0.0",
+ "232.1.1.1", 32,
+ MRouteEntryFlags.MFIB_ENTRY_FLAG_NONE,
+ table_id=1,
+ paths=[VppMRoutePath(self.pg1.sw_if_index,
+ MRouteItfFlags.MFIB_ITF_FLAG_FORWARD)])
+ route_232_1_1_1.add_vpp_config()
+
+ #
+ # An interface receive label that maps traffic to RX on interface
+ # pg1
+ # by injecting the packet in on pg0, which is in table 0
+ # doing an rpf-id and matching a route in table 1
+ # if the packet egresses, then we must have matched the route in
+ # table 1
+ #
+ route_34_eos = VppMplsRoute(self, 34, 1,
+ [VppRoutePath("0.0.0.0",
+ self.pg1.sw_if_index,
+ nh_table_id=1,
+ rpf_id=55)],
+ is_multicast=1)
+
+ route_34_eos.add_vpp_config()
+
+ #
+ # Drop due to interface lookup miss
+ #
+ self.vapi.cli("clear trace")
+ tx = self.create_stream_labelled_ip4(self.pg0, [34],
+ dst_ip="232.1.1.1", n=1)
+ self.send_and_assert_no_replies(self.pg0, tx, "RPF-ID drop none")
+
+ #
+ # set the RPF-ID of the enrtry to match the input packet's
+ #
+ route_232_1_1_1.update_rpf_id(55)
+
+ self.vapi.cli("clear trace")
+ tx = self.create_stream_labelled_ip4(self.pg0, [34],
+ dst_ip="232.1.1.1", n=257)
+ self.pg0.add_stream(tx)
+
+ self.pg_enable_capture(self.pg_interfaces)
+ self.pg_start()
+
+ rx = self.pg1.get_capture(257)
+ self.verify_capture_ip4(self.pg1, rx, tx)
+
+ #
+ # set the RPF-ID of the enrtry to not match the input packet's
+ #
+ route_232_1_1_1.update_rpf_id(56)
+ tx = self.create_stream_labelled_ip4(self.pg0, [34],
+ dst_ip="232.1.1.1")
+ self.send_and_assert_no_replies(self.pg0, tx, "RPF-ID drop 56")
+
+ def test_mcast_ip6_tail(self):
+ """ MPLS IPv6 Multicast Tail """
+
+ #
+ # Add a multicast route that will forward the traffic
+ # post-disposition
+ #
+ route_ff = VppIpMRoute(
+ self,
+ "::",
+ "ff01::1", 32,
+ MRouteEntryFlags.MFIB_ENTRY_FLAG_NONE,
+ table_id=1,
+ paths=[VppMRoutePath(self.pg1.sw_if_index,
+ MRouteItfFlags.MFIB_ITF_FLAG_FORWARD)],
+ is_ip6=1)
+ route_ff.add_vpp_config()
+
+ #
+ # An interface receive label that maps traffic to RX on interface
+ # pg1
+ # by injecting the packet in on pg0, which is in table 0
+ # doing an rpf-id and matching a route in table 1
+ # if the packet egresses, then we must have matched the route in
+ # table 1
+ #
+ route_34_eos = VppMplsRoute(
+ self, 34, 1,
+ [VppRoutePath("::",
+ self.pg1.sw_if_index,
+ nh_table_id=1,
+ rpf_id=55,
+ proto=DpoProto.DPO_PROTO_IP6)],
+ is_multicast=1)
+
+ route_34_eos.add_vpp_config()
+
+ #
+ # Drop due to interface lookup miss
+ #
+ tx = self.create_stream_labelled_ip6(self.pg0, [34], 255,
+ dst_ip="ff01::1")
+
+ #
+ # set the RPF-ID of the enrtry to match the input packet's
+ #
+ route_ff.update_rpf_id(55)
+
+ tx = self.create_stream_labelled_ip6(self.pg0, [34], 255,
+ dst_ip="ff01::1")
+ self.pg0.add_stream(tx)
+
+ self.pg_enable_capture(self.pg_interfaces)
+ self.pg_start()
+
+ rx = self.pg1.get_capture(257)
+ self.verify_capture_ip6(self.pg1, rx, tx)
+
+ #
+ # set the RPF-ID of the enrtry to not match the input packet's
+ #
+ route_ff.update_rpf_id(56)
+ tx = self.create_stream_labelled_ip6(self.pg0, [34], 225,
+ dst_ip="ff01::1")
+ self.send_and_assert_no_replies(self.pg0, tx, "RPF-ID drop 56")
+
+
+class TestMPLSDisabled(VppTestCase):
+ """ MPLS disabled """
+
+ def setUp(self):
+ super(TestMPLSDisabled, self).setUp()
+
+ # create 2 pg interfaces
+ self.create_pg_interfaces(range(2))
+
+ self.tbl = VppMplsTable(self, 0)
+ self.tbl.add_vpp_config()
+
+ # PG0 is MPLS enalbed
+ self.pg0.admin_up()
+ self.pg0.config_ip4()
+ self.pg0.resolve_arp()
+ self.pg0.enable_mpls()
+
+ # PG 1 is not MPLS enabled
+ self.pg1.admin_up()
+
+ def tearDown(self):
+ for i in self.pg_interfaces:
+ i.unconfig_ip4()
+ i.admin_down()
+
+ self.pg0.disable_mpls()
+ super(TestMPLSDisabled, self).tearDown()
+
+ def send_and_assert_no_replies(self, intf, pkts, remark):
+ intf.add_stream(pkts)
+ self.pg_enable_capture(self.pg_interfaces)
+ self.pg_start()
+ for i in self.pg_interfaces:
+ i.get_capture(0)
+ i.assert_nothing_captured(remark=remark)
+
+ def test_mpls_disabled(self):
+ """ MPLS Disabled """
+
+ tx = (Ether(src=self.pg1.remote_mac,
+ dst=self.pg1.local_mac) /
+ MPLS(label=32, ttl=64) /
+ IPv6(src="2001::1", dst=self.pg0.remote_ip6) /
+ UDP(sport=1234, dport=1234) /
+ Raw('\xa5' * 100))
+
+ #
+ # A simple MPLS xconnect - eos label in label out
+ #
+ route_32_eos = VppMplsRoute(self, 32, 1,
+ [VppRoutePath(self.pg0.remote_ip4,
+ self.pg0.sw_if_index,
+ labels=[33])])
+ route_32_eos.add_vpp_config()
+
+ #
+ # PG1 does not forward IP traffic
+ #
+ self.send_and_assert_no_replies(self.pg1, tx, "MPLS disabled")
+
+ #
+ # MPLS enable PG1
+ #
+ self.pg1.enable_mpls()
+
+ #
+ # Now we get packets through
+ #
+ self.pg1.add_stream(tx)
+ self.pg_enable_capture(self.pg_interfaces)
+ self.pg_start()
+
+ rx = self.pg0.get_capture(1)
+
+ #
+ # Disable PG1
+ #
+ self.pg1.disable_mpls()
+
+ #
+ # PG1 does not forward IP traffic
+ #
+ self.send_and_assert_no_replies(self.pg1, tx, "IPv6 disabled")
+ self.send_and_assert_no_replies(self.pg1, tx, "IPv6 disabled")
+
+
+class TestMPLSPIC(VppTestCase):
+ """ MPLS PIC edge convergence """
+
+ def setUp(self):
+ super(TestMPLSPIC, self).setUp()
+
+ # create 2 pg interfaces
+ self.create_pg_interfaces(range(4))
+
+ mpls_tbl = VppMplsTable(self, 0)
+ mpls_tbl.add_vpp_config()
+ tbl4 = VppIpTable(self, 1)
+ tbl4.add_vpp_config()
+ tbl6 = VppIpTable(self, 1, is_ip6=1)
+ tbl6.add_vpp_config()
+
+ # core links
+ self.pg0.admin_up()
+ self.pg0.config_ip4()
+ self.pg0.resolve_arp()
+ self.pg0.enable_mpls()
+ self.pg1.admin_up()
+ self.pg1.config_ip4()
+ self.pg1.resolve_arp()
+ self.pg1.enable_mpls()
+
+ # VRF (customer facing) link
+ self.pg2.admin_up()
+ self.pg2.set_table_ip4(1)
+ self.pg2.config_ip4()
+ self.pg2.resolve_arp()
+ self.pg2.set_table_ip6(1)
+ self.pg2.config_ip6()
+ self.pg2.resolve_ndp()
+ self.pg3.admin_up()
+ self.pg3.set_table_ip4(1)
+ self.pg3.config_ip4()
+ self.pg3.resolve_arp()
+ self.pg3.set_table_ip6(1)
+ self.pg3.config_ip6()
+ self.pg3.resolve_ndp()
+
+ def tearDown(self):
+ self.pg0.disable_mpls()
+ self.pg1.disable_mpls()
+ for i in self.pg_interfaces:
+ i.unconfig_ip4()
+ i.unconfig_ip6()
+ i.set_table_ip4(0)
+ i.set_table_ip6(0)
+ i.admin_down()
+ super(TestMPLSPIC, self).tearDown()
+
+ def test_mpls_ibgp_pic(self):
+ """ MPLS iBGP PIC edge convergence
+
+ 1) setup many iBGP VPN routes via a pair of iBGP peers.
+ 2) Check EMCP forwarding to these peers
+ 3) withdraw the IGP route to one of these peers.
+ 4) check forwarding continues to the remaining peer
+ """
+
+ #
+ # IGP+LDP core routes
+ #
+ core_10_0_0_45 = VppIpRoute(self, "10.0.0.45", 32,
+ [VppRoutePath(self.pg0.remote_ip4,
+ self.pg0.sw_if_index,
+ labels=[45])])
+ core_10_0_0_45.add_vpp_config()
+
+ core_10_0_0_46 = VppIpRoute(self, "10.0.0.46", 32,
+ [VppRoutePath(self.pg1.remote_ip4,
+ self.pg1.sw_if_index,
+ labels=[46])])
+ core_10_0_0_46.add_vpp_config()
+
+ #
+ # Lot's of VPN routes. We need more the 64 so VPP will build
+ # the fast convergence indirection
+ #
+ vpn_routes = []
+ pkts = []
+ for ii in range(64):
+ dst = "192.168.1.%d" % ii
+ vpn_routes.append(VppIpRoute(self, dst, 32,
+ [VppRoutePath("10.0.0.45",
+ 0xffffffff,
+ labels=[145],
+ is_resolve_host=1),
+ VppRoutePath("10.0.0.46",
+ 0xffffffff,
+ labels=[146],
+ is_resolve_host=1)],
+ table_id=1))
+ vpn_routes[ii].add_vpp_config()
+
+ pkts.append(Ether(dst=self.pg2.local_mac,
+ src=self.pg2.remote_mac) /
+ IP(src=self.pg2.remote_ip4, dst=dst) /
+ UDP(sport=1234, dport=1234) /
+ Raw('\xa5' * 100))
+
+ #
+ # Send the packet stream (one pkt to each VPN route)
+ # - expect a 50-50 split of the traffic
+ #
+ self.pg2.add_stream(pkts)
+ self.pg_enable_capture(self.pg_interfaces)
+ self.pg_start()
+
+ rx0 = self.pg0._get_capture(1)
+ rx1 = self.pg1._get_capture(1)
+
+ # not testig the LB hashing algorithm so we're not concerned
+ # with the split ratio, just as long as neither is 0
+ self.assertNotEqual(0, len(rx0))
+ self.assertNotEqual(0, len(rx1))
+
+ #
+ # use a test CLI command to stop the FIB walk process, this
+ # will prevent the FIB converging the VPN routes and thus allow
+ # us to probe the interim (psot-fail, pre-converge) state
+ #
+ self.vapi.ppcli("test fib-walk-process disable")
+
+ #
+ # Withdraw one of the IGP routes
+ #
+ core_10_0_0_46.remove_vpp_config()
+
+ #
+ # now all packets should be forwarded through the remaining peer
+ #
+ self.vapi.ppcli("clear trace")
+ self.pg2.add_stream(pkts)
+ self.pg_enable_capture(self.pg_interfaces)
+ self.pg_start()
+
+ rx0 = self.pg0.get_capture(len(pkts))
+
+ #
+ # enable the FIB walk process to converge the FIB
+ #
+ self.vapi.ppcli("test fib-walk-process enable")
+
+ #
+ # packets should still be forwarded through the remaining peer
+ #
+ self.pg2.add_stream(pkts)
+ self.pg_enable_capture(self.pg_interfaces)
+ self.pg_start()
+
+ rx0 = self.pg0.get_capture(64)
+
+ #
+ # Add the IGP route back and we return to load-balancing
+ #
+ core_10_0_0_46.add_vpp_config()
+
+ self.pg2.add_stream(pkts)
+ self.pg_enable_capture(self.pg_interfaces)
+ self.pg_start()
+
+ rx0 = self.pg0._get_capture(1)
+ rx1 = self.pg1._get_capture(1)
+ self.assertNotEqual(0, len(rx0))
+ self.assertNotEqual(0, len(rx1))
+
+ def test_mpls_ebgp_pic(self):
+ """ MPLS eBGP PIC edge convergence
+
+ 1) setup many eBGP VPN routes via a pair of eBGP peers
+ 2) Check EMCP forwarding to these peers
+ 3) withdraw one eBGP path - expect LB across remaining eBGP
+ """
+
+ #
+ # Lot's of VPN routes. We need more the 64 so VPP will build
+ # the fast convergence indirection
+ #
+ vpn_routes = []
+ vpn_bindings = []
+ pkts = []
+ for ii in range(64):
+ dst = "192.168.1.%d" % ii
+ local_label = 1600 + ii
+ vpn_routes.append(VppIpRoute(self, dst, 32,
+ [VppRoutePath(self.pg2.remote_ip4,
+ 0xffffffff,
+ nh_table_id=1,
+ is_resolve_attached=1),
+ VppRoutePath(self.pg3.remote_ip4,
+ 0xffffffff,
+ nh_table_id=1,
+ is_resolve_attached=1)],
+ table_id=1))
+ vpn_routes[ii].add_vpp_config()
+
+ vpn_bindings.append(VppMplsIpBind(self, local_label, dst, 32,
+ ip_table_id=1))
+ vpn_bindings[ii].add_vpp_config()
+
+ pkts.append(Ether(dst=self.pg0.local_mac,
+ src=self.pg0.remote_mac) /
+ MPLS(label=local_label, ttl=64) /
+ IP(src=self.pg0.remote_ip4, dst=dst) /
+ UDP(sport=1234, dport=1234) /
+ Raw('\xa5' * 100))
+
+ self.pg0.add_stream(pkts)
+ self.pg_enable_capture(self.pg_interfaces)
+ self.pg_start()
+
+ rx0 = self.pg2._get_capture(1)
+ rx1 = self.pg3._get_capture(1)
+ self.assertNotEqual(0, len(rx0))
+ self.assertNotEqual(0, len(rx1))
+
+ #
+ # use a test CLI command to stop the FIB walk process, this
+ # will prevent the FIB converging the VPN routes and thus allow
+ # us to probe the interim (psot-fail, pre-converge) state
+ #
+ self.vapi.ppcli("test fib-walk-process disable")
+
+ #
+ # withdraw the connected prefix on the interface.
+ #
+ self.pg2.unconfig_ip4()
+
+ #
+ # now all packets should be forwarded through the remaining peer
+ #
+ self.pg0.add_stream(pkts)
+ self.pg_enable_capture(self.pg_interfaces)
+ self.pg_start()
+
+ rx0 = self.pg3.get_capture(len(pkts))
+
+ #
+ # enable the FIB walk process to converge the FIB
+ #
+ self.vapi.ppcli("test fib-walk-process enable")
+ self.pg0.add_stream(pkts)
+ self.pg_enable_capture(self.pg_interfaces)
+ self.pg_start()
+
+ rx0 = self.pg3.get_capture(len(pkts))
+
+ #
+ # put the connecteds back
+ #
+ self.pg2.config_ip4()
+
+ self.pg0.add_stream(pkts)
+ self.pg_enable_capture(self.pg_interfaces)
+ self.pg_start()
+
+ rx0 = self.pg2._get_capture(1)
+ rx1 = self.pg3._get_capture(1)
+ self.assertNotEqual(0, len(rx0))
+ self.assertNotEqual(0, len(rx1))
+
+ def test_mpls_v6_ebgp_pic(self):
+ """ MPLSv6 eBGP PIC edge convergence
+
+ 1) setup many eBGP VPNv6 routes via a pair of eBGP peers
+ 2) Check EMCP forwarding to these peers
+ 3) withdraw one eBGP path - expect LB across remaining eBGP
+ """
+
+ #
+ # Lot's of VPN routes. We need more the 64 so VPP will build
+ # the fast convergence indirection
+ #
+ vpn_routes = []
+ vpn_bindings = []
+ pkts = []
+ for ii in range(64):
+ dst = "3000::%d" % ii
+ local_label = 1600 + ii
+ vpn_routes.append(VppIpRoute(
+ self, dst, 128,
+ [VppRoutePath(self.pg2.remote_ip6,
+ 0xffffffff,
+ nh_table_id=1,
+ is_resolve_attached=1,
+ proto=DpoProto.DPO_PROTO_IP6),
+ VppRoutePath(self.pg3.remote_ip6,
+ 0xffffffff,
+ nh_table_id=1,
+ proto=DpoProto.DPO_PROTO_IP6,
+ is_resolve_attached=1)],
+ table_id=1,
+ is_ip6=1))
+ vpn_routes[ii].add_vpp_config()
+
+ vpn_bindings.append(VppMplsIpBind(self, local_label, dst, 128,
+ ip_table_id=1,
+ is_ip6=1))
+ vpn_bindings[ii].add_vpp_config()
+
+ pkts.append(Ether(dst=self.pg0.local_mac,
+ src=self.pg0.remote_mac) /
+ MPLS(label=local_label, ttl=64) /
+ IPv6(src=self.pg0.remote_ip6, dst=dst) /
+ UDP(sport=1234, dport=1234) /
+ Raw('\xa5' * 100))
+
+ self.pg0.add_stream(pkts)
+ self.pg_enable_capture(self.pg_interfaces)
+ self.pg_start()
+
+ rx0 = self.pg2._get_capture(1)
+ rx1 = self.pg3._get_capture(1)
+ self.assertNotEqual(0, len(rx0))
+ self.assertNotEqual(0, len(rx1))
+
+ #
+ # use a test CLI command to stop the FIB walk process, this
+ # will prevent the FIB converging the VPN routes and thus allow
+ # us to probe the interim (psot-fail, pre-converge) state
+ #
+ self.vapi.ppcli("test fib-walk-process disable")
+
+ #
+ # withdraw the connected prefix on the interface.
+ # and shutdown the interface so the ND cache is flushed.
+ #
+ self.pg2.unconfig_ip6()
+ self.pg2.admin_down()
+
+ #
+ # now all packets should be forwarded through the remaining peer
+ #
+ self.pg0.add_stream(pkts)
+ self.pg_enable_capture(self.pg_interfaces)
+ self.pg_start()
+
+ rx0 = self.pg3.get_capture(len(pkts))
+
+ #
+ # enable the FIB walk process to converge the FIB
+ #
+ self.vapi.ppcli("test fib-walk-process enable")
+ self.pg0.add_stream(pkts)
+ self.pg_enable_capture(self.pg_interfaces)
+ self.pg_start()
+
+ rx0 = self.pg3.get_capture(len(pkts))
+
+ #
+ # put the connecteds back
+ #
+ self.pg2.admin_up()
+ self.pg2.config_ip6()
+
+ self.pg0.add_stream(pkts)
+ self.pg_enable_capture(self.pg_interfaces)
+ self.pg_start()
+
+ rx0 = self.pg2._get_capture(1)
+ rx1 = self.pg3._get_capture(1)
+ self.assertNotEqual(0, len(rx0))
+ self.assertNotEqual(0, len(rx1))
+
+
+class TestMPLSL2(VppTestCase):
+ """ MPLS-L2 """
+
+ def setUp(self):
+ super(TestMPLSL2, self).setUp()
+
+ # create 2 pg interfaces
+ self.create_pg_interfaces(range(2))
+
+ # create the default MPLS table
+ self.tables = []
+ tbl = VppMplsTable(self, 0)
+ tbl.add_vpp_config()
+ self.tables.append(tbl)
+
+ # use pg0 as the core facing interface
+ self.pg0.admin_up()
+ self.pg0.config_ip4()
+ self.pg0.resolve_arp()
+ self.pg0.enable_mpls()
+
+ # use the other 2 for customer facing L2 links
+ for i in self.pg_interfaces[1:]:
+ i.admin_up()
+
+ def tearDown(self):
+ for i in self.pg_interfaces[1:]:
+ i.admin_down()
+
+ self.pg0.disable_mpls()
+ self.pg0.unconfig_ip4()
+ self.pg0.admin_down()
+ super(TestMPLSL2, self).tearDown()
+
+ def verify_capture_tunneled_ethernet(self, capture, sent, mpls_labels,
+ ttl=255, top=None):
+ if top is None:
+ top = len(mpls_labels) - 1
+
+ capture = verify_filter(capture, sent)
+
+ self.assertEqual(len(capture), len(sent))
+
+ for i in range(len(capture)):
+ tx = sent[i]
+ rx = capture[i]
+
+ # the MPLS TTL is 255 since it enters a new tunnel
+ verify_mpls_stack(self, rx, mpls_labels, ttl, top)
+
+ tx_eth = tx[Ether]
+ rx_eth = Ether(str(rx[MPLS].payload))
+
+ self.assertEqual(rx_eth.src, tx_eth.src)
+ self.assertEqual(rx_eth.dst, tx_eth.dst)
+
+ def test_vpws(self):
+ """ Virtual Private Wire Service """
+
+ #
+ # Create an MPLS tunnel that pushes 1 label
+ #
+ mpls_tun_1 = VppMPLSTunnelInterface(self,
+ [VppRoutePath(self.pg0.remote_ip4,
+ self.pg0.sw_if_index,
+ labels=[42])],
+ is_l2=1)
+ mpls_tun_1.add_vpp_config()
+ mpls_tun_1.admin_up()
+
+ #
+ # Create a label entry to for 55 that does L2 input to the tunnel
+ #
+ route_55_eos = VppMplsRoute(
+ self, 55, 1,
+ [VppRoutePath("0.0.0.0",
+ mpls_tun_1.sw_if_index,
+ is_interface_rx=1,
+ proto=DpoProto.DPO_PROTO_ETHERNET)])
+ route_55_eos.add_vpp_config()
+
+ #
+ # Cross-connect the tunnel with one of the customers L2 interfaces
+ #
+ self.vapi.sw_interface_set_l2_xconnect(self.pg1.sw_if_index,
+ mpls_tun_1.sw_if_index,
+ enable=1)
+ self.vapi.sw_interface_set_l2_xconnect(mpls_tun_1.sw_if_index,
+ self.pg1.sw_if_index,
+ enable=1)
+
+ #
+ # inject a packet from the core
+ #
+ pcore = (Ether(dst=self.pg0.local_mac,
+ src=self.pg0.remote_mac) /
+ MPLS(label=55, ttl=64) /
+ Ether(dst="00:00:de:ad:ba:be",
+ src="00:00:de:ad:be:ef") /
+ IP(src="10.10.10.10", dst="11.11.11.11") /
+ UDP(sport=1234, dport=1234) /
+ Raw('\xa5' * 100))
+
+ self.pg0.add_stream(pcore * 65)
+ self.pg_enable_capture(self.pg_interfaces)
+ self.pg_start()
+
+ rx0 = self.pg1.get_capture(65)
+ tx = pcore[MPLS].payload
+
+ self.assertEqual(rx0[0][Ether].dst, tx[Ether].dst)
+ self.assertEqual(rx0[0][Ether].src, tx[Ether].src)
+
+ #
+ # Inject a packet from the custoer/L2 side
+ #
+ self.pg1.add_stream(tx * 65)
+ self.pg_enable_capture(self.pg_interfaces)
+ self.pg_start()
+
+ rx0 = self.pg0.get_capture(65)
+
+ self.verify_capture_tunneled_ethernet(rx0, tx*65, [42])
+
+ def test_vpls(self):
+ """ Virtual Private LAN Service """
+ #
+ # Create an L2 MPLS tunnel
+ #
+ mpls_tun = VppMPLSTunnelInterface(self,
+ [VppRoutePath(self.pg0.remote_ip4,
+ self.pg0.sw_if_index,
+ labels=[42])],
+ is_l2=1)
+ mpls_tun.add_vpp_config()
+ mpls_tun.admin_up()
+
+ #
+ # Create a label entry to for 55 that does L2 input to the tunnel
+ #
+ route_55_eos = VppMplsRoute(
+ self, 55, 1,
+ [VppRoutePath("0.0.0.0",
+ mpls_tun.sw_if_index,
+ is_interface_rx=1,
+ proto=DpoProto.DPO_PROTO_ETHERNET)])
+ route_55_eos.add_vpp_config()
+
+ #
+ # add to tunnel to the customers bridge-domain
+ #
+ self.vapi.sw_interface_set_l2_bridge(mpls_tun.sw_if_index,
+ bd_id=1)
+ self.vapi.sw_interface_set_l2_bridge(self.pg1.sw_if_index,
+ bd_id=1)
+
+ #
+ # Packet from the customer interface and from the core
+ #
+ p_cust = (Ether(dst="00:00:de:ad:ba:be",
+ src="00:00:de:ad:be:ef") /
+ IP(src="10.10.10.10", dst="11.11.11.11") /
+ UDP(sport=1234, dport=1234) /
+ Raw('\xa5' * 100))
+ p_core = (Ether(src="00:00:de:ad:ba:be",
+ dst="00:00:de:ad:be:ef") /
+ IP(dst="10.10.10.10", src="11.11.11.11") /
+ UDP(sport=1234, dport=1234) /
+ Raw('\xa5' * 100))
+
+ #
+ # The BD is learning, so send in one of each packet to learn
+ #
+ p_core_encap = (Ether(dst=self.pg0.local_mac,
+ src=self.pg0.remote_mac) /
+ MPLS(label=55, ttl=64) /
+ p_core)
+
+ self.pg1.add_stream(p_cust)
+ self.pg_enable_capture(self.pg_interfaces)
+ self.pg_start()
+ self.pg0.add_stream(p_core_encap)
+ self.pg_enable_capture(self.pg_interfaces)
+ self.pg_start()
+
+ # we've learnt this so expect it be be forwarded
+ rx0 = self.pg1.get_capture(1)
+
+ self.assertEqual(rx0[0][Ether].dst, p_core[Ether].dst)
+ self.assertEqual(rx0[0][Ether].src, p_core[Ether].src)
+
+ #
+ # now a stream in each direction
+ #
+ self.pg1.add_stream(p_cust * 65)
+ self.pg_enable_capture(self.pg_interfaces)
+ self.pg_start()
+
+ rx0 = self.pg0.get_capture(65)
+
+ self.verify_capture_tunneled_ethernet(rx0, p_cust*65, [42])
+
+ #
+ # remove interfaces from customers bridge-domain
+ #
+ self.vapi.sw_interface_set_l2_bridge(mpls_tun.sw_if_index,
+ bd_id=1,
+ enable=0)
+ self.vapi.sw_interface_set_l2_bridge(self.pg1.sw_if_index,
+ bd_id=1,
+ enable=0)
+
+if __name__ == '__main__':
+ unittest.main(testRunner=VppTestRunner)
diff --git a/test/test_nat.py b/test/test_nat.py
new file mode 100644
index 00000000..44758906
--- /dev/null
+++ b/test/test_nat.py
@@ -0,0 +1,3846 @@
+#!/usr/bin/env python
+
+import socket
+import unittest
+import struct
+
+from framework import VppTestCase, VppTestRunner, running_extended_tests
+from scapy.layers.inet import IP, TCP, UDP, ICMP
+from scapy.layers.inet import IPerror, TCPerror, UDPerror, ICMPerror
+from scapy.layers.inet6 import IPv6, ICMPv6EchoRequest, ICMPv6EchoReply
+from scapy.layers.inet6 import ICMPv6DestUnreach, IPerror6
+from scapy.layers.l2 import Ether, ARP, GRE
+from scapy.data import IP_PROTOS
+from scapy.packet import bind_layers
+from util import ppp
+from ipfix import IPFIX, Set, Template, Data, IPFIXDecoder
+from time import sleep
+from util import ip4_range
+from util import mactobinary
+
+
+class MethodHolder(VppTestCase):
+ """ NAT create capture and verify method holder """
+
+ @classmethod
+ def setUpClass(cls):
+ super(MethodHolder, cls).setUpClass()
+
+ def tearDown(self):
+ super(MethodHolder, self).tearDown()
+
+ def check_ip_checksum(self, pkt):
+ """
+ Check IP checksum of the packet
+
+ :param pkt: Packet to check IP checksum
+ """
+ new = pkt.__class__(str(pkt))
+ del new['IP'].chksum
+ new = new.__class__(str(new))
+ self.assertEqual(new['IP'].chksum, pkt['IP'].chksum)
+
+ def check_tcp_checksum(self, pkt):
+ """
+ Check TCP checksum in IP packet
+
+ :param pkt: Packet to check TCP checksum
+ """
+ new = pkt.__class__(str(pkt))
+ del new['TCP'].chksum
+ new = new.__class__(str(new))
+ self.assertEqual(new['TCP'].chksum, pkt['TCP'].chksum)
+
+ def check_udp_checksum(self, pkt):
+ """
+ Check UDP checksum in IP packet
+
+ :param pkt: Packet to check UDP checksum
+ """
+ new = pkt.__class__(str(pkt))
+ del new['UDP'].chksum
+ new = new.__class__(str(new))
+ self.assertEqual(new['UDP'].chksum, pkt['UDP'].chksum)
+
+ def check_icmp_errror_embedded(self, pkt):
+ """
+ Check ICMP error embeded packet checksum
+
+ :param pkt: Packet to check ICMP error embeded packet checksum
+ """
+ if pkt.haslayer(IPerror):
+ new = pkt.__class__(str(pkt))
+ del new['IPerror'].chksum
+ new = new.__class__(str(new))
+ self.assertEqual(new['IPerror'].chksum, pkt['IPerror'].chksum)
+
+ if pkt.haslayer(TCPerror):
+ new = pkt.__class__(str(pkt))
+ del new['TCPerror'].chksum
+ new = new.__class__(str(new))
+ self.assertEqual(new['TCPerror'].chksum, pkt['TCPerror'].chksum)
+
+ if pkt.haslayer(UDPerror):
+ if pkt['UDPerror'].chksum != 0:
+ new = pkt.__class__(str(pkt))
+ del new['UDPerror'].chksum
+ new = new.__class__(str(new))
+ self.assertEqual(new['UDPerror'].chksum,
+ pkt['UDPerror'].chksum)
+
+ if pkt.haslayer(ICMPerror):
+ del new['ICMPerror'].chksum
+ new = new.__class__(str(new))
+ self.assertEqual(new['ICMPerror'].chksum, pkt['ICMPerror'].chksum)
+
+ def check_icmp_checksum(self, pkt):
+ """
+ Check ICMP checksum in IPv4 packet
+
+ :param pkt: Packet to check ICMP checksum
+ """
+ new = pkt.__class__(str(pkt))
+ del new['ICMP'].chksum
+ new = new.__class__(str(new))
+ self.assertEqual(new['ICMP'].chksum, pkt['ICMP'].chksum)
+ if pkt.haslayer(IPerror):
+ self.check_icmp_errror_embedded(pkt)
+
+ def check_icmpv6_checksum(self, pkt):
+ """
+ Check ICMPv6 checksum in IPv4 packet
+
+ :param pkt: Packet to check ICMPv6 checksum
+ """
+ new = pkt.__class__(str(pkt))
+ if pkt.haslayer(ICMPv6DestUnreach):
+ del new['ICMPv6DestUnreach'].cksum
+ new = new.__class__(str(new))
+ self.assertEqual(new['ICMPv6DestUnreach'].cksum,
+ pkt['ICMPv6DestUnreach'].cksum)
+ self.check_icmp_errror_embedded(pkt)
+ if pkt.haslayer(ICMPv6EchoRequest):
+ del new['ICMPv6EchoRequest'].cksum
+ new = new.__class__(str(new))
+ self.assertEqual(new['ICMPv6EchoRequest'].cksum,
+ pkt['ICMPv6EchoRequest'].cksum)
+ if pkt.haslayer(ICMPv6EchoReply):
+ del new['ICMPv6EchoReply'].cksum
+ new = new.__class__(str(new))
+ self.assertEqual(new['ICMPv6EchoReply'].cksum,
+ pkt['ICMPv6EchoReply'].cksum)
+
+ def create_stream_in(self, in_if, out_if, ttl=64):
+ """
+ Create packet stream for inside network
+
+ :param in_if: Inside interface
+ :param out_if: Outside interface
+ :param ttl: TTL of generated packets
+ """
+ pkts = []
+ # TCP
+ p = (Ether(dst=in_if.local_mac, src=in_if.remote_mac) /
+ IP(src=in_if.remote_ip4, dst=out_if.remote_ip4, ttl=ttl) /
+ TCP(sport=self.tcp_port_in, dport=20))
+ pkts.append(p)
+
+ # UDP
+ p = (Ether(dst=in_if.local_mac, src=in_if.remote_mac) /
+ IP(src=in_if.remote_ip4, dst=out_if.remote_ip4, ttl=ttl) /
+ UDP(sport=self.udp_port_in, dport=20))
+ pkts.append(p)
+
+ # ICMP
+ p = (Ether(dst=in_if.local_mac, src=in_if.remote_mac) /
+ IP(src=in_if.remote_ip4, dst=out_if.remote_ip4, ttl=ttl) /
+ ICMP(id=self.icmp_id_in, type='echo-request'))
+ pkts.append(p)
+
+ return pkts
+
+ def compose_ip6(self, ip4, pref, plen):
+ """
+ Compose IPv4-embedded IPv6 addresses
+
+ :param ip4: IPv4 address
+ :param pref: IPv6 prefix
+ :param plen: IPv6 prefix length
+ :returns: IPv4-embedded IPv6 addresses
+ """
+ pref_n = list(socket.inet_pton(socket.AF_INET6, pref))
+ ip4_n = list(socket.inet_pton(socket.AF_INET, ip4))
+ if plen == 32:
+ pref_n[4] = ip4_n[0]
+ pref_n[5] = ip4_n[1]
+ pref_n[6] = ip4_n[2]
+ pref_n[7] = ip4_n[3]
+ elif plen == 40:
+ pref_n[5] = ip4_n[0]
+ pref_n[6] = ip4_n[1]
+ pref_n[7] = ip4_n[2]
+ pref_n[9] = ip4_n[3]
+ elif plen == 48:
+ pref_n[6] = ip4_n[0]
+ pref_n[7] = ip4_n[1]
+ pref_n[9] = ip4_n[2]
+ pref_n[10] = ip4_n[3]
+ elif plen == 56:
+ pref_n[7] = ip4_n[0]
+ pref_n[9] = ip4_n[1]
+ pref_n[10] = ip4_n[2]
+ pref_n[11] = ip4_n[3]
+ elif plen == 64:
+ pref_n[9] = ip4_n[0]
+ pref_n[10] = ip4_n[1]
+ pref_n[11] = ip4_n[2]
+ pref_n[12] = ip4_n[3]
+ elif plen == 96:
+ pref_n[12] = ip4_n[0]
+ pref_n[13] = ip4_n[1]
+ pref_n[14] = ip4_n[2]
+ pref_n[15] = ip4_n[3]
+ return socket.inet_ntop(socket.AF_INET6, ''.join(pref_n))
+
+ def create_stream_in_ip6(self, in_if, out_if, hlim=64, pref=None, plen=0):
+ """
+ Create IPv6 packet stream for inside network
+
+ :param in_if: Inside interface
+ :param out_if: Outside interface
+ :param ttl: Hop Limit of generated packets
+ :param pref: NAT64 prefix
+ :param plen: NAT64 prefix length
+ """
+ pkts = []
+ if pref is None:
+ dst = ''.join(['64:ff9b::', out_if.remote_ip4])
+ else:
+ dst = self.compose_ip6(out_if.remote_ip4, pref, plen)
+
+ # TCP
+ p = (Ether(dst=in_if.local_mac, src=in_if.remote_mac) /
+ IPv6(src=in_if.remote_ip6, dst=dst, hlim=hlim) /
+ TCP(sport=self.tcp_port_in, dport=20))
+ pkts.append(p)
+
+ # UDP
+ p = (Ether(dst=in_if.local_mac, src=in_if.remote_mac) /
+ IPv6(src=in_if.remote_ip6, dst=dst, hlim=hlim) /
+ UDP(sport=self.udp_port_in, dport=20))
+ pkts.append(p)
+
+ # ICMP
+ p = (Ether(dst=in_if.local_mac, src=in_if.remote_mac) /
+ IPv6(src=in_if.remote_ip6, dst=dst, hlim=hlim) /
+ ICMPv6EchoRequest(id=self.icmp_id_in))
+ pkts.append(p)
+
+ return pkts
+
+ def create_stream_out(self, out_if, dst_ip=None, ttl=64):
+ """
+ Create packet stream for outside network
+
+ :param out_if: Outside interface
+ :param dst_ip: Destination IP address (Default use global NAT address)
+ :param ttl: TTL of generated packets
+ """
+ if dst_ip is None:
+ dst_ip = self.nat_addr
+ pkts = []
+ # TCP
+ p = (Ether(dst=out_if.local_mac, src=out_if.remote_mac) /
+ IP(src=out_if.remote_ip4, dst=dst_ip, ttl=ttl) /
+ TCP(dport=self.tcp_port_out, sport=20))
+ pkts.append(p)
+
+ # UDP
+ p = (Ether(dst=out_if.local_mac, src=out_if.remote_mac) /
+ IP(src=out_if.remote_ip4, dst=dst_ip, ttl=ttl) /
+ UDP(dport=self.udp_port_out, sport=20))
+ pkts.append(p)
+
+ # ICMP
+ p = (Ether(dst=out_if.local_mac, src=out_if.remote_mac) /
+ IP(src=out_if.remote_ip4, dst=dst_ip, ttl=ttl) /
+ ICMP(id=self.icmp_id_out, type='echo-reply'))
+ pkts.append(p)
+
+ return pkts
+
+ def verify_capture_out(self, capture, nat_ip=None, same_port=False,
+ packet_num=3, dst_ip=None):
+ """
+ Verify captured packets on outside network
+
+ :param capture: Captured packets
+ :param nat_ip: Translated IP address (Default use global NAT address)
+ :param same_port: Sorce port number is not translated (Default False)
+ :param packet_num: Expected number of packets (Default 3)
+ :param dst_ip: Destination IP address (Default do not verify)
+ """
+ if nat_ip is None:
+ nat_ip = self.nat_addr
+ self.assertEqual(packet_num, len(capture))
+ for packet in capture:
+ try:
+ self.check_ip_checksum(packet)
+ self.assertEqual(packet[IP].src, nat_ip)
+ if dst_ip is not None:
+ self.assertEqual(packet[IP].dst, dst_ip)
+ if packet.haslayer(TCP):
+ if same_port:
+ self.assertEqual(packet[TCP].sport, self.tcp_port_in)
+ else:
+ self.assertNotEqual(
+ packet[TCP].sport, self.tcp_port_in)
+ self.tcp_port_out = packet[TCP].sport
+ self.check_tcp_checksum(packet)
+ elif packet.haslayer(UDP):
+ if same_port:
+ self.assertEqual(packet[UDP].sport, self.udp_port_in)
+ else:
+ self.assertNotEqual(
+ packet[UDP].sport, self.udp_port_in)
+ self.udp_port_out = packet[UDP].sport
+ else:
+ if same_port:
+ self.assertEqual(packet[ICMP].id, self.icmp_id_in)
+ else:
+ self.assertNotEqual(packet[ICMP].id, self.icmp_id_in)
+ self.icmp_id_out = packet[ICMP].id
+ self.check_icmp_checksum(packet)
+ except:
+ self.logger.error(ppp("Unexpected or invalid packet "
+ "(outside network):", packet))
+ raise
+
+ def verify_capture_in(self, capture, in_if, packet_num=3):
+ """
+ Verify captured packets on inside network
+
+ :param capture: Captured packets
+ :param in_if: Inside interface
+ :param packet_num: Expected number of packets (Default 3)
+ """
+ self.assertEqual(packet_num, len(capture))
+ for packet in capture:
+ try:
+ self.check_ip_checksum(packet)
+ self.assertEqual(packet[IP].dst, in_if.remote_ip4)
+ if packet.haslayer(TCP):
+ self.assertEqual(packet[TCP].dport, self.tcp_port_in)
+ self.check_tcp_checksum(packet)
+ elif packet.haslayer(UDP):
+ self.assertEqual(packet[UDP].dport, self.udp_port_in)
+ else:
+ self.assertEqual(packet[ICMP].id, self.icmp_id_in)
+ self.check_icmp_checksum(packet)
+ except:
+ self.logger.error(ppp("Unexpected or invalid packet "
+ "(inside network):", packet))
+ raise
+
+ def verify_capture_in_ip6(self, capture, src_ip, dst_ip, packet_num=3):
+ """
+ Verify captured IPv6 packets on inside network
+
+ :param capture: Captured packets
+ :param src_ip: Source IP
+ :param dst_ip: Destination IP address
+ :param packet_num: Expected number of packets (Default 3)
+ """
+ self.assertEqual(packet_num, len(capture))
+ for packet in capture:
+ try:
+ self.assertEqual(packet[IPv6].src, src_ip)
+ self.assertEqual(packet[IPv6].dst, dst_ip)
+ if packet.haslayer(TCP):
+ self.assertEqual(packet[TCP].dport, self.tcp_port_in)
+ self.check_tcp_checksum(packet)
+ elif packet.haslayer(UDP):
+ self.assertEqual(packet[UDP].dport, self.udp_port_in)
+ self.check_udp_checksum(packet)
+ else:
+ self.assertEqual(packet[ICMPv6EchoReply].id,
+ self.icmp_id_in)
+ self.check_icmpv6_checksum(packet)
+ except:
+ self.logger.error(ppp("Unexpected or invalid packet "
+ "(inside network):", packet))
+ raise
+
+ def verify_capture_no_translation(self, capture, ingress_if, egress_if):
+ """
+ Verify captured packet that don't have to be translated
+
+ :param capture: Captured packets
+ :param ingress_if: Ingress interface
+ :param egress_if: Egress interface
+ """
+ for packet in capture:
+ try:
+ self.assertEqual(packet[IP].src, ingress_if.remote_ip4)
+ self.assertEqual(packet[IP].dst, egress_if.remote_ip4)
+ if packet.haslayer(TCP):
+ self.assertEqual(packet[TCP].sport, self.tcp_port_in)
+ elif packet.haslayer(UDP):
+ self.assertEqual(packet[UDP].sport, self.udp_port_in)
+ else:
+ self.assertEqual(packet[ICMP].id, self.icmp_id_in)
+ except:
+ self.logger.error(ppp("Unexpected or invalid packet "
+ "(inside network):", packet))
+ raise
+
+ def verify_capture_out_with_icmp_errors(self, capture, src_ip=None,
+ packet_num=3, icmp_type=11):
+ """
+ Verify captured packets with ICMP errors on outside network
+
+ :param capture: Captured packets
+ :param src_ip: Translated IP address or IP address of VPP
+ (Default use global NAT address)
+ :param packet_num: Expected number of packets (Default 3)
+ :param icmp_type: Type of error ICMP packet
+ we are expecting (Default 11)
+ """
+ if src_ip is None:
+ src_ip = self.nat_addr
+ self.assertEqual(packet_num, len(capture))
+ for packet in capture:
+ try:
+ self.assertEqual(packet[IP].src, src_ip)
+ self.assertTrue(packet.haslayer(ICMP))
+ icmp = packet[ICMP]
+ self.assertEqual(icmp.type, icmp_type)
+ self.assertTrue(icmp.haslayer(IPerror))
+ inner_ip = icmp[IPerror]
+ if inner_ip.haslayer(TCPerror):
+ self.assertEqual(inner_ip[TCPerror].dport,
+ self.tcp_port_out)
+ elif inner_ip.haslayer(UDPerror):
+ self.assertEqual(inner_ip[UDPerror].dport,
+ self.udp_port_out)
+ else:
+ self.assertEqual(inner_ip[ICMPerror].id, self.icmp_id_out)
+ except:
+ self.logger.error(ppp("Unexpected or invalid packet "
+ "(outside network):", packet))
+ raise
+
+ def verify_capture_in_with_icmp_errors(self, capture, in_if, packet_num=3,
+ icmp_type=11):
+ """
+ Verify captured packets with ICMP errors on inside network
+
+ :param capture: Captured packets
+ :param in_if: Inside interface
+ :param packet_num: Expected number of packets (Default 3)
+ :param icmp_type: Type of error ICMP packet
+ we are expecting (Default 11)
+ """
+ self.assertEqual(packet_num, len(capture))
+ for packet in capture:
+ try:
+ self.assertEqual(packet[IP].dst, in_if.remote_ip4)
+ self.assertTrue(packet.haslayer(ICMP))
+ icmp = packet[ICMP]
+ self.assertEqual(icmp.type, icmp_type)
+ self.assertTrue(icmp.haslayer(IPerror))
+ inner_ip = icmp[IPerror]
+ if inner_ip.haslayer(TCPerror):
+ self.assertEqual(inner_ip[TCPerror].sport,
+ self.tcp_port_in)
+ elif inner_ip.haslayer(UDPerror):
+ self.assertEqual(inner_ip[UDPerror].sport,
+ self.udp_port_in)
+ else:
+ self.assertEqual(inner_ip[ICMPerror].id, self.icmp_id_in)
+ except:
+ self.logger.error(ppp("Unexpected or invalid packet "
+ "(inside network):", packet))
+ raise
+
+ def verify_ipfix_nat44_ses(self, data):
+ """
+ Verify IPFIX NAT44 session create/delete event
+
+ :param data: Decoded IPFIX data records
+ """
+ nat44_ses_create_num = 0
+ nat44_ses_delete_num = 0
+ self.assertEqual(6, len(data))
+ for record in data:
+ # natEvent
+ self.assertIn(ord(record[230]), [4, 5])
+ if ord(record[230]) == 4:
+ nat44_ses_create_num += 1
+ else:
+ nat44_ses_delete_num += 1
+ # sourceIPv4Address
+ self.assertEqual(self.pg0.remote_ip4n, record[8])
+ # postNATSourceIPv4Address
+ self.assertEqual(socket.inet_pton(socket.AF_INET, self.nat_addr),
+ record[225])
+ # ingressVRFID
+ self.assertEqual(struct.pack("!I", 0), record[234])
+ # protocolIdentifier/sourceTransportPort/postNAPTSourceTransportPort
+ if IP_PROTOS.icmp == ord(record[4]):
+ self.assertEqual(struct.pack("!H", self.icmp_id_in), record[7])
+ self.assertEqual(struct.pack("!H", self.icmp_id_out),
+ record[227])
+ elif IP_PROTOS.tcp == ord(record[4]):
+ self.assertEqual(struct.pack("!H", self.tcp_port_in),
+ record[7])
+ self.assertEqual(struct.pack("!H", self.tcp_port_out),
+ record[227])
+ elif IP_PROTOS.udp == ord(record[4]):
+ self.assertEqual(struct.pack("!H", self.udp_port_in),
+ record[7])
+ self.assertEqual(struct.pack("!H", self.udp_port_out),
+ record[227])
+ else:
+ self.fail("Invalid protocol")
+ self.assertEqual(3, nat44_ses_create_num)
+ self.assertEqual(3, nat44_ses_delete_num)
+
+ def verify_ipfix_addr_exhausted(self, data):
+ """
+ Verify IPFIX NAT addresses event
+
+ :param data: Decoded IPFIX data records
+ """
+ self.assertEqual(1, len(data))
+ record = data[0]
+ # natEvent
+ self.assertEqual(ord(record[230]), 3)
+ # natPoolID
+ self.assertEqual(struct.pack("!I", 0), record[283])
+
+
+class TestNAT44(MethodHolder):
+ """ NAT44 Test Cases """
+
+ @classmethod
+ def setUpClass(cls):
+ super(TestNAT44, cls).setUpClass()
+
+ try:
+ cls.tcp_port_in = 6303
+ cls.tcp_port_out = 6303
+ cls.udp_port_in = 6304
+ cls.udp_port_out = 6304
+ cls.icmp_id_in = 6305
+ cls.icmp_id_out = 6305
+ cls.nat_addr = '10.0.0.3'
+ cls.ipfix_src_port = 4739
+ cls.ipfix_domain_id = 1
+
+ cls.create_pg_interfaces(range(9))
+ cls.interfaces = list(cls.pg_interfaces[0:4])
+
+ for i in cls.interfaces:
+ i.admin_up()
+ i.config_ip4()
+ i.resolve_arp()
+
+ cls.pg0.generate_remote_hosts(3)
+ cls.pg0.configure_ipv4_neighbors()
+
+ cls.overlapping_interfaces = list(list(cls.pg_interfaces[4:7]))
+ cls.vapi.ip_table_add_del(10, is_add=1)
+ cls.vapi.ip_table_add_del(20, is_add=1)
+
+ cls.pg4._local_ip4 = "172.16.255.1"
+ cls.pg4._local_ip4n = socket.inet_pton(socket.AF_INET, i.local_ip4)
+ cls.pg4._remote_hosts[0]._ip4 = "172.16.255.2"
+ cls.pg4.set_table_ip4(10)
+ cls.pg5._local_ip4 = "172.17.255.3"
+ cls.pg5._local_ip4n = socket.inet_pton(socket.AF_INET, i.local_ip4)
+ cls.pg5._remote_hosts[0]._ip4 = "172.17.255.4"
+ cls.pg5.set_table_ip4(10)
+ cls.pg6._local_ip4 = "172.16.255.1"
+ cls.pg6._local_ip4n = socket.inet_pton(socket.AF_INET, i.local_ip4)
+ cls.pg6._remote_hosts[0]._ip4 = "172.16.255.2"
+ cls.pg6.set_table_ip4(20)
+ for i in cls.overlapping_interfaces:
+ i.config_ip4()
+ i.admin_up()
+ i.resolve_arp()
+
+ cls.pg7.admin_up()
+ cls.pg8.admin_up()
+
+ except Exception:
+ super(TestNAT44, cls).tearDownClass()
+ raise
+
+ def clear_nat44(self):
+ """
+ Clear NAT44 configuration.
+ """
+ # I found no elegant way to do this
+ self.vapi.ip_add_del_route(dst_address=self.pg7.remote_ip4n,
+ dst_address_length=32,
+ next_hop_address=self.pg7.remote_ip4n,
+ next_hop_sw_if_index=self.pg7.sw_if_index,
+ is_add=0)
+ self.vapi.ip_add_del_route(dst_address=self.pg8.remote_ip4n,
+ dst_address_length=32,
+ next_hop_address=self.pg8.remote_ip4n,
+ next_hop_sw_if_index=self.pg8.sw_if_index,
+ is_add=0)
+
+ for intf in [self.pg7, self.pg8]:
+ neighbors = self.vapi.ip_neighbor_dump(intf.sw_if_index)
+ for n in neighbors:
+ self.vapi.ip_neighbor_add_del(intf.sw_if_index,
+ n.mac_address,
+ n.ip_address,
+ is_add=0)
+
+ if self.pg7.has_ip4_config:
+ self.pg7.unconfig_ip4()
+
+ interfaces = self.vapi.nat44_interface_addr_dump()
+ for intf in interfaces:
+ self.vapi.nat44_add_interface_addr(intf.sw_if_index, is_add=0)
+
+ self.vapi.nat_ipfix(enable=0, src_port=self.ipfix_src_port,
+ domain_id=self.ipfix_domain_id)
+ self.ipfix_src_port = 4739
+ self.ipfix_domain_id = 1
+
+ interfaces = self.vapi.nat44_interface_dump()
+ for intf in interfaces:
+ self.vapi.nat44_interface_add_del_feature(intf.sw_if_index,
+ intf.is_inside,
+ is_add=0)
+
+ interfaces = self.vapi.nat44_interface_output_feature_dump()
+ for intf in interfaces:
+ self.vapi.nat44_interface_add_del_output_feature(intf.sw_if_index,
+ intf.is_inside,
+ is_add=0)
+
+ static_mappings = self.vapi.nat44_static_mapping_dump()
+ for sm in static_mappings:
+ self.vapi.nat44_add_del_static_mapping(
+ sm.local_ip_address,
+ sm.external_ip_address,
+ local_port=sm.local_port,
+ external_port=sm.external_port,
+ addr_only=sm.addr_only,
+ vrf_id=sm.vrf_id,
+ protocol=sm.protocol,
+ is_add=0)
+
+ lb_static_mappings = self.vapi.nat44_lb_static_mapping_dump()
+ for lb_sm in lb_static_mappings:
+ self.vapi.nat44_add_del_lb_static_mapping(
+ lb_sm.external_addr,
+ lb_sm.external_port,
+ lb_sm.protocol,
+ lb_sm.vrf_id,
+ is_add=0,
+ local_num=0,
+ locals=[])
+
+ adresses = self.vapi.nat44_address_dump()
+ for addr in adresses:
+ self.vapi.nat44_add_del_address_range(addr.ip_address,
+ addr.ip_address,
+ is_add=0)
+
+ def nat44_add_static_mapping(self, local_ip, external_ip='0.0.0.0',
+ local_port=0, external_port=0, vrf_id=0,
+ is_add=1, external_sw_if_index=0xFFFFFFFF,
+ proto=0):
+ """
+ Add/delete NAT44 static mapping
+
+ :param local_ip: Local IP address
+ :param external_ip: External IP address
+ :param local_port: Local port number (Optional)
+ :param external_port: External port number (Optional)
+ :param vrf_id: VRF ID (Default 0)
+ :param is_add: 1 if add, 0 if delete (Default add)
+ :param external_sw_if_index: External interface instead of IP address
+ :param proto: IP protocol (Mandatory if port specified)
+ """
+ addr_only = 1
+ if local_port and external_port:
+ addr_only = 0
+ l_ip = socket.inet_pton(socket.AF_INET, local_ip)
+ e_ip = socket.inet_pton(socket.AF_INET, external_ip)
+ self.vapi.nat44_add_del_static_mapping(
+ l_ip,
+ e_ip,
+ external_sw_if_index,
+ local_port,
+ external_port,
+ addr_only,
+ vrf_id,
+ proto,
+ is_add)
+
+ def nat44_add_address(self, ip, is_add=1, vrf_id=0xFFFFFFFF):
+ """
+ Add/delete NAT44 address
+
+ :param ip: IP address
+ :param is_add: 1 if add, 0 if delete (Default add)
+ """
+ nat_addr = socket.inet_pton(socket.AF_INET, ip)
+ self.vapi.nat44_add_del_address_range(nat_addr, nat_addr, is_add,
+ vrf_id=vrf_id)
+
+ def test_dynamic(self):
+ """ NAT44 dynamic translation test """
+
+ self.nat44_add_address(self.nat_addr)
+ self.vapi.nat44_interface_add_del_feature(self.pg0.sw_if_index)
+ self.vapi.nat44_interface_add_del_feature(self.pg1.sw_if_index,
+ is_inside=0)
+
+ # in2out
+ pkts = self.create_stream_in(self.pg0, self.pg1)
+ self.pg0.add_stream(pkts)
+ self.pg_enable_capture(self.pg_interfaces)
+ self.pg_start()
+ capture = self.pg1.get_capture(len(pkts))
+ self.verify_capture_out(capture)
+
+ # out2in
+ pkts = self.create_stream_out(self.pg1)
+ self.pg1.add_stream(pkts)
+ self.pg_enable_capture(self.pg_interfaces)
+ self.pg_start()
+ capture = self.pg0.get_capture(len(pkts))
+ self.verify_capture_in(capture, self.pg0)
+
+ def test_dynamic_icmp_errors_in2out_ttl_1(self):
+ """ NAT44 handling of client packets with TTL=1 """
+
+ self.nat44_add_address(self.nat_addr)
+ self.vapi.nat44_interface_add_del_feature(self.pg0.sw_if_index)
+ self.vapi.nat44_interface_add_del_feature(self.pg1.sw_if_index,
+ is_inside=0)
+
+ # Client side - generate traffic
+ pkts = self.create_stream_in(self.pg0, self.pg1, ttl=1)
+ self.pg0.add_stream(pkts)
+ self.pg_enable_capture(self.pg_interfaces)
+ self.pg_start()
+
+ # Client side - verify ICMP type 11 packets
+ capture = self.pg0.get_capture(len(pkts))
+ self.verify_capture_in_with_icmp_errors(capture, self.pg0)
+
+ def test_dynamic_icmp_errors_out2in_ttl_1(self):
+ """ NAT44 handling of server packets with TTL=1 """
+
+ self.nat44_add_address(self.nat_addr)
+ self.vapi.nat44_interface_add_del_feature(self.pg0.sw_if_index)
+ self.vapi.nat44_interface_add_del_feature(self.pg1.sw_if_index,
+ is_inside=0)
+
+ # Client side - create sessions
+ pkts = self.create_stream_in(self.pg0, self.pg1)
+ self.pg0.add_stream(pkts)
+ self.pg_enable_capture(self.pg_interfaces)
+ self.pg_start()
+
+ # Server side - generate traffic
+ capture = self.pg1.get_capture(len(pkts))
+ self.verify_capture_out(capture)
+ pkts = self.create_stream_out(self.pg1, ttl=1)
+ self.pg1.add_stream(pkts)
+ self.pg_enable_capture(self.pg_interfaces)
+ self.pg_start()
+
+ # Server side - verify ICMP type 11 packets
+ capture = self.pg1.get_capture(len(pkts))
+ self.verify_capture_out_with_icmp_errors(capture,
+ src_ip=self.pg1.local_ip4)
+
+ def test_dynamic_icmp_errors_in2out_ttl_2(self):
+ """ NAT44 handling of error responses to client packets with TTL=2 """
+
+ self.nat44_add_address(self.nat_addr)
+ self.vapi.nat44_interface_add_del_feature(self.pg0.sw_if_index)
+ self.vapi.nat44_interface_add_del_feature(self.pg1.sw_if_index,
+ is_inside=0)
+
+ # Client side - generate traffic
+ pkts = self.create_stream_in(self.pg0, self.pg1, ttl=2)
+ self.pg0.add_stream(pkts)
+ self.pg_enable_capture(self.pg_interfaces)
+ self.pg_start()
+
+ # Server side - simulate ICMP type 11 response
+ capture = self.pg1.get_capture(len(pkts))
+ pkts = [Ether(dst=self.pg1.local_mac, src=self.pg1.remote_mac) /
+ IP(src=self.pg1.remote_ip4, dst=self.nat_addr) /
+ ICMP(type=11) / packet[IP] for packet in capture]
+ self.pg1.add_stream(pkts)
+ self.pg_enable_capture(self.pg_interfaces)
+ self.pg_start()
+
+ # Client side - verify ICMP type 11 packets
+ capture = self.pg0.get_capture(len(pkts))
+ self.verify_capture_in_with_icmp_errors(capture, self.pg0)
+
+ def test_dynamic_icmp_errors_out2in_ttl_2(self):
+ """ NAT44 handling of error responses to server packets with TTL=2 """
+
+ self.nat44_add_address(self.nat_addr)
+ self.vapi.nat44_interface_add_del_feature(self.pg0.sw_if_index)
+ self.vapi.nat44_interface_add_del_feature(self.pg1.sw_if_index,
+ is_inside=0)
+
+ # Client side - create sessions
+ pkts = self.create_stream_in(self.pg0, self.pg1)
+ self.pg0.add_stream(pkts)
+ self.pg_enable_capture(self.pg_interfaces)
+ self.pg_start()
+
+ # Server side - generate traffic
+ capture = self.pg1.get_capture(len(pkts))
+ self.verify_capture_out(capture)
+ pkts = self.create_stream_out(self.pg1, ttl=2)
+ self.pg1.add_stream(pkts)
+ self.pg_enable_capture(self.pg_interfaces)
+ self.pg_start()
+
+ # Client side - simulate ICMP type 11 response
+ capture = self.pg0.get_capture(len(pkts))
+ pkts = [Ether(dst=self.pg0.local_mac, src=self.pg0.remote_mac) /
+ IP(src=self.pg0.remote_ip4, dst=self.pg1.remote_ip4) /
+ ICMP(type=11) / packet[IP] for packet in capture]
+ self.pg0.add_stream(pkts)
+ self.pg_enable_capture(self.pg_interfaces)
+ self.pg_start()
+
+ # Server side - verify ICMP type 11 packets
+ capture = self.pg1.get_capture(len(pkts))
+ self.verify_capture_out_with_icmp_errors(capture)
+
+ def test_ping_out_interface_from_outside(self):
+ """ Ping NAT44 out interface from outside network """
+
+ self.nat44_add_address(self.nat_addr)
+ self.vapi.nat44_interface_add_del_feature(self.pg0.sw_if_index)
+ self.vapi.nat44_interface_add_del_feature(self.pg1.sw_if_index,
+ is_inside=0)
+
+ p = (Ether(dst=self.pg1.local_mac, src=self.pg1.remote_mac) /
+ IP(src=self.pg1.remote_ip4, dst=self.pg1.local_ip4) /
+ ICMP(id=self.icmp_id_out, type='echo-request'))
+ pkts = [p]
+ self.pg1.add_stream(pkts)
+ self.pg_enable_capture(self.pg_interfaces)
+ self.pg_start()
+ capture = self.pg1.get_capture(len(pkts))
+ self.assertEqual(1, len(capture))
+ packet = capture[0]
+ try:
+ self.assertEqual(packet[IP].src, self.pg1.local_ip4)
+ self.assertEqual(packet[IP].dst, self.pg1.remote_ip4)
+ self.assertEqual(packet[ICMP].id, self.icmp_id_in)
+ self.assertEqual(packet[ICMP].type, 0) # echo reply
+ except:
+ self.logger.error(ppp("Unexpected or invalid packet "
+ "(outside network):", packet))
+ raise
+
+ def test_ping_internal_host_from_outside(self):
+ """ Ping internal host from outside network """
+
+ self.nat44_add_static_mapping(self.pg0.remote_ip4, self.nat_addr)
+ self.vapi.nat44_interface_add_del_feature(self.pg0.sw_if_index)
+ self.vapi.nat44_interface_add_del_feature(self.pg1.sw_if_index,
+ is_inside=0)
+
+ # out2in
+ pkt = (Ether(dst=self.pg1.local_mac, src=self.pg1.remote_mac) /
+ IP(src=self.pg1.remote_ip4, dst=self.nat_addr, ttl=64) /
+ ICMP(id=self.icmp_id_out, type='echo-request'))
+ self.pg1.add_stream(pkt)
+ self.pg_enable_capture(self.pg_interfaces)
+ self.pg_start()
+ capture = self.pg0.get_capture(1)
+ self.verify_capture_in(capture, self.pg0, packet_num=1)
+ self.assert_equal(capture[0][IP].proto, IP_PROTOS.icmp)
+
+ # in2out
+ pkt = (Ether(dst=self.pg0.local_mac, src=self.pg0.remote_mac) /
+ IP(src=self.pg0.remote_ip4, dst=self.pg1.remote_ip4, ttl=64) /
+ ICMP(id=self.icmp_id_in, type='echo-reply'))
+ self.pg0.add_stream(pkt)
+ self.pg_enable_capture(self.pg_interfaces)
+ self.pg_start()
+ capture = self.pg1.get_capture(1)
+ self.verify_capture_out(capture, same_port=True, packet_num=1)
+ self.assert_equal(capture[0][IP].proto, IP_PROTOS.icmp)
+
+ def test_static_in(self):
+ """ 1:1 NAT initialized from inside network """
+
+ nat_ip = "10.0.0.10"
+ self.tcp_port_out = 6303
+ self.udp_port_out = 6304
+ self.icmp_id_out = 6305
+
+ self.nat44_add_static_mapping(self.pg0.remote_ip4, nat_ip)
+ self.vapi.nat44_interface_add_del_feature(self.pg0.sw_if_index)
+ self.vapi.nat44_interface_add_del_feature(self.pg1.sw_if_index,
+ is_inside=0)
+
+ # in2out
+ pkts = self.create_stream_in(self.pg0, self.pg1)
+ self.pg0.add_stream(pkts)
+ self.pg_enable_capture(self.pg_interfaces)
+ self.pg_start()
+ capture = self.pg1.get_capture(len(pkts))
+ self.verify_capture_out(capture, nat_ip, True)
+
+ # out2in
+ pkts = self.create_stream_out(self.pg1, nat_ip)
+ self.pg1.add_stream(pkts)
+ self.pg_enable_capture(self.pg_interfaces)
+ self.pg_start()
+ capture = self.pg0.get_capture(len(pkts))
+ self.verify_capture_in(capture, self.pg0)
+
+ def test_static_out(self):
+ """ 1:1 NAT initialized from outside network """
+
+ nat_ip = "10.0.0.20"
+ self.tcp_port_out = 6303
+ self.udp_port_out = 6304
+ self.icmp_id_out = 6305
+
+ self.nat44_add_static_mapping(self.pg0.remote_ip4, nat_ip)
+ self.vapi.nat44_interface_add_del_feature(self.pg0.sw_if_index)
+ self.vapi.nat44_interface_add_del_feature(self.pg1.sw_if_index,
+ is_inside=0)
+
+ # out2in
+ pkts = self.create_stream_out(self.pg1, nat_ip)
+ self.pg1.add_stream(pkts)
+ self.pg_enable_capture(self.pg_interfaces)
+ self.pg_start()
+ capture = self.pg0.get_capture(len(pkts))
+ self.verify_capture_in(capture, self.pg0)
+
+ # in2out
+ pkts = self.create_stream_in(self.pg0, self.pg1)
+ self.pg0.add_stream(pkts)
+ self.pg_enable_capture(self.pg_interfaces)
+ self.pg_start()
+ capture = self.pg1.get_capture(len(pkts))
+ self.verify_capture_out(capture, nat_ip, True)
+
+ def test_static_with_port_in(self):
+ """ 1:1 NAPT initialized from inside network """
+
+ self.tcp_port_out = 3606
+ self.udp_port_out = 3607
+ self.icmp_id_out = 3608
+
+ self.nat44_add_address(self.nat_addr)
+ self.nat44_add_static_mapping(self.pg0.remote_ip4, self.nat_addr,
+ self.tcp_port_in, self.tcp_port_out,
+ proto=IP_PROTOS.tcp)
+ self.nat44_add_static_mapping(self.pg0.remote_ip4, self.nat_addr,
+ self.udp_port_in, self.udp_port_out,
+ proto=IP_PROTOS.udp)
+ self.nat44_add_static_mapping(self.pg0.remote_ip4, self.nat_addr,
+ self.icmp_id_in, self.icmp_id_out,
+ proto=IP_PROTOS.icmp)
+ self.vapi.nat44_interface_add_del_feature(self.pg0.sw_if_index)
+ self.vapi.nat44_interface_add_del_feature(self.pg1.sw_if_index,
+ is_inside=0)
+
+ # in2out
+ pkts = self.create_stream_in(self.pg0, self.pg1)
+ self.pg0.add_stream(pkts)
+ self.pg_enable_capture(self.pg_interfaces)
+ self.pg_start()
+ capture = self.pg1.get_capture(len(pkts))
+ self.verify_capture_out(capture)
+
+ # out2in
+ pkts = self.create_stream_out(self.pg1)
+ self.pg1.add_stream(pkts)
+ self.pg_enable_capture(self.pg_interfaces)
+ self.pg_start()
+ capture = self.pg0.get_capture(len(pkts))
+ self.verify_capture_in(capture, self.pg0)
+
+ def test_static_with_port_out(self):
+ """ 1:1 NAPT initialized from outside network """
+
+ self.tcp_port_out = 30606
+ self.udp_port_out = 30607
+ self.icmp_id_out = 30608
+
+ self.nat44_add_address(self.nat_addr)
+ self.nat44_add_static_mapping(self.pg0.remote_ip4, self.nat_addr,
+ self.tcp_port_in, self.tcp_port_out,
+ proto=IP_PROTOS.tcp)
+ self.nat44_add_static_mapping(self.pg0.remote_ip4, self.nat_addr,
+ self.udp_port_in, self.udp_port_out,
+ proto=IP_PROTOS.udp)
+ self.nat44_add_static_mapping(self.pg0.remote_ip4, self.nat_addr,
+ self.icmp_id_in, self.icmp_id_out,
+ proto=IP_PROTOS.icmp)
+ self.vapi.nat44_interface_add_del_feature(self.pg0.sw_if_index)
+ self.vapi.nat44_interface_add_del_feature(self.pg1.sw_if_index,
+ is_inside=0)
+
+ # out2in
+ pkts = self.create_stream_out(self.pg1)
+ self.pg1.add_stream(pkts)
+ self.pg_enable_capture(self.pg_interfaces)
+ self.pg_start()
+ capture = self.pg0.get_capture(len(pkts))
+ self.verify_capture_in(capture, self.pg0)
+
+ # in2out
+ pkts = self.create_stream_in(self.pg0, self.pg1)
+ self.pg0.add_stream(pkts)
+ self.pg_enable_capture(self.pg_interfaces)
+ self.pg_start()
+ capture = self.pg1.get_capture(len(pkts))
+ self.verify_capture_out(capture)
+
+ def test_static_vrf_aware(self):
+ """ 1:1 NAT VRF awareness """
+
+ nat_ip1 = "10.0.0.30"
+ nat_ip2 = "10.0.0.40"
+ self.tcp_port_out = 6303
+ self.udp_port_out = 6304
+ self.icmp_id_out = 6305
+
+ self.nat44_add_static_mapping(self.pg4.remote_ip4, nat_ip1,
+ vrf_id=10)
+ self.nat44_add_static_mapping(self.pg0.remote_ip4, nat_ip2,
+ vrf_id=10)
+ self.vapi.nat44_interface_add_del_feature(self.pg3.sw_if_index,
+ is_inside=0)
+ self.vapi.nat44_interface_add_del_feature(self.pg0.sw_if_index)
+ self.vapi.nat44_interface_add_del_feature(self.pg4.sw_if_index)
+
+ # inside interface VRF match NAT44 static mapping VRF
+ pkts = self.create_stream_in(self.pg4, self.pg3)
+ self.pg4.add_stream(pkts)
+ self.pg_enable_capture(self.pg_interfaces)
+ self.pg_start()
+ capture = self.pg3.get_capture(len(pkts))
+ self.verify_capture_out(capture, nat_ip1, True)
+
+ # inside interface VRF don't match NAT44 static mapping VRF (packets
+ # are dropped)
+ pkts = self.create_stream_in(self.pg0, self.pg3)
+ self.pg0.add_stream(pkts)
+ self.pg_enable_capture(self.pg_interfaces)
+ self.pg_start()
+ self.pg3.assert_nothing_captured()
+
+ def test_static_lb(self):
+ """ NAT44 local service load balancing """
+ external_addr_n = socket.inet_pton(socket.AF_INET, self.nat_addr)
+ external_port = 80
+ local_port = 8080
+ server1 = self.pg0.remote_hosts[0]
+ server2 = self.pg0.remote_hosts[1]
+
+ locals = [{'addr': server1.ip4n,
+ 'port': local_port,
+ 'probability': 70},
+ {'addr': server2.ip4n,
+ 'port': local_port,
+ 'probability': 30}]
+
+ self.nat44_add_address(self.nat_addr)
+ self.vapi.nat44_add_del_lb_static_mapping(external_addr_n,
+ external_port,
+ IP_PROTOS.tcp,
+ local_num=len(locals),
+ locals=locals)
+ self.vapi.nat44_interface_add_del_feature(self.pg0.sw_if_index)
+ self.vapi.nat44_interface_add_del_feature(self.pg1.sw_if_index,
+ is_inside=0)
+
+ # from client to service
+ p = (Ether(src=self.pg1.remote_mac, dst=self.pg1.local_mac) /
+ IP(src=self.pg1.remote_ip4, dst=self.nat_addr) /
+ TCP(sport=12345, dport=external_port))
+ self.pg1.add_stream(p)
+ self.pg_enable_capture(self.pg_interfaces)
+ self.pg_start()
+ capture = self.pg0.get_capture(1)
+ p = capture[0]
+ server = None
+ try:
+ ip = p[IP]
+ tcp = p[TCP]
+ self.assertIn(ip.dst, [server1.ip4, server2.ip4])
+ if ip.dst == server1.ip4:
+ server = server1
+ else:
+ server = server2
+ self.assertEqual(tcp.dport, local_port)
+ self.check_tcp_checksum(p)
+ self.check_ip_checksum(p)
+ except:
+ self.logger.error(ppp("Unexpected or invalid packet:", p))
+ raise
+
+ # from service back to client
+ p = (Ether(src=server.mac, dst=self.pg0.local_mac) /
+ IP(src=server.ip4, dst=self.pg1.remote_ip4) /
+ TCP(sport=local_port, dport=12345))
+ self.pg0.add_stream(p)
+ self.pg_enable_capture(self.pg_interfaces)
+ self.pg_start()
+ capture = self.pg1.get_capture(1)
+ p = capture[0]
+ try:
+ ip = p[IP]
+ tcp = p[TCP]
+ self.assertEqual(ip.src, self.nat_addr)
+ self.assertEqual(tcp.sport, external_port)
+ self.check_tcp_checksum(p)
+ self.check_ip_checksum(p)
+ except:
+ self.logger.error(ppp("Unexpected or invalid packet:", p))
+ raise
+
+ # multiple clients
+ server1_n = 0
+ server2_n = 0
+ clients = ip4_range(self.pg1.remote_ip4, 10, 20)
+ pkts = []
+ for client in clients:
+ p = (Ether(src=self.pg1.remote_mac, dst=self.pg1.local_mac) /
+ IP(src=client, dst=self.nat_addr) /
+ TCP(sport=12345, dport=external_port))
+ pkts.append(p)
+ self.pg1.add_stream(pkts)
+ self.pg_enable_capture(self.pg_interfaces)
+ self.pg_start()
+ capture = self.pg0.get_capture(len(pkts))
+ for p in capture:
+ if p[IP].dst == server1.ip4:
+ server1_n += 1
+ else:
+ server2_n += 1
+ self.assertTrue(server1_n > server2_n)
+
+ def test_multiple_inside_interfaces(self):
+ """ NAT44 multiple non-overlapping address space inside interfaces """
+
+ self.nat44_add_address(self.nat_addr)
+ self.vapi.nat44_interface_add_del_feature(self.pg0.sw_if_index)
+ self.vapi.nat44_interface_add_del_feature(self.pg1.sw_if_index)
+ self.vapi.nat44_interface_add_del_feature(self.pg3.sw_if_index,
+ is_inside=0)
+
+ # between two NAT44 inside interfaces (no translation)
+ pkts = self.create_stream_in(self.pg0, self.pg1)
+ self.pg0.add_stream(pkts)
+ self.pg_enable_capture(self.pg_interfaces)
+ self.pg_start()
+ capture = self.pg1.get_capture(len(pkts))
+ self.verify_capture_no_translation(capture, self.pg0, self.pg1)
+
+ # from NAT44 inside to interface without NAT44 feature (no translation)
+ pkts = self.create_stream_in(self.pg0, self.pg2)
+ self.pg0.add_stream(pkts)
+ self.pg_enable_capture(self.pg_interfaces)
+ self.pg_start()
+ capture = self.pg2.get_capture(len(pkts))
+ self.verify_capture_no_translation(capture, self.pg0, self.pg2)
+
+ # in2out 1st interface
+ pkts = self.create_stream_in(self.pg0, self.pg3)
+ self.pg0.add_stream(pkts)
+ self.pg_enable_capture(self.pg_interfaces)
+ self.pg_start()
+ capture = self.pg3.get_capture(len(pkts))
+ self.verify_capture_out(capture)
+
+ # out2in 1st interface
+ pkts = self.create_stream_out(self.pg3)
+ self.pg3.add_stream(pkts)
+ self.pg_enable_capture(self.pg_interfaces)
+ self.pg_start()
+ capture = self.pg0.get_capture(len(pkts))
+ self.verify_capture_in(capture, self.pg0)
+
+ # in2out 2nd interface
+ pkts = self.create_stream_in(self.pg1, self.pg3)
+ self.pg1.add_stream(pkts)
+ self.pg_enable_capture(self.pg_interfaces)
+ self.pg_start()
+ capture = self.pg3.get_capture(len(pkts))
+ self.verify_capture_out(capture)
+
+ # out2in 2nd interface
+ pkts = self.create_stream_out(self.pg3)
+ self.pg3.add_stream(pkts)
+ self.pg_enable_capture(self.pg_interfaces)
+ self.pg_start()
+ capture = self.pg1.get_capture(len(pkts))
+ self.verify_capture_in(capture, self.pg1)
+
+ def test_inside_overlapping_interfaces(self):
+ """ NAT44 multiple inside interfaces with overlapping address space """
+
+ static_nat_ip = "10.0.0.10"
+ self.nat44_add_address(self.nat_addr)
+ self.vapi.nat44_interface_add_del_feature(self.pg3.sw_if_index,
+ is_inside=0)
+ self.vapi.nat44_interface_add_del_feature(self.pg4.sw_if_index)
+ self.vapi.nat44_interface_add_del_feature(self.pg5.sw_if_index)
+ self.vapi.nat44_interface_add_del_feature(self.pg6.sw_if_index)
+ self.nat44_add_static_mapping(self.pg6.remote_ip4, static_nat_ip,
+ vrf_id=20)
+
+ # between NAT44 inside interfaces with same VRF (no translation)
+ pkts = self.create_stream_in(self.pg4, self.pg5)
+ self.pg4.add_stream(pkts)
+ self.pg_enable_capture(self.pg_interfaces)
+ self.pg_start()
+ capture = self.pg5.get_capture(len(pkts))
+ self.verify_capture_no_translation(capture, self.pg4, self.pg5)
+
+ # between NAT44 inside interfaces with different VRF (hairpinning)
+ p = (Ether(src=self.pg4.remote_mac, dst=self.pg4.local_mac) /
+ IP(src=self.pg4.remote_ip4, dst=static_nat_ip) /
+ TCP(sport=1234, dport=5678))
+ self.pg4.add_stream(p)
+ self.pg_enable_capture(self.pg_interfaces)
+ self.pg_start()
+ capture = self.pg6.get_capture(1)
+ p = capture[0]
+ try:
+ ip = p[IP]
+ tcp = p[TCP]
+ self.assertEqual(ip.src, self.nat_addr)
+ self.assertEqual(ip.dst, self.pg6.remote_ip4)
+ self.assertNotEqual(tcp.sport, 1234)
+ self.assertEqual(tcp.dport, 5678)
+ except:
+ self.logger.error(ppp("Unexpected or invalid packet:", p))
+ raise
+
+ # in2out 1st interface
+ pkts = self.create_stream_in(self.pg4, self.pg3)
+ self.pg4.add_stream(pkts)
+ self.pg_enable_capture(self.pg_interfaces)
+ self.pg_start()
+ capture = self.pg3.get_capture(len(pkts))
+ self.verify_capture_out(capture)
+
+ # out2in 1st interface
+ pkts = self.create_stream_out(self.pg3)
+ self.pg3.add_stream(pkts)
+ self.pg_enable_capture(self.pg_interfaces)
+ self.pg_start()
+ capture = self.pg4.get_capture(len(pkts))
+ self.verify_capture_in(capture, self.pg4)
+
+ # in2out 2nd interface
+ pkts = self.create_stream_in(self.pg5, self.pg3)
+ self.pg5.add_stream(pkts)
+ self.pg_enable_capture(self.pg_interfaces)
+ self.pg_start()
+ capture = self.pg3.get_capture(len(pkts))
+ self.verify_capture_out(capture)
+
+ # out2in 2nd interface
+ pkts = self.create_stream_out(self.pg3)
+ self.pg3.add_stream(pkts)
+ self.pg_enable_capture(self.pg_interfaces)
+ self.pg_start()
+ capture = self.pg5.get_capture(len(pkts))
+ self.verify_capture_in(capture, self.pg5)
+
+ # pg5 session dump
+ addresses = self.vapi.nat44_address_dump()
+ self.assertEqual(len(addresses), 1)
+ sessions = self.vapi.nat44_user_session_dump(self.pg5.remote_ip4n, 10)
+ self.assertEqual(len(sessions), 3)
+ for session in sessions:
+ self.assertFalse(session.is_static)
+ self.assertEqual(session.inside_ip_address[0:4],
+ self.pg5.remote_ip4n)
+ self.assertEqual(session.outside_ip_address,
+ addresses[0].ip_address)
+ self.assertEqual(sessions[0].protocol, IP_PROTOS.tcp)
+ self.assertEqual(sessions[1].protocol, IP_PROTOS.udp)
+ self.assertEqual(sessions[2].protocol, IP_PROTOS.icmp)
+ self.assertEqual(sessions[0].inside_port, self.tcp_port_in)
+ self.assertEqual(sessions[1].inside_port, self.udp_port_in)
+ self.assertEqual(sessions[2].inside_port, self.icmp_id_in)
+ self.assertEqual(sessions[0].outside_port, self.tcp_port_out)
+ self.assertEqual(sessions[1].outside_port, self.udp_port_out)
+ self.assertEqual(sessions[2].outside_port, self.icmp_id_out)
+
+ # in2out 3rd interface
+ pkts = self.create_stream_in(self.pg6, self.pg3)
+ self.pg6.add_stream(pkts)
+ self.pg_enable_capture(self.pg_interfaces)
+ self.pg_start()
+ capture = self.pg3.get_capture(len(pkts))
+ self.verify_capture_out(capture, static_nat_ip, True)
+
+ # out2in 3rd interface
+ pkts = self.create_stream_out(self.pg3, static_nat_ip)
+ self.pg3.add_stream(pkts)
+ self.pg_enable_capture(self.pg_interfaces)
+ self.pg_start()
+ capture = self.pg6.get_capture(len(pkts))
+ self.verify_capture_in(capture, self.pg6)
+
+ # general user and session dump verifications
+ users = self.vapi.nat44_user_dump()
+ self.assertTrue(len(users) >= 3)
+ addresses = self.vapi.nat44_address_dump()
+ self.assertEqual(len(addresses), 1)
+ for user in users:
+ sessions = self.vapi.nat44_user_session_dump(user.ip_address,
+ user.vrf_id)
+ for session in sessions:
+ self.assertEqual(user.ip_address, session.inside_ip_address)
+ self.assertTrue(session.total_bytes > session.total_pkts > 0)
+ self.assertTrue(session.protocol in
+ [IP_PROTOS.tcp, IP_PROTOS.udp,
+ IP_PROTOS.icmp])
+
+ # pg4 session dump
+ sessions = self.vapi.nat44_user_session_dump(self.pg4.remote_ip4n, 10)
+ self.assertTrue(len(sessions) >= 4)
+ for session in sessions:
+ self.assertFalse(session.is_static)
+ self.assertEqual(session.inside_ip_address[0:4],
+ self.pg4.remote_ip4n)
+ self.assertEqual(session.outside_ip_address,
+ addresses[0].ip_address)
+
+ # pg6 session dump
+ sessions = self.vapi.nat44_user_session_dump(self.pg6.remote_ip4n, 20)
+ self.assertTrue(len(sessions) >= 3)
+ for session in sessions:
+ self.assertTrue(session.is_static)
+ self.assertEqual(session.inside_ip_address[0:4],
+ self.pg6.remote_ip4n)
+ self.assertEqual(map(ord, session.outside_ip_address[0:4]),
+ map(int, static_nat_ip.split('.')))
+ self.assertTrue(session.inside_port in
+ [self.tcp_port_in, self.udp_port_in,
+ self.icmp_id_in])
+
+ def test_hairpinning(self):
+ """ NAT44 hairpinning - 1:1 NAPT """
+
+ host = self.pg0.remote_hosts[0]
+ server = self.pg0.remote_hosts[1]
+ host_in_port = 1234
+ host_out_port = 0
+ server_in_port = 5678
+ server_out_port = 8765
+
+ self.nat44_add_address(self.nat_addr)
+ self.vapi.nat44_interface_add_del_feature(self.pg0.sw_if_index)
+ self.vapi.nat44_interface_add_del_feature(self.pg1.sw_if_index,
+ is_inside=0)
+ # add static mapping for server
+ self.nat44_add_static_mapping(server.ip4, self.nat_addr,
+ server_in_port, server_out_port,
+ proto=IP_PROTOS.tcp)
+
+ # send packet from host to server
+ p = (Ether(src=host.mac, dst=self.pg0.local_mac) /
+ IP(src=host.ip4, dst=self.nat_addr) /
+ TCP(sport=host_in_port, dport=server_out_port))
+ self.pg0.add_stream(p)
+ self.pg_enable_capture(self.pg_interfaces)
+ self.pg_start()
+ capture = self.pg0.get_capture(1)
+ p = capture[0]
+ try:
+ ip = p[IP]
+ tcp = p[TCP]
+ self.assertEqual(ip.src, self.nat_addr)
+ self.assertEqual(ip.dst, server.ip4)
+ self.assertNotEqual(tcp.sport, host_in_port)
+ self.assertEqual(tcp.dport, server_in_port)
+ self.check_tcp_checksum(p)
+ host_out_port = tcp.sport
+ except:
+ self.logger.error(ppp("Unexpected or invalid packet:", p))
+ raise
+
+ # send reply from server to host
+ p = (Ether(src=server.mac, dst=self.pg0.local_mac) /
+ IP(src=server.ip4, dst=self.nat_addr) /
+ TCP(sport=server_in_port, dport=host_out_port))
+ self.pg0.add_stream(p)
+ self.pg_enable_capture(self.pg_interfaces)
+ self.pg_start()
+ capture = self.pg0.get_capture(1)
+ p = capture[0]
+ try:
+ ip = p[IP]
+ tcp = p[TCP]
+ self.assertEqual(ip.src, self.nat_addr)
+ self.assertEqual(ip.dst, host.ip4)
+ self.assertEqual(tcp.sport, server_out_port)
+ self.assertEqual(tcp.dport, host_in_port)
+ self.check_tcp_checksum(p)
+ except:
+ self.logger.error(ppp("Unexpected or invalid packet:"), p)
+ raise
+
+ def test_hairpinning2(self):
+ """ NAT44 hairpinning - 1:1 NAT"""
+
+ server1_nat_ip = "10.0.0.10"
+ server2_nat_ip = "10.0.0.11"
+ host = self.pg0.remote_hosts[0]
+ server1 = self.pg0.remote_hosts[1]
+ server2 = self.pg0.remote_hosts[2]
+ server_tcp_port = 22
+ server_udp_port = 20
+
+ self.nat44_add_address(self.nat_addr)
+ self.vapi.nat44_interface_add_del_feature(self.pg0.sw_if_index)
+ self.vapi.nat44_interface_add_del_feature(self.pg1.sw_if_index,
+ is_inside=0)
+
+ # add static mapping for servers
+ self.nat44_add_static_mapping(server1.ip4, server1_nat_ip)
+ self.nat44_add_static_mapping(server2.ip4, server2_nat_ip)
+
+ # host to server1
+ pkts = []
+ p = (Ether(dst=self.pg0.local_mac, src=self.pg0.remote_mac) /
+ IP(src=host.ip4, dst=server1_nat_ip) /
+ TCP(sport=self.tcp_port_in, dport=server_tcp_port))
+ pkts.append(p)
+ p = (Ether(dst=self.pg0.local_mac, src=self.pg0.remote_mac) /
+ IP(src=host.ip4, dst=server1_nat_ip) /
+ UDP(sport=self.udp_port_in, dport=server_udp_port))
+ pkts.append(p)
+ p = (Ether(dst=self.pg0.local_mac, src=self.pg0.remote_mac) /
+ IP(src=host.ip4, dst=server1_nat_ip) /
+ ICMP(id=self.icmp_id_in, type='echo-request'))
+ pkts.append(p)
+ self.pg0.add_stream(pkts)
+ self.pg_enable_capture(self.pg_interfaces)
+ self.pg_start()
+ capture = self.pg0.get_capture(len(pkts))
+ for packet in capture:
+ try:
+ self.assertEqual(packet[IP].src, self.nat_addr)
+ self.assertEqual(packet[IP].dst, server1.ip4)
+ if packet.haslayer(TCP):
+ self.assertNotEqual(packet[TCP].sport, self.tcp_port_in)
+ self.assertEqual(packet[TCP].dport, server_tcp_port)
+ self.tcp_port_out = packet[TCP].sport
+ self.check_tcp_checksum(packet)
+ elif packet.haslayer(UDP):
+ self.assertNotEqual(packet[UDP].sport, self.udp_port_in)
+ self.assertEqual(packet[UDP].dport, server_udp_port)
+ self.udp_port_out = packet[UDP].sport
+ else:
+ self.assertNotEqual(packet[ICMP].id, self.icmp_id_in)
+ self.icmp_id_out = packet[ICMP].id
+ except:
+ self.logger.error(ppp("Unexpected or invalid packet:", packet))
+ raise
+
+ # server1 to host
+ pkts = []
+ p = (Ether(dst=self.pg0.local_mac, src=self.pg0.remote_mac) /
+ IP(src=server1.ip4, dst=self.nat_addr) /
+ TCP(sport=server_tcp_port, dport=self.tcp_port_out))
+ pkts.append(p)
+ p = (Ether(dst=self.pg0.local_mac, src=self.pg0.remote_mac) /
+ IP(src=server1.ip4, dst=self.nat_addr) /
+ UDP(sport=server_udp_port, dport=self.udp_port_out))
+ pkts.append(p)
+ p = (Ether(dst=self.pg0.local_mac, src=self.pg0.remote_mac) /
+ IP(src=server1.ip4, dst=self.nat_addr) /
+ ICMP(id=self.icmp_id_out, type='echo-reply'))
+ pkts.append(p)
+ self.pg0.add_stream(pkts)
+ self.pg_enable_capture(self.pg_interfaces)
+ self.pg_start()
+ capture = self.pg0.get_capture(len(pkts))
+ for packet in capture:
+ try:
+ self.assertEqual(packet[IP].src, server1_nat_ip)
+ self.assertEqual(packet[IP].dst, host.ip4)
+ if packet.haslayer(TCP):
+ self.assertEqual(packet[TCP].dport, self.tcp_port_in)
+ self.assertEqual(packet[TCP].sport, server_tcp_port)
+ self.check_tcp_checksum(packet)
+ elif packet.haslayer(UDP):
+ self.assertEqual(packet[UDP].dport, self.udp_port_in)
+ self.assertEqual(packet[UDP].sport, server_udp_port)
+ else:
+ self.assertEqual(packet[ICMP].id, self.icmp_id_in)
+ except:
+ self.logger.error(ppp("Unexpected or invalid packet:", packet))
+ raise
+
+ # server2 to server1
+ pkts = []
+ p = (Ether(dst=self.pg0.local_mac, src=self.pg0.remote_mac) /
+ IP(src=server2.ip4, dst=server1_nat_ip) /
+ TCP(sport=self.tcp_port_in, dport=server_tcp_port))
+ pkts.append(p)
+ p = (Ether(dst=self.pg0.local_mac, src=self.pg0.remote_mac) /
+ IP(src=server2.ip4, dst=server1_nat_ip) /
+ UDP(sport=self.udp_port_in, dport=server_udp_port))
+ pkts.append(p)
+ p = (Ether(dst=self.pg0.local_mac, src=self.pg0.remote_mac) /
+ IP(src=server2.ip4, dst=server1_nat_ip) /
+ ICMP(id=self.icmp_id_in, type='echo-request'))
+ pkts.append(p)
+ self.pg0.add_stream(pkts)
+ self.pg_enable_capture(self.pg_interfaces)
+ self.pg_start()
+ capture = self.pg0.get_capture(len(pkts))
+ for packet in capture:
+ try:
+ self.assertEqual(packet[IP].src, server2_nat_ip)
+ self.assertEqual(packet[IP].dst, server1.ip4)
+ if packet.haslayer(TCP):
+ self.assertEqual(packet[TCP].sport, self.tcp_port_in)
+ self.assertEqual(packet[TCP].dport, server_tcp_port)
+ self.tcp_port_out = packet[TCP].sport
+ self.check_tcp_checksum(packet)
+ elif packet.haslayer(UDP):
+ self.assertEqual(packet[UDP].sport, self.udp_port_in)
+ self.assertEqual(packet[UDP].dport, server_udp_port)
+ self.udp_port_out = packet[UDP].sport
+ else:
+ self.assertEqual(packet[ICMP].id, self.icmp_id_in)
+ self.icmp_id_out = packet[ICMP].id
+ except:
+ self.logger.error(ppp("Unexpected or invalid packet:", packet))
+ raise
+
+ # server1 to server2
+ pkts = []
+ p = (Ether(dst=self.pg0.local_mac, src=self.pg0.remote_mac) /
+ IP(src=server1.ip4, dst=server2_nat_ip) /
+ TCP(sport=server_tcp_port, dport=self.tcp_port_out))
+ pkts.append(p)
+ p = (Ether(dst=self.pg0.local_mac, src=self.pg0.remote_mac) /
+ IP(src=server1.ip4, dst=server2_nat_ip) /
+ UDP(sport=server_udp_port, dport=self.udp_port_out))
+ pkts.append(p)
+ p = (Ether(dst=self.pg0.local_mac, src=self.pg0.remote_mac) /
+ IP(src=server1.ip4, dst=server2_nat_ip) /
+ ICMP(id=self.icmp_id_out, type='echo-reply'))
+ pkts.append(p)
+ self.pg0.add_stream(pkts)
+ self.pg_enable_capture(self.pg_interfaces)
+ self.pg_start()
+ capture = self.pg0.get_capture(len(pkts))
+ for packet in capture:
+ try:
+ self.assertEqual(packet[IP].src, server1_nat_ip)
+ self.assertEqual(packet[IP].dst, server2.ip4)
+ if packet.haslayer(TCP):
+ self.assertEqual(packet[TCP].dport, self.tcp_port_in)
+ self.assertEqual(packet[TCP].sport, server_tcp_port)
+ self.check_tcp_checksum(packet)
+ elif packet.haslayer(UDP):
+ self.assertEqual(packet[UDP].dport, self.udp_port_in)
+ self.assertEqual(packet[UDP].sport, server_udp_port)
+ else:
+ self.assertEqual(packet[ICMP].id, self.icmp_id_in)
+ except:
+ self.logger.error(ppp("Unexpected or invalid packet:", packet))
+ raise
+
+ def test_max_translations_per_user(self):
+ """ MAX translations per user - recycle the least recently used """
+
+ self.nat44_add_address(self.nat_addr)
+ self.vapi.nat44_interface_add_del_feature(self.pg0.sw_if_index)
+ self.vapi.nat44_interface_add_del_feature(self.pg1.sw_if_index,
+ is_inside=0)
+
+ # get maximum number of translations per user
+ nat44_config = self.vapi.nat_show_config()
+
+ # send more than maximum number of translations per user packets
+ pkts_num = nat44_config.max_translations_per_user + 5
+ pkts = []
+ for port in range(0, pkts_num):
+ p = (Ether(dst=self.pg0.local_mac, src=self.pg0.remote_mac) /
+ IP(src=self.pg0.remote_ip4, dst=self.pg1.remote_ip4) /
+ TCP(sport=1025 + port))
+ pkts.append(p)
+ self.pg0.add_stream(pkts)
+ self.pg_enable_capture(self.pg_interfaces)
+ self.pg_start()
+
+ # verify number of translated packet
+ self.pg1.get_capture(pkts_num)
+
+ def test_interface_addr(self):
+ """ Acquire NAT44 addresses from interface """
+ self.vapi.nat44_add_interface_addr(self.pg7.sw_if_index)
+
+ # no address in NAT pool
+ adresses = self.vapi.nat44_address_dump()
+ self.assertEqual(0, len(adresses))
+
+ # configure interface address and check NAT address pool
+ self.pg7.config_ip4()
+ adresses = self.vapi.nat44_address_dump()
+ self.assertEqual(1, len(adresses))
+ self.assertEqual(adresses[0].ip_address[0:4], self.pg7.local_ip4n)
+
+ # remove interface address and check NAT address pool
+ self.pg7.unconfig_ip4()
+ adresses = self.vapi.nat44_address_dump()
+ self.assertEqual(0, len(adresses))
+
+ def test_interface_addr_static_mapping(self):
+ """ Static mapping with addresses from interface """
+ self.vapi.nat44_add_interface_addr(self.pg7.sw_if_index)
+ self.nat44_add_static_mapping(
+ '1.2.3.4',
+ external_sw_if_index=self.pg7.sw_if_index)
+
+ # static mappings with external interface
+ static_mappings = self.vapi.nat44_static_mapping_dump()
+ self.assertEqual(1, len(static_mappings))
+ self.assertEqual(self.pg7.sw_if_index,
+ static_mappings[0].external_sw_if_index)
+
+ # configure interface address and check static mappings
+ self.pg7.config_ip4()
+ static_mappings = self.vapi.nat44_static_mapping_dump()
+ self.assertEqual(1, len(static_mappings))
+ self.assertEqual(static_mappings[0].external_ip_address[0:4],
+ self.pg7.local_ip4n)
+ self.assertEqual(0xFFFFFFFF, static_mappings[0].external_sw_if_index)
+
+ # remove interface address and check static mappings
+ self.pg7.unconfig_ip4()
+ static_mappings = self.vapi.nat44_static_mapping_dump()
+ self.assertEqual(0, len(static_mappings))
+
+ def test_ipfix_nat44_sess(self):
+ """ IPFIX logging NAT44 session created/delted """
+ self.ipfix_domain_id = 10
+ self.ipfix_src_port = 20202
+ colector_port = 30303
+ bind_layers(UDP, IPFIX, dport=30303)
+ self.nat44_add_address(self.nat_addr)
+ self.vapi.nat44_interface_add_del_feature(self.pg0.sw_if_index)
+ self.vapi.nat44_interface_add_del_feature(self.pg1.sw_if_index,
+ is_inside=0)
+ self.vapi.set_ipfix_exporter(collector_address=self.pg3.remote_ip4n,
+ src_address=self.pg3.local_ip4n,
+ path_mtu=512,
+ template_interval=10,
+ collector_port=colector_port)
+ self.vapi.nat_ipfix(domain_id=self.ipfix_domain_id,
+ src_port=self.ipfix_src_port)
+
+ pkts = self.create_stream_in(self.pg0, self.pg1)
+ self.pg0.add_stream(pkts)
+ self.pg_enable_capture(self.pg_interfaces)
+ self.pg_start()
+ capture = self.pg1.get_capture(len(pkts))
+ self.verify_capture_out(capture)
+ self.nat44_add_address(self.nat_addr, is_add=0)
+ self.vapi.cli("ipfix flush") # FIXME this should be an API call
+ capture = self.pg3.get_capture(3)
+ ipfix = IPFIXDecoder()
+ # first load template
+ for p in capture:
+ self.assertTrue(p.haslayer(IPFIX))
+ self.assertEqual(p[IP].src, self.pg3.local_ip4)
+ self.assertEqual(p[IP].dst, self.pg3.remote_ip4)
+ self.assertEqual(p[UDP].sport, self.ipfix_src_port)
+ self.assertEqual(p[UDP].dport, colector_port)
+ self.assertEqual(p[IPFIX].observationDomainID,
+ self.ipfix_domain_id)
+ if p.haslayer(Template):
+ ipfix.add_template(p.getlayer(Template))
+ # verify events in data set
+ for p in capture:
+ if p.haslayer(Data):
+ data = ipfix.decode_data_set(p.getlayer(Set))
+ self.verify_ipfix_nat44_ses(data)
+
+ def test_ipfix_addr_exhausted(self):
+ """ IPFIX logging NAT addresses exhausted """
+ self.vapi.nat44_interface_add_del_feature(self.pg0.sw_if_index)
+ self.vapi.nat44_interface_add_del_feature(self.pg1.sw_if_index,
+ is_inside=0)
+ self.vapi.set_ipfix_exporter(collector_address=self.pg3.remote_ip4n,
+ src_address=self.pg3.local_ip4n,
+ path_mtu=512,
+ template_interval=10)
+ self.vapi.nat_ipfix(domain_id=self.ipfix_domain_id,
+ src_port=self.ipfix_src_port)
+
+ p = (Ether(src=self.pg0.remote_mac, dst=self.pg0.local_mac) /
+ IP(src=self.pg0.remote_ip4, dst=self.pg1.remote_ip4) /
+ TCP(sport=3025))
+ self.pg0.add_stream(p)
+ self.pg_enable_capture(self.pg_interfaces)
+ self.pg_start()
+ capture = self.pg1.get_capture(0)
+ self.vapi.cli("ipfix flush") # FIXME this should be an API call
+ capture = self.pg3.get_capture(3)
+ ipfix = IPFIXDecoder()
+ # first load template
+ for p in capture:
+ self.assertTrue(p.haslayer(IPFIX))
+ self.assertEqual(p[IP].src, self.pg3.local_ip4)
+ self.assertEqual(p[IP].dst, self.pg3.remote_ip4)
+ self.assertEqual(p[UDP].sport, self.ipfix_src_port)
+ self.assertEqual(p[UDP].dport, 4739)
+ self.assertEqual(p[IPFIX].observationDomainID,
+ self.ipfix_domain_id)
+ if p.haslayer(Template):
+ ipfix.add_template(p.getlayer(Template))
+ # verify events in data set
+ for p in capture:
+ if p.haslayer(Data):
+ data = ipfix.decode_data_set(p.getlayer(Set))
+ self.verify_ipfix_addr_exhausted(data)
+
+ def test_pool_addr_fib(self):
+ """ NAT44 add pool addresses to FIB """
+ static_addr = '10.0.0.10'
+ self.nat44_add_address(self.nat_addr)
+ self.vapi.nat44_interface_add_del_feature(self.pg0.sw_if_index)
+ self.vapi.nat44_interface_add_del_feature(self.pg1.sw_if_index,
+ is_inside=0)
+ self.nat44_add_static_mapping(self.pg0.remote_ip4, static_addr)
+
+ # NAT44 address
+ p = (Ether(src=self.pg1.remote_mac, dst='ff:ff:ff:ff:ff:ff') /
+ ARP(op=ARP.who_has, pdst=self.nat_addr,
+ psrc=self.pg1.remote_ip4, hwsrc=self.pg1.remote_mac))
+ self.pg1.add_stream(p)
+ self.pg_enable_capture(self.pg_interfaces)
+ self.pg_start()
+ capture = self.pg1.get_capture(1)
+ self.assertTrue(capture[0].haslayer(ARP))
+ self.assertTrue(capture[0][ARP].op, ARP.is_at)
+
+ # 1:1 NAT address
+ p = (Ether(src=self.pg1.remote_mac, dst='ff:ff:ff:ff:ff:ff') /
+ ARP(op=ARP.who_has, pdst=static_addr,
+ psrc=self.pg1.remote_ip4, hwsrc=self.pg1.remote_mac))
+ self.pg1.add_stream(p)
+ self.pg_enable_capture(self.pg_interfaces)
+ self.pg_start()
+ capture = self.pg1.get_capture(1)
+ self.assertTrue(capture[0].haslayer(ARP))
+ self.assertTrue(capture[0][ARP].op, ARP.is_at)
+
+ # send ARP to non-NAT44 interface
+ p = (Ether(src=self.pg2.remote_mac, dst='ff:ff:ff:ff:ff:ff') /
+ ARP(op=ARP.who_has, pdst=self.nat_addr,
+ psrc=self.pg2.remote_ip4, hwsrc=self.pg2.remote_mac))
+ self.pg2.add_stream(p)
+ self.pg_enable_capture(self.pg_interfaces)
+ self.pg_start()
+ capture = self.pg1.get_capture(0)
+
+ # remove addresses and verify
+ self.nat44_add_address(self.nat_addr, is_add=0)
+ self.nat44_add_static_mapping(self.pg0.remote_ip4, static_addr,
+ is_add=0)
+
+ p = (Ether(src=self.pg1.remote_mac, dst='ff:ff:ff:ff:ff:ff') /
+ ARP(op=ARP.who_has, pdst=self.nat_addr,
+ psrc=self.pg1.remote_ip4, hwsrc=self.pg1.remote_mac))
+ self.pg1.add_stream(p)
+ self.pg_enable_capture(self.pg_interfaces)
+ self.pg_start()
+ capture = self.pg1.get_capture(0)
+
+ p = (Ether(src=self.pg1.remote_mac, dst='ff:ff:ff:ff:ff:ff') /
+ ARP(op=ARP.who_has, pdst=static_addr,
+ psrc=self.pg1.remote_ip4, hwsrc=self.pg1.remote_mac))
+ self.pg1.add_stream(p)
+ self.pg_enable_capture(self.pg_interfaces)
+ self.pg_start()
+ capture = self.pg1.get_capture(0)
+
+ def test_vrf_mode(self):
+ """ NAT44 tenant VRF aware address pool mode """
+
+ vrf_id1 = 1
+ vrf_id2 = 2
+ nat_ip1 = "10.0.0.10"
+ nat_ip2 = "10.0.0.11"
+
+ self.pg0.unconfig_ip4()
+ self.pg1.unconfig_ip4()
+ self.vapi.ip_table_add_del(vrf_id1, is_add=1)
+ self.vapi.ip_table_add_del(vrf_id2, is_add=1)
+ self.pg0.set_table_ip4(vrf_id1)
+ self.pg1.set_table_ip4(vrf_id2)
+ self.pg0.config_ip4()
+ self.pg1.config_ip4()
+
+ self.nat44_add_address(nat_ip1, vrf_id=vrf_id1)
+ self.nat44_add_address(nat_ip2, vrf_id=vrf_id2)
+ self.vapi.nat44_interface_add_del_feature(self.pg0.sw_if_index)
+ self.vapi.nat44_interface_add_del_feature(self.pg1.sw_if_index)
+ self.vapi.nat44_interface_add_del_feature(self.pg2.sw_if_index,
+ is_inside=0)
+
+ # first VRF
+ pkts = self.create_stream_in(self.pg0, self.pg2)
+ self.pg0.add_stream(pkts)
+ self.pg_enable_capture(self.pg_interfaces)
+ self.pg_start()
+ capture = self.pg2.get_capture(len(pkts))
+ self.verify_capture_out(capture, nat_ip1)
+
+ # second VRF
+ pkts = self.create_stream_in(self.pg1, self.pg2)
+ self.pg1.add_stream(pkts)
+ self.pg_enable_capture(self.pg_interfaces)
+ self.pg_start()
+ capture = self.pg2.get_capture(len(pkts))
+ self.verify_capture_out(capture, nat_ip2)
+
+ self.pg0.unconfig_ip4()
+ self.pg1.unconfig_ip4()
+ self.pg0.set_table_ip4(0)
+ self.pg1.set_table_ip4(0)
+ self.vapi.ip_table_add_del(vrf_id1, is_add=0)
+ self.vapi.ip_table_add_del(vrf_id2, is_add=0)
+
+ def test_vrf_feature_independent(self):
+ """ NAT44 tenant VRF independent address pool mode """
+
+ nat_ip1 = "10.0.0.10"
+ nat_ip2 = "10.0.0.11"
+
+ self.nat44_add_address(nat_ip1)
+ self.nat44_add_address(nat_ip2)
+ self.vapi.nat44_interface_add_del_feature(self.pg0.sw_if_index)
+ self.vapi.nat44_interface_add_del_feature(self.pg1.sw_if_index)
+ self.vapi.nat44_interface_add_del_feature(self.pg2.sw_if_index,
+ is_inside=0)
+
+ # first VRF
+ pkts = self.create_stream_in(self.pg0, self.pg2)
+ self.pg0.add_stream(pkts)
+ self.pg_enable_capture(self.pg_interfaces)
+ self.pg_start()
+ capture = self.pg2.get_capture(len(pkts))
+ self.verify_capture_out(capture, nat_ip1)
+
+ # second VRF
+ pkts = self.create_stream_in(self.pg1, self.pg2)
+ self.pg1.add_stream(pkts)
+ self.pg_enable_capture(self.pg_interfaces)
+ self.pg_start()
+ capture = self.pg2.get_capture(len(pkts))
+ self.verify_capture_out(capture, nat_ip1)
+
+ def test_dynamic_ipless_interfaces(self):
+ """ NAT44 interfaces without configured IP address """
+
+ self.vapi.ip_neighbor_add_del(self.pg7.sw_if_index,
+ mactobinary(self.pg7.remote_mac),
+ self.pg7.remote_ip4n,
+ is_static=1)
+ self.vapi.ip_neighbor_add_del(self.pg8.sw_if_index,
+ mactobinary(self.pg8.remote_mac),
+ self.pg8.remote_ip4n,
+ is_static=1)
+
+ self.vapi.ip_add_del_route(dst_address=self.pg7.remote_ip4n,
+ dst_address_length=32,
+ next_hop_address=self.pg7.remote_ip4n,
+ next_hop_sw_if_index=self.pg7.sw_if_index)
+ self.vapi.ip_add_del_route(dst_address=self.pg8.remote_ip4n,
+ dst_address_length=32,
+ next_hop_address=self.pg8.remote_ip4n,
+ next_hop_sw_if_index=self.pg8.sw_if_index)
+
+ self.nat44_add_address(self.nat_addr)
+ self.vapi.nat44_interface_add_del_feature(self.pg7.sw_if_index)
+ self.vapi.nat44_interface_add_del_feature(self.pg8.sw_if_index,
+ is_inside=0)
+
+ # in2out
+ pkts = self.create_stream_in(self.pg7, self.pg8)
+ self.pg7.add_stream(pkts)
+ self.pg_enable_capture(self.pg_interfaces)
+ self.pg_start()
+ capture = self.pg8.get_capture(len(pkts))
+ self.verify_capture_out(capture)
+
+ # out2in
+ pkts = self.create_stream_out(self.pg8, self.nat_addr)
+ self.pg8.add_stream(pkts)
+ self.pg_enable_capture(self.pg_interfaces)
+ self.pg_start()
+ capture = self.pg7.get_capture(len(pkts))
+ self.verify_capture_in(capture, self.pg7)
+
+ def test_static_ipless_interfaces(self):
+ """ NAT44 interfaces without configured IP address - 1:1 NAT """
+
+ self.vapi.ip_neighbor_add_del(self.pg7.sw_if_index,
+ mactobinary(self.pg7.remote_mac),
+ self.pg7.remote_ip4n,
+ is_static=1)
+ self.vapi.ip_neighbor_add_del(self.pg8.sw_if_index,
+ mactobinary(self.pg8.remote_mac),
+ self.pg8.remote_ip4n,
+ is_static=1)
+
+ self.vapi.ip_add_del_route(dst_address=self.pg7.remote_ip4n,
+ dst_address_length=32,
+ next_hop_address=self.pg7.remote_ip4n,
+ next_hop_sw_if_index=self.pg7.sw_if_index)
+ self.vapi.ip_add_del_route(dst_address=self.pg8.remote_ip4n,
+ dst_address_length=32,
+ next_hop_address=self.pg8.remote_ip4n,
+ next_hop_sw_if_index=self.pg8.sw_if_index)
+
+ self.nat44_add_static_mapping(self.pg7.remote_ip4, self.nat_addr)
+ self.vapi.nat44_interface_add_del_feature(self.pg7.sw_if_index)
+ self.vapi.nat44_interface_add_del_feature(self.pg8.sw_if_index,
+ is_inside=0)
+
+ # out2in
+ pkts = self.create_stream_out(self.pg8)
+ self.pg8.add_stream(pkts)
+ self.pg_enable_capture(self.pg_interfaces)
+ self.pg_start()
+ capture = self.pg7.get_capture(len(pkts))
+ self.verify_capture_in(capture, self.pg7)
+
+ # in2out
+ pkts = self.create_stream_in(self.pg7, self.pg8)
+ self.pg7.add_stream(pkts)
+ self.pg_enable_capture(self.pg_interfaces)
+ self.pg_start()
+ capture = self.pg8.get_capture(len(pkts))
+ self.verify_capture_out(capture, self.nat_addr, True)
+
+ def test_static_with_port_ipless_interfaces(self):
+ """ NAT44 interfaces without configured IP address - 1:1 NAPT """
+
+ self.tcp_port_out = 30606
+ self.udp_port_out = 30607
+ self.icmp_id_out = 30608
+
+ self.vapi.ip_neighbor_add_del(self.pg7.sw_if_index,
+ mactobinary(self.pg7.remote_mac),
+ self.pg7.remote_ip4n,
+ is_static=1)
+ self.vapi.ip_neighbor_add_del(self.pg8.sw_if_index,
+ mactobinary(self.pg8.remote_mac),
+ self.pg8.remote_ip4n,
+ is_static=1)
+
+ self.vapi.ip_add_del_route(dst_address=self.pg7.remote_ip4n,
+ dst_address_length=32,
+ next_hop_address=self.pg7.remote_ip4n,
+ next_hop_sw_if_index=self.pg7.sw_if_index)
+ self.vapi.ip_add_del_route(dst_address=self.pg8.remote_ip4n,
+ dst_address_length=32,
+ next_hop_address=self.pg8.remote_ip4n,
+ next_hop_sw_if_index=self.pg8.sw_if_index)
+
+ self.nat44_add_address(self.nat_addr)
+ self.nat44_add_static_mapping(self.pg7.remote_ip4, self.nat_addr,
+ self.tcp_port_in, self.tcp_port_out,
+ proto=IP_PROTOS.tcp)
+ self.nat44_add_static_mapping(self.pg7.remote_ip4, self.nat_addr,
+ self.udp_port_in, self.udp_port_out,
+ proto=IP_PROTOS.udp)
+ self.nat44_add_static_mapping(self.pg7.remote_ip4, self.nat_addr,
+ self.icmp_id_in, self.icmp_id_out,
+ proto=IP_PROTOS.icmp)
+ self.vapi.nat44_interface_add_del_feature(self.pg7.sw_if_index)
+ self.vapi.nat44_interface_add_del_feature(self.pg8.sw_if_index,
+ is_inside=0)
+
+ # out2in
+ pkts = self.create_stream_out(self.pg8)
+ self.pg8.add_stream(pkts)
+ self.pg_enable_capture(self.pg_interfaces)
+ self.pg_start()
+ capture = self.pg7.get_capture(len(pkts))
+ self.verify_capture_in(capture, self.pg7)
+
+ # in2out
+ pkts = self.create_stream_in(self.pg7, self.pg8)
+ self.pg7.add_stream(pkts)
+ self.pg_enable_capture(self.pg_interfaces)
+ self.pg_start()
+ capture = self.pg8.get_capture(len(pkts))
+ self.verify_capture_out(capture)
+
+ def test_static_unknown_proto(self):
+ """ 1:1 NAT translate packet with unknown protocol """
+ nat_ip = "10.0.0.10"
+ self.nat44_add_static_mapping(self.pg0.remote_ip4, nat_ip)
+ self.vapi.nat44_interface_add_del_feature(self.pg0.sw_if_index)
+ self.vapi.nat44_interface_add_del_feature(self.pg1.sw_if_index,
+ is_inside=0)
+
+ # in2out
+ p = (Ether(dst=self.pg0.local_mac, src=self.pg0.remote_mac) /
+ IP(src=self.pg0.remote_ip4, dst=self.pg1.remote_ip4) /
+ GRE() /
+ IP(src=self.pg2.remote_ip4, dst=self.pg3.remote_ip4) /
+ TCP(sport=1234, dport=1234))
+ self.pg0.add_stream(p)
+ self.pg_enable_capture(self.pg_interfaces)
+ self.pg_start()
+ p = self.pg1.get_capture(1)
+ packet = p[0]
+ try:
+ self.assertEqual(packet[IP].src, nat_ip)
+ self.assertEqual(packet[IP].dst, self.pg1.remote_ip4)
+ self.assertTrue(packet.haslayer(GRE))
+ self.check_ip_checksum(packet)
+ except:
+ self.logger.error(ppp("Unexpected or invalid packet:", packet))
+ raise
+
+ # out2in
+ p = (Ether(dst=self.pg1.local_mac, src=self.pg1.remote_mac) /
+ IP(src=self.pg1.remote_ip4, dst=nat_ip) /
+ GRE() /
+ IP(src=self.pg3.remote_ip4, dst=self.pg2.remote_ip4) /
+ TCP(sport=1234, dport=1234))
+ self.pg1.add_stream(p)
+ self.pg_enable_capture(self.pg_interfaces)
+ self.pg_start()
+ p = self.pg0.get_capture(1)
+ packet = p[0]
+ try:
+ self.assertEqual(packet[IP].src, self.pg1.remote_ip4)
+ self.assertEqual(packet[IP].dst, self.pg0.remote_ip4)
+ self.assertTrue(packet.haslayer(GRE))
+ self.check_ip_checksum(packet)
+ except:
+ self.logger.error(ppp("Unexpected or invalid packet:", packet))
+ raise
+
+ def test_hairpinning_static_unknown_proto(self):
+ """ 1:1 NAT translate packet with unknown protocol - hairpinning """
+
+ host = self.pg0.remote_hosts[0]
+ server = self.pg0.remote_hosts[1]
+
+ host_nat_ip = "10.0.0.10"
+ server_nat_ip = "10.0.0.11"
+
+ self.nat44_add_static_mapping(host.ip4, host_nat_ip)
+ self.nat44_add_static_mapping(server.ip4, server_nat_ip)
+ self.vapi.nat44_interface_add_del_feature(self.pg0.sw_if_index)
+ self.vapi.nat44_interface_add_del_feature(self.pg1.sw_if_index,
+ is_inside=0)
+
+ # host to server
+ p = (Ether(dst=self.pg0.local_mac, src=host.mac) /
+ IP(src=host.ip4, dst=server_nat_ip) /
+ GRE() /
+ IP(src=self.pg2.remote_ip4, dst=self.pg3.remote_ip4) /
+ TCP(sport=1234, dport=1234))
+ self.pg0.add_stream(p)
+ self.pg_enable_capture(self.pg_interfaces)
+ self.pg_start()
+ p = self.pg0.get_capture(1)
+ packet = p[0]
+ try:
+ self.assertEqual(packet[IP].src, host_nat_ip)
+ self.assertEqual(packet[IP].dst, server.ip4)
+ self.assertTrue(packet.haslayer(GRE))
+ self.check_ip_checksum(packet)
+ except:
+ self.logger.error(ppp("Unexpected or invalid packet:", packet))
+ raise
+
+ # server to host
+ p = (Ether(dst=self.pg0.local_mac, src=server.mac) /
+ IP(src=server.ip4, dst=host_nat_ip) /
+ GRE() /
+ IP(src=self.pg3.remote_ip4, dst=self.pg2.remote_ip4) /
+ TCP(sport=1234, dport=1234))
+ self.pg0.add_stream(p)
+ self.pg_enable_capture(self.pg_interfaces)
+ self.pg_start()
+ p = self.pg0.get_capture(1)
+ packet = p[0]
+ try:
+ self.assertEqual(packet[IP].src, server_nat_ip)
+ self.assertEqual(packet[IP].dst, host.ip4)
+ self.assertTrue(packet.haslayer(GRE))
+ self.check_ip_checksum(packet)
+ except:
+ self.logger.error(ppp("Unexpected or invalid packet:", packet))
+ raise
+
+ def test_unknown_proto(self):
+ """ NAT44 translate packet with unknown protocol """
+ self.nat44_add_address(self.nat_addr)
+ self.vapi.nat44_interface_add_del_feature(self.pg0.sw_if_index)
+ self.vapi.nat44_interface_add_del_feature(self.pg1.sw_if_index,
+ is_inside=0)
+
+ # in2out
+ p = (Ether(dst=self.pg0.local_mac, src=self.pg0.remote_mac) /
+ IP(src=self.pg0.remote_ip4, dst=self.pg1.remote_ip4) /
+ TCP(sport=self.tcp_port_in, dport=20))
+ self.pg0.add_stream(p)
+ self.pg_enable_capture(self.pg_interfaces)
+ self.pg_start()
+ p = self.pg1.get_capture(1)
+
+ p = (Ether(dst=self.pg0.local_mac, src=self.pg0.remote_mac) /
+ IP(src=self.pg0.remote_ip4, dst=self.pg1.remote_ip4) /
+ GRE() /
+ IP(src=self.pg2.remote_ip4, dst=self.pg3.remote_ip4) /
+ TCP(sport=1234, dport=1234))
+ self.pg0.add_stream(p)
+ self.pg_enable_capture(self.pg_interfaces)
+ self.pg_start()
+ p = self.pg1.get_capture(1)
+ packet = p[0]
+ try:
+ self.assertEqual(packet[IP].src, self.nat_addr)
+ self.assertEqual(packet[IP].dst, self.pg1.remote_ip4)
+ self.assertTrue(packet.haslayer(GRE))
+ self.check_ip_checksum(packet)
+ except:
+ self.logger.error(ppp("Unexpected or invalid packet:", packet))
+ raise
+
+ # out2in
+ p = (Ether(dst=self.pg1.local_mac, src=self.pg1.remote_mac) /
+ IP(src=self.pg1.remote_ip4, dst=self.nat_addr) /
+ GRE() /
+ IP(src=self.pg3.remote_ip4, dst=self.pg2.remote_ip4) /
+ TCP(sport=1234, dport=1234))
+ self.pg1.add_stream(p)
+ self.pg_enable_capture(self.pg_interfaces)
+ self.pg_start()
+ p = self.pg0.get_capture(1)
+ packet = p[0]
+ try:
+ self.assertEqual(packet[IP].src, self.pg1.remote_ip4)
+ self.assertEqual(packet[IP].dst, self.pg0.remote_ip4)
+ self.assertTrue(packet.haslayer(GRE))
+ self.check_ip_checksum(packet)
+ except:
+ self.logger.error(ppp("Unexpected or invalid packet:", packet))
+ raise
+
+ def test_hairpinning_unknown_proto(self):
+ """ NAT44 translate packet with unknown protocol - hairpinning """
+ host = self.pg0.remote_hosts[0]
+ server = self.pg0.remote_hosts[1]
+ host_in_port = 1234
+ host_out_port = 0
+ server_in_port = 5678
+ server_out_port = 8765
+ server_nat_ip = "10.0.0.11"
+
+ self.nat44_add_address(self.nat_addr)
+ self.vapi.nat44_interface_add_del_feature(self.pg0.sw_if_index)
+ self.vapi.nat44_interface_add_del_feature(self.pg1.sw_if_index,
+ is_inside=0)
+
+ # add static mapping for server
+ self.nat44_add_static_mapping(server.ip4, server_nat_ip)
+
+ # host to server
+ p = (Ether(src=host.mac, dst=self.pg0.local_mac) /
+ IP(src=host.ip4, dst=server_nat_ip) /
+ TCP(sport=host_in_port, dport=server_out_port))
+ self.pg0.add_stream(p)
+ self.pg_enable_capture(self.pg_interfaces)
+ self.pg_start()
+ capture = self.pg0.get_capture(1)
+
+ p = (Ether(dst=self.pg0.local_mac, src=host.mac) /
+ IP(src=host.ip4, dst=server_nat_ip) /
+ GRE() /
+ IP(src=self.pg2.remote_ip4, dst=self.pg3.remote_ip4) /
+ TCP(sport=1234, dport=1234))
+ self.pg0.add_stream(p)
+ self.pg_enable_capture(self.pg_interfaces)
+ self.pg_start()
+ p = self.pg0.get_capture(1)
+ packet = p[0]
+ try:
+ self.assertEqual(packet[IP].src, self.nat_addr)
+ self.assertEqual(packet[IP].dst, server.ip4)
+ self.assertTrue(packet.haslayer(GRE))
+ self.check_ip_checksum(packet)
+ except:
+ self.logger.error(ppp("Unexpected or invalid packet:", packet))
+ raise
+
+ # server to host
+ p = (Ether(dst=self.pg0.local_mac, src=server.mac) /
+ IP(src=server.ip4, dst=self.nat_addr) /
+ GRE() /
+ IP(src=self.pg3.remote_ip4, dst=self.pg2.remote_ip4) /
+ TCP(sport=1234, dport=1234))
+ self.pg0.add_stream(p)
+ self.pg_enable_capture(self.pg_interfaces)
+ self.pg_start()
+ p = self.pg0.get_capture(1)
+ packet = p[0]
+ try:
+ self.assertEqual(packet[IP].src, server_nat_ip)
+ self.assertEqual(packet[IP].dst, host.ip4)
+ self.assertTrue(packet.haslayer(GRE))
+ self.check_ip_checksum(packet)
+ except:
+ self.logger.error(ppp("Unexpected or invalid packet:", packet))
+ raise
+
+ def test_output_feature(self):
+ """ NAT44 interface output feature (in2out postrouting) """
+ self.nat44_add_address(self.nat_addr)
+ self.vapi.nat44_interface_add_del_output_feature(self.pg0.sw_if_index)
+ self.vapi.nat44_interface_add_del_output_feature(self.pg1.sw_if_index)
+ self.vapi.nat44_interface_add_del_output_feature(self.pg3.sw_if_index,
+ is_inside=0)
+
+ # in2out
+ pkts = self.create_stream_in(self.pg0, self.pg3)
+ self.pg0.add_stream(pkts)
+ self.pg_enable_capture(self.pg_interfaces)
+ self.pg_start()
+ capture = self.pg3.get_capture(len(pkts))
+ self.verify_capture_out(capture)
+
+ # out2in
+ pkts = self.create_stream_out(self.pg3)
+ self.pg3.add_stream(pkts)
+ self.pg_enable_capture(self.pg_interfaces)
+ self.pg_start()
+ capture = self.pg0.get_capture(len(pkts))
+ self.verify_capture_in(capture, self.pg0)
+
+ # from non-NAT interface to NAT inside interface
+ pkts = self.create_stream_in(self.pg2, self.pg0)
+ self.pg2.add_stream(pkts)
+ self.pg_enable_capture(self.pg_interfaces)
+ self.pg_start()
+ capture = self.pg0.get_capture(len(pkts))
+ self.verify_capture_no_translation(capture, self.pg2, self.pg0)
+
+ def test_output_feature_vrf_aware(self):
+ """ NAT44 interface output feature VRF aware (in2out postrouting) """
+ nat_ip_vrf10 = "10.0.0.10"
+ nat_ip_vrf20 = "10.0.0.20"
+
+ self.vapi.ip_add_del_route(dst_address=self.pg3.remote_ip4n,
+ dst_address_length=32,
+ next_hop_address=self.pg3.remote_ip4n,
+ next_hop_sw_if_index=self.pg3.sw_if_index,
+ table_id=10)
+ self.vapi.ip_add_del_route(dst_address=self.pg3.remote_ip4n,
+ dst_address_length=32,
+ next_hop_address=self.pg3.remote_ip4n,
+ next_hop_sw_if_index=self.pg3.sw_if_index,
+ table_id=20)
+
+ self.nat44_add_address(nat_ip_vrf10, vrf_id=10)
+ self.nat44_add_address(nat_ip_vrf20, vrf_id=20)
+ self.vapi.nat44_interface_add_del_output_feature(self.pg4.sw_if_index)
+ self.vapi.nat44_interface_add_del_output_feature(self.pg6.sw_if_index)
+ self.vapi.nat44_interface_add_del_output_feature(self.pg3.sw_if_index,
+ is_inside=0)
+
+ # in2out VRF 10
+ pkts = self.create_stream_in(self.pg4, self.pg3)
+ self.pg4.add_stream(pkts)
+ self.pg_enable_capture(self.pg_interfaces)
+ self.pg_start()
+ capture = self.pg3.get_capture(len(pkts))
+ self.verify_capture_out(capture, nat_ip=nat_ip_vrf10)
+
+ # out2in VRF 10
+ pkts = self.create_stream_out(self.pg3, dst_ip=nat_ip_vrf10)
+ self.pg3.add_stream(pkts)
+ self.pg_enable_capture(self.pg_interfaces)
+ self.pg_start()
+ capture = self.pg4.get_capture(len(pkts))
+ self.verify_capture_in(capture, self.pg4)
+
+ # in2out VRF 20
+ pkts = self.create_stream_in(self.pg6, self.pg3)
+ self.pg6.add_stream(pkts)
+ self.pg_enable_capture(self.pg_interfaces)
+ self.pg_start()
+ capture = self.pg3.get_capture(len(pkts))
+ self.verify_capture_out(capture, nat_ip=nat_ip_vrf20)
+
+ # out2in VRF 20
+ pkts = self.create_stream_out(self.pg3, dst_ip=nat_ip_vrf20)
+ self.pg3.add_stream(pkts)
+ self.pg_enable_capture(self.pg_interfaces)
+ self.pg_start()
+ capture = self.pg6.get_capture(len(pkts))
+ self.verify_capture_in(capture, self.pg6)
+
+ def test_output_feature_hairpinning(self):
+ """ NAT44 interface output feature hairpinning (in2out postrouting) """
+ host = self.pg0.remote_hosts[0]
+ server = self.pg0.remote_hosts[1]
+ host_in_port = 1234
+ host_out_port = 0
+ server_in_port = 5678
+ server_out_port = 8765
+
+ self.nat44_add_address(self.nat_addr)
+ self.vapi.nat44_interface_add_del_output_feature(self.pg0.sw_if_index)
+ self.vapi.nat44_interface_add_del_output_feature(self.pg1.sw_if_index,
+ is_inside=0)
+
+ # add static mapping for server
+ self.nat44_add_static_mapping(server.ip4, self.nat_addr,
+ server_in_port, server_out_port,
+ proto=IP_PROTOS.tcp)
+
+ # send packet from host to server
+ p = (Ether(src=host.mac, dst=self.pg0.local_mac) /
+ IP(src=host.ip4, dst=self.nat_addr) /
+ TCP(sport=host_in_port, dport=server_out_port))
+ self.pg0.add_stream(p)
+ self.pg_enable_capture(self.pg_interfaces)
+ self.pg_start()
+ capture = self.pg0.get_capture(1)
+ p = capture[0]
+ try:
+ ip = p[IP]
+ tcp = p[TCP]
+ self.assertEqual(ip.src, self.nat_addr)
+ self.assertEqual(ip.dst, server.ip4)
+ self.assertNotEqual(tcp.sport, host_in_port)
+ self.assertEqual(tcp.dport, server_in_port)
+ self.check_tcp_checksum(p)
+ host_out_port = tcp.sport
+ except:
+ self.logger.error(ppp("Unexpected or invalid packet:", p))
+ raise
+
+ # send reply from server to host
+ p = (Ether(src=server.mac, dst=self.pg0.local_mac) /
+ IP(src=server.ip4, dst=self.nat_addr) /
+ TCP(sport=server_in_port, dport=host_out_port))
+ self.pg0.add_stream(p)
+ self.pg_enable_capture(self.pg_interfaces)
+ self.pg_start()
+ capture = self.pg0.get_capture(1)
+ p = capture[0]
+ try:
+ ip = p[IP]
+ tcp = p[TCP]
+ self.assertEqual(ip.src, self.nat_addr)
+ self.assertEqual(ip.dst, host.ip4)
+ self.assertEqual(tcp.sport, server_out_port)
+ self.assertEqual(tcp.dport, host_in_port)
+ self.check_tcp_checksum(p)
+ except:
+ self.logger.error(ppp("Unexpected or invalid packet:"), p)
+ raise
+
+ def tearDown(self):
+ super(TestNAT44, self).tearDown()
+ if not self.vpp_dead:
+ self.logger.info(self.vapi.cli("show nat44 verbose"))
+ self.clear_nat44()
+
+
+class TestDeterministicNAT(MethodHolder):
+ """ Deterministic NAT Test Cases """
+
+ @classmethod
+ def setUpConstants(cls):
+ super(TestDeterministicNAT, cls).setUpConstants()
+ cls.vpp_cmdline.extend(["nat", "{", "deterministic", "}"])
+
+ @classmethod
+ def setUpClass(cls):
+ super(TestDeterministicNAT, cls).setUpClass()
+
+ try:
+ cls.tcp_port_in = 6303
+ cls.tcp_external_port = 6303
+ cls.udp_port_in = 6304
+ cls.udp_external_port = 6304
+ cls.icmp_id_in = 6305
+ cls.nat_addr = '10.0.0.3'
+
+ cls.create_pg_interfaces(range(3))
+ cls.interfaces = list(cls.pg_interfaces)
+
+ for i in cls.interfaces:
+ i.admin_up()
+ i.config_ip4()
+ i.resolve_arp()
+
+ cls.pg0.generate_remote_hosts(2)
+ cls.pg0.configure_ipv4_neighbors()
+
+ except Exception:
+ super(TestDeterministicNAT, cls).tearDownClass()
+ raise
+
+ def create_stream_in(self, in_if, out_if, ttl=64):
+ """
+ Create packet stream for inside network
+
+ :param in_if: Inside interface
+ :param out_if: Outside interface
+ :param ttl: TTL of generated packets
+ """
+ pkts = []
+ # TCP
+ p = (Ether(dst=in_if.local_mac, src=in_if.remote_mac) /
+ IP(src=in_if.remote_ip4, dst=out_if.remote_ip4, ttl=ttl) /
+ TCP(sport=self.tcp_port_in, dport=self.tcp_external_port))
+ pkts.append(p)
+
+ # UDP
+ p = (Ether(dst=in_if.local_mac, src=in_if.remote_mac) /
+ IP(src=in_if.remote_ip4, dst=out_if.remote_ip4, ttl=ttl) /
+ UDP(sport=self.udp_port_in, dport=self.udp_external_port))
+ pkts.append(p)
+
+ # ICMP
+ p = (Ether(dst=in_if.local_mac, src=in_if.remote_mac) /
+ IP(src=in_if.remote_ip4, dst=out_if.remote_ip4, ttl=ttl) /
+ ICMP(id=self.icmp_id_in, type='echo-request'))
+ pkts.append(p)
+
+ return pkts
+
+ def create_stream_out(self, out_if, dst_ip=None, ttl=64):
+ """
+ Create packet stream for outside network
+
+ :param out_if: Outside interface
+ :param dst_ip: Destination IP address (Default use global NAT address)
+ :param ttl: TTL of generated packets
+ """
+ if dst_ip is None:
+ dst_ip = self.nat_addr
+ pkts = []
+ # TCP
+ p = (Ether(dst=out_if.local_mac, src=out_if.remote_mac) /
+ IP(src=out_if.remote_ip4, dst=dst_ip, ttl=ttl) /
+ TCP(dport=self.tcp_port_out, sport=self.tcp_external_port))
+ pkts.append(p)
+
+ # UDP
+ p = (Ether(dst=out_if.local_mac, src=out_if.remote_mac) /
+ IP(src=out_if.remote_ip4, dst=dst_ip, ttl=ttl) /
+ UDP(dport=self.udp_port_out, sport=self.udp_external_port))
+ pkts.append(p)
+
+ # ICMP
+ p = (Ether(dst=out_if.local_mac, src=out_if.remote_mac) /
+ IP(src=out_if.remote_ip4, dst=dst_ip, ttl=ttl) /
+ ICMP(id=self.icmp_external_id, type='echo-reply'))
+ pkts.append(p)
+
+ return pkts
+
+ def verify_capture_out(self, capture, nat_ip=None, packet_num=3):
+ """
+ Verify captured packets on outside network
+
+ :param capture: Captured packets
+ :param nat_ip: Translated IP address (Default use global NAT address)
+ :param same_port: Sorce port number is not translated (Default False)
+ :param packet_num: Expected number of packets (Default 3)
+ """
+ if nat_ip is None:
+ nat_ip = self.nat_addr
+ self.assertEqual(packet_num, len(capture))
+ for packet in capture:
+ try:
+ self.assertEqual(packet[IP].src, nat_ip)
+ if packet.haslayer(TCP):
+ self.tcp_port_out = packet[TCP].sport
+ elif packet.haslayer(UDP):
+ self.udp_port_out = packet[UDP].sport
+ else:
+ self.icmp_external_id = packet[ICMP].id
+ except:
+ self.logger.error(ppp("Unexpected or invalid packet "
+ "(outside network):", packet))
+ raise
+
+ def initiate_tcp_session(self, in_if, out_if):
+ """
+ Initiates TCP session
+
+ :param in_if: Inside interface
+ :param out_if: Outside interface
+ """
+ try:
+ # SYN packet in->out
+ p = (Ether(src=in_if.remote_mac, dst=in_if.local_mac) /
+ IP(src=in_if.remote_ip4, dst=out_if.remote_ip4) /
+ TCP(sport=self.tcp_port_in, dport=self.tcp_external_port,
+ flags="S"))
+ in_if.add_stream(p)
+ self.pg_enable_capture(self.pg_interfaces)
+ self.pg_start()
+ capture = out_if.get_capture(1)
+ p = capture[0]
+ self.tcp_port_out = p[TCP].sport
+
+ # SYN + ACK packet out->in
+ p = (Ether(src=out_if.remote_mac, dst=out_if.local_mac) /
+ IP(src=out_if.remote_ip4, dst=self.nat_addr) /
+ TCP(sport=self.tcp_external_port, dport=self.tcp_port_out,
+ flags="SA"))
+ out_if.add_stream(p)
+ self.pg_enable_capture(self.pg_interfaces)
+ self.pg_start()
+ in_if.get_capture(1)
+
+ # ACK packet in->out
+ p = (Ether(src=in_if.remote_mac, dst=in_if.local_mac) /
+ IP(src=in_if.remote_ip4, dst=out_if.remote_ip4) /
+ TCP(sport=self.tcp_port_in, dport=self.tcp_external_port,
+ flags="A"))
+ in_if.add_stream(p)
+ self.pg_enable_capture(self.pg_interfaces)
+ self.pg_start()
+ out_if.get_capture(1)
+
+ except:
+ self.logger.error("TCP 3 way handshake failed")
+ raise
+
+ def verify_ipfix_max_entries_per_user(self, data):
+ """
+ Verify IPFIX maximum entries per user exceeded event
+
+ :param data: Decoded IPFIX data records
+ """
+ self.assertEqual(1, len(data))
+ record = data[0]
+ # natEvent
+ self.assertEqual(ord(record[230]), 13)
+ # natQuotaExceededEvent
+ self.assertEqual('\x03\x00\x00\x00', record[466])
+ # sourceIPv4Address
+ self.assertEqual(self.pg0.remote_ip4n, record[8])
+
+ def test_deterministic_mode(self):
+ """ NAT plugin run deterministic mode """
+ in_addr = '172.16.255.0'
+ out_addr = '172.17.255.50'
+ in_addr_t = '172.16.255.20'
+ in_addr_n = socket.inet_aton(in_addr)
+ out_addr_n = socket.inet_aton(out_addr)
+ in_addr_t_n = socket.inet_aton(in_addr_t)
+ in_plen = 24
+ out_plen = 32
+
+ nat_config = self.vapi.nat_show_config()
+ self.assertEqual(1, nat_config.deterministic)
+
+ self.vapi.nat_det_add_del_map(in_addr_n, in_plen, out_addr_n, out_plen)
+
+ rep1 = self.vapi.nat_det_forward(in_addr_t_n)
+ self.assertEqual(rep1.out_addr[:4], out_addr_n)
+ rep2 = self.vapi.nat_det_reverse(out_addr_n, rep1.out_port_hi)
+ self.assertEqual(rep2.in_addr[:4], in_addr_t_n)
+
+ deterministic_mappings = self.vapi.nat_det_map_dump()
+ self.assertEqual(len(deterministic_mappings), 1)
+ dsm = deterministic_mappings[0]
+ self.assertEqual(in_addr_n, dsm.in_addr[:4])
+ self.assertEqual(in_plen, dsm.in_plen)
+ self.assertEqual(out_addr_n, dsm.out_addr[:4])
+ self.assertEqual(out_plen, dsm.out_plen)
+
+ self.clear_nat_det()
+ deterministic_mappings = self.vapi.nat_det_map_dump()
+ self.assertEqual(len(deterministic_mappings), 0)
+
+ def test_set_timeouts(self):
+ """ Set deterministic NAT timeouts """
+ timeouts_before = self.vapi.nat_det_get_timeouts()
+
+ self.vapi.nat_det_set_timeouts(timeouts_before.udp + 10,
+ timeouts_before.tcp_established + 10,
+ timeouts_before.tcp_transitory + 10,
+ timeouts_before.icmp + 10)
+
+ timeouts_after = self.vapi.nat_det_get_timeouts()
+
+ self.assertNotEqual(timeouts_before.udp, timeouts_after.udp)
+ self.assertNotEqual(timeouts_before.icmp, timeouts_after.icmp)
+ self.assertNotEqual(timeouts_before.tcp_established,
+ timeouts_after.tcp_established)
+ self.assertNotEqual(timeouts_before.tcp_transitory,
+ timeouts_after.tcp_transitory)
+
+ def test_det_in(self):
+ """ Deterministic NAT translation test (TCP, UDP, ICMP) """
+
+ nat_ip = "10.0.0.10"
+
+ self.vapi.nat_det_add_del_map(self.pg0.remote_ip4n,
+ 32,
+ socket.inet_aton(nat_ip),
+ 32)
+ self.vapi.nat44_interface_add_del_feature(self.pg0.sw_if_index)
+ self.vapi.nat44_interface_add_del_feature(self.pg1.sw_if_index,
+ is_inside=0)
+
+ # in2out
+ pkts = self.create_stream_in(self.pg0, self.pg1)
+ self.pg0.add_stream(pkts)
+ self.pg_enable_capture(self.pg_interfaces)
+ self.pg_start()
+ capture = self.pg1.get_capture(len(pkts))
+ self.verify_capture_out(capture, nat_ip)
+
+ # out2in
+ pkts = self.create_stream_out(self.pg1, nat_ip)
+ self.pg1.add_stream(pkts)
+ self.pg_enable_capture(self.pg_interfaces)
+ self.pg_start()
+ capture = self.pg0.get_capture(len(pkts))
+ self.verify_capture_in(capture, self.pg0)
+
+ # session dump test
+ sessions = self.vapi.nat_det_session_dump(self.pg0.remote_ip4n)
+ self.assertEqual(len(sessions), 3)
+
+ # TCP session
+ s = sessions[0]
+ self.assertEqual(s.ext_addr[:4], self.pg1.remote_ip4n)
+ self.assertEqual(s.in_port, self.tcp_port_in)
+ self.assertEqual(s.out_port, self.tcp_port_out)
+ self.assertEqual(s.ext_port, self.tcp_external_port)
+
+ # UDP session
+ s = sessions[1]
+ self.assertEqual(s.ext_addr[:4], self.pg1.remote_ip4n)
+ self.assertEqual(s.in_port, self.udp_port_in)
+ self.assertEqual(s.out_port, self.udp_port_out)
+ self.assertEqual(s.ext_port, self.udp_external_port)
+
+ # ICMP session
+ s = sessions[2]
+ self.assertEqual(s.ext_addr[:4], self.pg1.remote_ip4n)
+ self.assertEqual(s.in_port, self.icmp_id_in)
+ self.assertEqual(s.out_port, self.icmp_external_id)
+
+ def test_multiple_users(self):
+ """ Deterministic NAT multiple users """
+
+ nat_ip = "10.0.0.10"
+ port_in = 80
+ external_port = 6303
+
+ host0 = self.pg0.remote_hosts[0]
+ host1 = self.pg0.remote_hosts[1]
+
+ self.vapi.nat_det_add_del_map(host0.ip4n,
+ 24,
+ socket.inet_aton(nat_ip),
+ 32)
+ self.vapi.nat44_interface_add_del_feature(self.pg0.sw_if_index)
+ self.vapi.nat44_interface_add_del_feature(self.pg1.sw_if_index,
+ is_inside=0)
+
+ # host0 to out
+ p = (Ether(src=host0.mac, dst=self.pg0.local_mac) /
+ IP(src=host0.ip4, dst=self.pg1.remote_ip4) /
+ TCP(sport=port_in, dport=external_port))
+ self.pg0.add_stream(p)
+ self.pg_enable_capture(self.pg_interfaces)
+ self.pg_start()
+ capture = self.pg1.get_capture(1)
+ p = capture[0]
+ try:
+ ip = p[IP]
+ tcp = p[TCP]
+ self.assertEqual(ip.src, nat_ip)
+ self.assertEqual(ip.dst, self.pg1.remote_ip4)
+ self.assertEqual(tcp.dport, external_port)
+ port_out0 = tcp.sport
+ except:
+ self.logger.error(ppp("Unexpected or invalid packet:", p))
+ raise
+
+ # host1 to out
+ p = (Ether(src=host1.mac, dst=self.pg0.local_mac) /
+ IP(src=host1.ip4, dst=self.pg1.remote_ip4) /
+ TCP(sport=port_in, dport=external_port))
+ self.pg0.add_stream(p)
+ self.pg_enable_capture(self.pg_interfaces)
+ self.pg_start()
+ capture = self.pg1.get_capture(1)
+ p = capture[0]
+ try:
+ ip = p[IP]
+ tcp = p[TCP]
+ self.assertEqual(ip.src, nat_ip)
+ self.assertEqual(ip.dst, self.pg1.remote_ip4)
+ self.assertEqual(tcp.dport, external_port)
+ port_out1 = tcp.sport
+ except:
+ self.logger.error(ppp("Unexpected or invalid packet:", p))
+ raise
+
+ dms = self.vapi.nat_det_map_dump()
+ self.assertEqual(1, len(dms))
+ self.assertEqual(2, dms[0].ses_num)
+
+ # out to host0
+ p = (Ether(src=self.pg1.remote_mac, dst=self.pg1.local_mac) /
+ IP(src=self.pg1.remote_ip4, dst=nat_ip) /
+ TCP(sport=external_port, dport=port_out0))
+ self.pg1.add_stream(p)
+ self.pg_enable_capture(self.pg_interfaces)
+ self.pg_start()
+ capture = self.pg0.get_capture(1)
+ p = capture[0]
+ try:
+ ip = p[IP]
+ tcp = p[TCP]
+ self.assertEqual(ip.src, self.pg1.remote_ip4)
+ self.assertEqual(ip.dst, host0.ip4)
+ self.assertEqual(tcp.dport, port_in)
+ self.assertEqual(tcp.sport, external_port)
+ except:
+ self.logger.error(ppp("Unexpected or invalid packet:", p))
+ raise
+
+ # out to host1
+ p = (Ether(src=self.pg1.remote_mac, dst=self.pg1.local_mac) /
+ IP(src=self.pg1.remote_ip4, dst=nat_ip) /
+ TCP(sport=external_port, dport=port_out1))
+ self.pg1.add_stream(p)
+ self.pg_enable_capture(self.pg_interfaces)
+ self.pg_start()
+ capture = self.pg0.get_capture(1)
+ p = capture[0]
+ try:
+ ip = p[IP]
+ tcp = p[TCP]
+ self.assertEqual(ip.src, self.pg1.remote_ip4)
+ self.assertEqual(ip.dst, host1.ip4)
+ self.assertEqual(tcp.dport, port_in)
+ self.assertEqual(tcp.sport, external_port)
+ except:
+ self.logger.error(ppp("Unexpected or invalid packet", p))
+ raise
+
+ # session close api test
+ self.vapi.nat_det_close_session_out(socket.inet_aton(nat_ip),
+ port_out1,
+ self.pg1.remote_ip4n,
+ external_port)
+ dms = self.vapi.nat_det_map_dump()
+ self.assertEqual(dms[0].ses_num, 1)
+
+ self.vapi.nat_det_close_session_in(host0.ip4n,
+ port_in,
+ self.pg1.remote_ip4n,
+ external_port)
+ dms = self.vapi.nat_det_map_dump()
+ self.assertEqual(dms[0].ses_num, 0)
+
+ def test_tcp_session_close_detection_in(self):
+ """ Deterministic NAT TCP session close from inside network """
+ self.vapi.nat_det_add_del_map(self.pg0.remote_ip4n,
+ 32,
+ socket.inet_aton(self.nat_addr),
+ 32)
+ self.vapi.nat44_interface_add_del_feature(self.pg0.sw_if_index)
+ self.vapi.nat44_interface_add_del_feature(self.pg1.sw_if_index,
+ is_inside=0)
+
+ self.initiate_tcp_session(self.pg0, self.pg1)
+
+ # close the session from inside
+ try:
+ # FIN packet in -> out
+ p = (Ether(src=self.pg0.remote_mac, dst=self.pg0.local_mac) /
+ IP(src=self.pg0.remote_ip4, dst=self.pg1.remote_ip4) /
+ TCP(sport=self.tcp_port_in, dport=self.tcp_external_port,
+ flags="F"))
+ self.pg0.add_stream(p)
+ self.pg_enable_capture(self.pg_interfaces)
+ self.pg_start()
+ self.pg1.get_capture(1)
+
+ pkts = []
+
+ # ACK packet out -> in
+ p = (Ether(src=self.pg1.remote_mac, dst=self.pg1.local_mac) /
+ IP(src=self.pg1.remote_ip4, dst=self.nat_addr) /
+ TCP(sport=self.tcp_external_port, dport=self.tcp_port_out,
+ flags="A"))
+ pkts.append(p)
+
+ # FIN packet out -> in
+ p = (Ether(src=self.pg1.remote_mac, dst=self.pg1.local_mac) /
+ IP(src=self.pg1.remote_ip4, dst=self.nat_addr) /
+ TCP(sport=self.tcp_external_port, dport=self.tcp_port_out,
+ flags="F"))
+ pkts.append(p)
+
+ self.pg1.add_stream(pkts)
+ self.pg_enable_capture(self.pg_interfaces)
+ self.pg_start()
+ self.pg0.get_capture(2)
+
+ # ACK packet in -> out
+ p = (Ether(src=self.pg0.remote_mac, dst=self.pg0.local_mac) /
+ IP(src=self.pg0.remote_ip4, dst=self.pg1.remote_ip4) /
+ TCP(sport=self.tcp_port_in, dport=self.tcp_external_port,
+ flags="A"))
+ self.pg0.add_stream(p)
+ self.pg_enable_capture(self.pg_interfaces)
+ self.pg_start()
+ self.pg1.get_capture(1)
+
+ # Check if deterministic NAT44 closed the session
+ dms = self.vapi.nat_det_map_dump()
+ self.assertEqual(0, dms[0].ses_num)
+ except:
+ self.logger.error("TCP session termination failed")
+ raise
+
+ def test_tcp_session_close_detection_out(self):
+ """ Deterministic NAT TCP session close from outside network """
+ self.vapi.nat_det_add_del_map(self.pg0.remote_ip4n,
+ 32,
+ socket.inet_aton(self.nat_addr),
+ 32)
+ self.vapi.nat44_interface_add_del_feature(self.pg0.sw_if_index)
+ self.vapi.nat44_interface_add_del_feature(self.pg1.sw_if_index,
+ is_inside=0)
+
+ self.initiate_tcp_session(self.pg0, self.pg1)
+
+ # close the session from outside
+ try:
+ # FIN packet out -> in
+ p = (Ether(src=self.pg1.remote_mac, dst=self.pg1.local_mac) /
+ IP(src=self.pg1.remote_ip4, dst=self.nat_addr) /
+ TCP(sport=self.tcp_external_port, dport=self.tcp_port_out,
+ flags="F"))
+ self.pg1.add_stream(p)
+ self.pg_enable_capture(self.pg_interfaces)
+ self.pg_start()
+ self.pg0.get_capture(1)
+
+ pkts = []
+
+ # ACK packet in -> out
+ p = (Ether(src=self.pg0.remote_mac, dst=self.pg0.local_mac) /
+ IP(src=self.pg0.remote_ip4, dst=self.pg1.remote_ip4) /
+ TCP(sport=self.tcp_port_in, dport=self.tcp_external_port,
+ flags="A"))
+ pkts.append(p)
+
+ # ACK packet in -> out
+ p = (Ether(src=self.pg0.remote_mac, dst=self.pg0.local_mac) /
+ IP(src=self.pg0.remote_ip4, dst=self.pg1.remote_ip4) /
+ TCP(sport=self.tcp_port_in, dport=self.tcp_external_port,
+ flags="F"))
+ pkts.append(p)
+
+ self.pg0.add_stream(pkts)
+ self.pg_enable_capture(self.pg_interfaces)
+ self.pg_start()
+ self.pg1.get_capture(2)
+
+ # ACK packet out -> in
+ p = (Ether(src=self.pg1.remote_mac, dst=self.pg1.local_mac) /
+ IP(src=self.pg1.remote_ip4, dst=self.nat_addr) /
+ TCP(sport=self.tcp_external_port, dport=self.tcp_port_out,
+ flags="A"))
+ self.pg1.add_stream(p)
+ self.pg_enable_capture(self.pg_interfaces)
+ self.pg_start()
+ self.pg0.get_capture(1)
+
+ # Check if deterministic NAT44 closed the session
+ dms = self.vapi.nat_det_map_dump()
+ self.assertEqual(0, dms[0].ses_num)
+ except:
+ self.logger.error("TCP session termination failed")
+ raise
+
+ @unittest.skipUnless(running_extended_tests(), "part of extended tests")
+ def test_session_timeout(self):
+ """ Deterministic NAT session timeouts """
+ self.vapi.nat_det_add_del_map(self.pg0.remote_ip4n,
+ 32,
+ socket.inet_aton(self.nat_addr),
+ 32)
+ self.vapi.nat44_interface_add_del_feature(self.pg0.sw_if_index)
+ self.vapi.nat44_interface_add_del_feature(self.pg1.sw_if_index,
+ is_inside=0)
+
+ self.initiate_tcp_session(self.pg0, self.pg1)
+ self.vapi.nat_det_set_timeouts(5, 5, 5, 5)
+ pkts = self.create_stream_in(self.pg0, self.pg1)
+ self.pg0.add_stream(pkts)
+ self.pg_enable_capture(self.pg_interfaces)
+ self.pg_start()
+ capture = self.pg1.get_capture(len(pkts))
+ sleep(15)
+
+ dms = self.vapi.nat_det_map_dump()
+ self.assertEqual(0, dms[0].ses_num)
+
+ @unittest.skipUnless(running_extended_tests(), "part of extended tests")
+ def test_session_limit_per_user(self):
+ """ Deterministic NAT maximum sessions per user limit """
+ self.vapi.nat_det_add_del_map(self.pg0.remote_ip4n,
+ 32,
+ socket.inet_aton(self.nat_addr),
+ 32)
+ self.vapi.nat44_interface_add_del_feature(self.pg0.sw_if_index)
+ self.vapi.nat44_interface_add_del_feature(self.pg1.sw_if_index,
+ is_inside=0)
+ self.vapi.set_ipfix_exporter(collector_address=self.pg2.remote_ip4n,
+ src_address=self.pg2.local_ip4n,
+ path_mtu=512,
+ template_interval=10)
+ self.vapi.nat_ipfix()
+
+ pkts = []
+ for port in range(1025, 2025):
+ p = (Ether(src=self.pg0.remote_mac, dst=self.pg0.local_mac) /
+ IP(src=self.pg0.remote_ip4, dst=self.pg1.remote_ip4) /
+ UDP(sport=port, dport=port))
+ pkts.append(p)
+
+ self.pg0.add_stream(pkts)
+ self.pg_enable_capture(self.pg_interfaces)
+ self.pg_start()
+ capture = self.pg1.get_capture(len(pkts))
+
+ p = (Ether(src=self.pg0.remote_mac, dst=self.pg0.local_mac) /
+ IP(src=self.pg0.remote_ip4, dst=self.pg1.remote_ip4) /
+ UDP(sport=3001, dport=3002))
+ self.pg0.add_stream(p)
+ self.pg_enable_capture(self.pg_interfaces)
+ self.pg_start()
+ capture = self.pg1.assert_nothing_captured()
+
+ # verify ICMP error packet
+ capture = self.pg0.get_capture(1)
+ p = capture[0]
+ self.assertTrue(p.haslayer(ICMP))
+ icmp = p[ICMP]
+ self.assertEqual(icmp.type, 3)
+ self.assertEqual(icmp.code, 1)
+ self.assertTrue(icmp.haslayer(IPerror))
+ inner_ip = icmp[IPerror]
+ self.assertEqual(inner_ip[UDPerror].sport, 3001)
+ self.assertEqual(inner_ip[UDPerror].dport, 3002)
+
+ dms = self.vapi.nat_det_map_dump()
+
+ self.assertEqual(1000, dms[0].ses_num)
+
+ # verify IPFIX logging
+ self.vapi.cli("ipfix flush") # FIXME this should be an API call
+ sleep(1)
+ capture = self.pg2.get_capture(2)
+ ipfix = IPFIXDecoder()
+ # first load template
+ for p in capture:
+ self.assertTrue(p.haslayer(IPFIX))
+ if p.haslayer(Template):
+ ipfix.add_template(p.getlayer(Template))
+ # verify events in data set
+ for p in capture:
+ if p.haslayer(Data):
+ data = ipfix.decode_data_set(p.getlayer(Set))
+ self.verify_ipfix_max_entries_per_user(data)
+
+ def clear_nat_det(self):
+ """
+ Clear deterministic NAT configuration.
+ """
+ self.vapi.nat_ipfix(enable=0)
+ self.vapi.nat_det_set_timeouts()
+ deterministic_mappings = self.vapi.nat_det_map_dump()
+ for dsm in deterministic_mappings:
+ self.vapi.nat_det_add_del_map(dsm.in_addr,
+ dsm.in_plen,
+ dsm.out_addr,
+ dsm.out_plen,
+ is_add=0)
+
+ interfaces = self.vapi.nat44_interface_dump()
+ for intf in interfaces:
+ self.vapi.nat44_interface_add_del_feature(intf.sw_if_index,
+ intf.is_inside,
+ is_add=0)
+
+ def tearDown(self):
+ super(TestDeterministicNAT, self).tearDown()
+ if not self.vpp_dead:
+ self.logger.info(self.vapi.cli("show nat44 detail"))
+ self.clear_nat_det()
+
+
+class TestNAT64(MethodHolder):
+ """ NAT64 Test Cases """
+
+ @classmethod
+ def setUpClass(cls):
+ super(TestNAT64, cls).setUpClass()
+
+ try:
+ cls.tcp_port_in = 6303
+ cls.tcp_port_out = 6303
+ cls.udp_port_in = 6304
+ cls.udp_port_out = 6304
+ cls.icmp_id_in = 6305
+ cls.icmp_id_out = 6305
+ cls.nat_addr = '10.0.0.3'
+ cls.nat_addr_n = socket.inet_pton(socket.AF_INET, cls.nat_addr)
+ cls.vrf1_id = 10
+ cls.vrf1_nat_addr = '10.0.10.3'
+ cls.vrf1_nat_addr_n = socket.inet_pton(socket.AF_INET,
+ cls.vrf1_nat_addr)
+
+ cls.create_pg_interfaces(range(3))
+ cls.ip6_interfaces = list(cls.pg_interfaces[0:1])
+ cls.ip6_interfaces.append(cls.pg_interfaces[2])
+ cls.ip4_interfaces = list(cls.pg_interfaces[1:2])
+
+ cls.vapi.ip_table_add_del(cls.vrf1_id, is_add=1, is_ipv6=1)
+
+ cls.pg_interfaces[2].set_table_ip6(cls.vrf1_id)
+
+ cls.pg0.generate_remote_hosts(2)
+
+ for i in cls.ip6_interfaces:
+ i.admin_up()
+ i.config_ip6()
+ i.configure_ipv6_neighbors()
+
+ for i in cls.ip4_interfaces:
+ i.admin_up()
+ i.config_ip4()
+ i.resolve_arp()
+
+ except Exception:
+ super(TestNAT64, cls).tearDownClass()
+ raise
+
+ def test_pool(self):
+ """ Add/delete address to NAT64 pool """
+ nat_addr = socket.inet_pton(socket.AF_INET, '1.2.3.4')
+
+ self.vapi.nat64_add_del_pool_addr_range(nat_addr, nat_addr)
+
+ addresses = self.vapi.nat64_pool_addr_dump()
+ self.assertEqual(len(addresses), 1)
+ self.assertEqual(addresses[0].address, nat_addr)
+
+ self.vapi.nat64_add_del_pool_addr_range(nat_addr, nat_addr, is_add=0)
+
+ addresses = self.vapi.nat64_pool_addr_dump()
+ self.assertEqual(len(addresses), 0)
+
+ def test_interface(self):
+ """ Enable/disable NAT64 feature on the interface """
+ self.vapi.nat64_add_del_interface(self.pg0.sw_if_index)
+ self.vapi.nat64_add_del_interface(self.pg1.sw_if_index, is_inside=0)
+
+ interfaces = self.vapi.nat64_interface_dump()
+ self.assertEqual(len(interfaces), 2)
+ pg0_found = False
+ pg1_found = False
+ for intf in interfaces:
+ if intf.sw_if_index == self.pg0.sw_if_index:
+ self.assertEqual(intf.is_inside, 1)
+ pg0_found = True
+ elif intf.sw_if_index == self.pg1.sw_if_index:
+ self.assertEqual(intf.is_inside, 0)
+ pg1_found = True
+ self.assertTrue(pg0_found)
+ self.assertTrue(pg1_found)
+
+ features = self.vapi.cli("show interface features pg0")
+ self.assertNotEqual(features.find('nat64-in2out'), -1)
+ features = self.vapi.cli("show interface features pg1")
+ self.assertNotEqual(features.find('nat64-out2in'), -1)
+
+ self.vapi.nat64_add_del_interface(self.pg0.sw_if_index, is_add=0)
+ self.vapi.nat64_add_del_interface(self.pg1.sw_if_index, is_add=0)
+
+ interfaces = self.vapi.nat64_interface_dump()
+ self.assertEqual(len(interfaces), 0)
+
+ def test_static_bib(self):
+ """ Add/delete static BIB entry """
+ in_addr = socket.inet_pton(socket.AF_INET6,
+ '2001:db8:85a3::8a2e:370:7334')
+ out_addr = socket.inet_pton(socket.AF_INET, '10.1.1.3')
+ in_port = 1234
+ out_port = 5678
+ proto = IP_PROTOS.tcp
+
+ self.vapi.nat64_add_del_static_bib(in_addr,
+ out_addr,
+ in_port,
+ out_port,
+ proto)
+ bib = self.vapi.nat64_bib_dump(IP_PROTOS.tcp)
+ static_bib_num = 0
+ for bibe in bib:
+ if bibe.is_static:
+ static_bib_num += 1
+ self.assertEqual(bibe.i_addr, in_addr)
+ self.assertEqual(bibe.o_addr, out_addr)
+ self.assertEqual(bibe.i_port, in_port)
+ self.assertEqual(bibe.o_port, out_port)
+ self.assertEqual(static_bib_num, 1)
+
+ self.vapi.nat64_add_del_static_bib(in_addr,
+ out_addr,
+ in_port,
+ out_port,
+ proto,
+ is_add=0)
+ bib = self.vapi.nat64_bib_dump(IP_PROTOS.tcp)
+ static_bib_num = 0
+ for bibe in bib:
+ if bibe.is_static:
+ static_bib_num += 1
+ self.assertEqual(static_bib_num, 0)
+
+ def test_set_timeouts(self):
+ """ Set NAT64 timeouts """
+ # verify default values
+ timeouts = self.vapi.nat64_get_timeouts()
+ self.assertEqual(timeouts.udp, 300)
+ self.assertEqual(timeouts.icmp, 60)
+ self.assertEqual(timeouts.tcp_trans, 240)
+ self.assertEqual(timeouts.tcp_est, 7440)
+ self.assertEqual(timeouts.tcp_incoming_syn, 6)
+
+ # set and verify custom values
+ self.vapi.nat64_set_timeouts(udp=200, icmp=30, tcp_trans=250,
+ tcp_est=7450, tcp_incoming_syn=10)
+ timeouts = self.vapi.nat64_get_timeouts()
+ self.assertEqual(timeouts.udp, 200)
+ self.assertEqual(timeouts.icmp, 30)
+ self.assertEqual(timeouts.tcp_trans, 250)
+ self.assertEqual(timeouts.tcp_est, 7450)
+ self.assertEqual(timeouts.tcp_incoming_syn, 10)
+
+ def test_dynamic(self):
+ """ NAT64 dynamic translation test """
+ self.tcp_port_in = 6303
+ self.udp_port_in = 6304
+ self.icmp_id_in = 6305
+
+ ses_num_start = self.nat64_get_ses_num()
+
+ self.vapi.nat64_add_del_pool_addr_range(self.nat_addr_n,
+ self.nat_addr_n)
+ self.vapi.nat64_add_del_interface(self.pg0.sw_if_index)
+ self.vapi.nat64_add_del_interface(self.pg1.sw_if_index, is_inside=0)
+
+ # in2out
+ pkts = self.create_stream_in_ip6(self.pg0, self.pg1)
+ self.pg0.add_stream(pkts)
+ self.pg_enable_capture(self.pg_interfaces)
+ self.pg_start()
+ capture = self.pg1.get_capture(len(pkts))
+ self.verify_capture_out(capture, nat_ip=self.nat_addr,
+ dst_ip=self.pg1.remote_ip4)
+
+ # out2in
+ pkts = self.create_stream_out(self.pg1, dst_ip=self.nat_addr)
+ self.pg1.add_stream(pkts)
+ self.pg_enable_capture(self.pg_interfaces)
+ self.pg_start()
+ capture = self.pg0.get_capture(len(pkts))
+ ip = IPv6(src=''.join(['64:ff9b::', self.pg1.remote_ip4]))
+ self.verify_capture_in_ip6(capture, ip[IPv6].src, self.pg0.remote_ip6)
+
+ # in2out
+ pkts = self.create_stream_in_ip6(self.pg0, self.pg1)
+ self.pg0.add_stream(pkts)
+ self.pg_enable_capture(self.pg_interfaces)
+ self.pg_start()
+ capture = self.pg1.get_capture(len(pkts))
+ self.verify_capture_out(capture, nat_ip=self.nat_addr,
+ dst_ip=self.pg1.remote_ip4)
+
+ # out2in
+ pkts = self.create_stream_out(self.pg1, dst_ip=self.nat_addr)
+ self.pg1.add_stream(pkts)
+ self.pg_enable_capture(self.pg_interfaces)
+ self.pg_start()
+ capture = self.pg0.get_capture(len(pkts))
+ self.verify_capture_in_ip6(capture, ip[IPv6].src, self.pg0.remote_ip6)
+
+ ses_num_end = self.nat64_get_ses_num()
+
+ self.assertEqual(ses_num_end - ses_num_start, 3)
+
+ # tenant with specific VRF
+ self.vapi.nat64_add_del_pool_addr_range(self.vrf1_nat_addr_n,
+ self.vrf1_nat_addr_n,
+ vrf_id=self.vrf1_id)
+ self.vapi.nat64_add_del_interface(self.pg2.sw_if_index)
+
+ pkts = self.create_stream_in_ip6(self.pg2, self.pg1)
+ self.pg2.add_stream(pkts)
+ self.pg_enable_capture(self.pg_interfaces)
+ self.pg_start()
+ capture = self.pg1.get_capture(len(pkts))
+ self.verify_capture_out(capture, nat_ip=self.vrf1_nat_addr,
+ dst_ip=self.pg1.remote_ip4)
+
+ pkts = self.create_stream_out(self.pg1, dst_ip=self.vrf1_nat_addr)
+ self.pg1.add_stream(pkts)
+ self.pg_enable_capture(self.pg_interfaces)
+ self.pg_start()
+ capture = self.pg2.get_capture(len(pkts))
+ self.verify_capture_in_ip6(capture, ip[IPv6].src, self.pg2.remote_ip6)
+
+ def test_static(self):
+ """ NAT64 static translation test """
+ self.tcp_port_in = 60303
+ self.udp_port_in = 60304
+ self.icmp_id_in = 60305
+ self.tcp_port_out = 60303
+ self.udp_port_out = 60304
+ self.icmp_id_out = 60305
+
+ ses_num_start = self.nat64_get_ses_num()
+
+ self.vapi.nat64_add_del_pool_addr_range(self.nat_addr_n,
+ self.nat_addr_n)
+ self.vapi.nat64_add_del_interface(self.pg0.sw_if_index)
+ self.vapi.nat64_add_del_interface(self.pg1.sw_if_index, is_inside=0)
+
+ self.vapi.nat64_add_del_static_bib(self.pg0.remote_ip6n,
+ self.nat_addr_n,
+ self.tcp_port_in,
+ self.tcp_port_out,
+ IP_PROTOS.tcp)
+ self.vapi.nat64_add_del_static_bib(self.pg0.remote_ip6n,
+ self.nat_addr_n,
+ self.udp_port_in,
+ self.udp_port_out,
+ IP_PROTOS.udp)
+ self.vapi.nat64_add_del_static_bib(self.pg0.remote_ip6n,
+ self.nat_addr_n,
+ self.icmp_id_in,
+ self.icmp_id_out,
+ IP_PROTOS.icmp)
+
+ # in2out
+ pkts = self.create_stream_in_ip6(self.pg0, self.pg1)
+ self.pg0.add_stream(pkts)
+ self.pg_enable_capture(self.pg_interfaces)
+ self.pg_start()
+ capture = self.pg1.get_capture(len(pkts))
+ self.verify_capture_out(capture, nat_ip=self.nat_addr,
+ dst_ip=self.pg1.remote_ip4, same_port=True)
+
+ # out2in
+ pkts = self.create_stream_out(self.pg1, dst_ip=self.nat_addr)
+ self.pg1.add_stream(pkts)
+ self.pg_enable_capture(self.pg_interfaces)
+ self.pg_start()
+ capture = self.pg0.get_capture(len(pkts))
+ ip = IPv6(src=''.join(['64:ff9b::', self.pg1.remote_ip4]))
+ self.verify_capture_in_ip6(capture, ip[IPv6].src, self.pg0.remote_ip6)
+
+ ses_num_end = self.nat64_get_ses_num()
+
+ self.assertEqual(ses_num_end - ses_num_start, 3)
+
+ @unittest.skipUnless(running_extended_tests(), "part of extended tests")
+ def test_session_timeout(self):
+ """ NAT64 session timeout """
+ self.icmp_id_in = 1234
+ self.vapi.nat64_add_del_pool_addr_range(self.nat_addr_n,
+ self.nat_addr_n)
+ self.vapi.nat64_add_del_interface(self.pg0.sw_if_index)
+ self.vapi.nat64_add_del_interface(self.pg1.sw_if_index, is_inside=0)
+ self.vapi.nat64_set_timeouts(icmp=5)
+
+ pkts = self.create_stream_in_ip6(self.pg0, self.pg1)
+ self.pg0.add_stream(pkts)
+ self.pg_enable_capture(self.pg_interfaces)
+ self.pg_start()
+ capture = self.pg1.get_capture(len(pkts))
+
+ ses_num_before_timeout = self.nat64_get_ses_num()
+
+ sleep(15)
+
+ # ICMP session after timeout
+ ses_num_after_timeout = self.nat64_get_ses_num()
+ self.assertNotEqual(ses_num_before_timeout, ses_num_after_timeout)
+
+ def test_icmp_error(self):
+ """ NAT64 ICMP Error message translation """
+ self.tcp_port_in = 6303
+ self.udp_port_in = 6304
+ self.icmp_id_in = 6305
+
+ ses_num_start = self.nat64_get_ses_num()
+
+ self.vapi.nat64_add_del_pool_addr_range(self.nat_addr_n,
+ self.nat_addr_n)
+ self.vapi.nat64_add_del_interface(self.pg0.sw_if_index)
+ self.vapi.nat64_add_del_interface(self.pg1.sw_if_index, is_inside=0)
+
+ # send some packets to create sessions
+ pkts = self.create_stream_in_ip6(self.pg0, self.pg1)
+ self.pg0.add_stream(pkts)
+ self.pg_enable_capture(self.pg_interfaces)
+ self.pg_start()
+ capture_ip4 = self.pg1.get_capture(len(pkts))
+ self.verify_capture_out(capture_ip4,
+ nat_ip=self.nat_addr,
+ dst_ip=self.pg1.remote_ip4)
+
+ pkts = self.create_stream_out(self.pg1, dst_ip=self.nat_addr)
+ self.pg1.add_stream(pkts)
+ self.pg_enable_capture(self.pg_interfaces)
+ self.pg_start()
+ capture_ip6 = self.pg0.get_capture(len(pkts))
+ ip = IPv6(src=''.join(['64:ff9b::', self.pg1.remote_ip4]))
+ self.verify_capture_in_ip6(capture_ip6, ip[IPv6].src,
+ self.pg0.remote_ip6)
+
+ # in2out
+ pkts = [Ether(dst=self.pg0.local_mac, src=self.pg0.remote_mac) /
+ IPv6(src=self.pg0.remote_ip6, dst=ip[IPv6].src) /
+ ICMPv6DestUnreach(code=1) /
+ packet[IPv6] for packet in capture_ip6]
+ self.pg0.add_stream(pkts)
+ self.pg_enable_capture(self.pg_interfaces)
+ self.pg_start()
+ capture = self.pg1.get_capture(len(pkts))
+ for packet in capture:
+ try:
+ self.assertEqual(packet[IP].src, self.nat_addr)
+ self.assertEqual(packet[IP].dst, self.pg1.remote_ip4)
+ self.assertEqual(packet[ICMP].type, 3)
+ self.assertEqual(packet[ICMP].code, 13)
+ inner = packet[IPerror]
+ self.assertEqual(inner.src, self.pg1.remote_ip4)
+ self.assertEqual(inner.dst, self.nat_addr)
+ self.check_icmp_checksum(packet)
+ if inner.haslayer(TCPerror):
+ self.assertEqual(inner[TCPerror].dport, self.tcp_port_out)
+ elif inner.haslayer(UDPerror):
+ self.assertEqual(inner[UDPerror].dport, self.udp_port_out)
+ else:
+ self.assertEqual(inner[ICMPerror].id, self.icmp_id_out)
+ except:
+ self.logger.error(ppp("Unexpected or invalid packet:", packet))
+ raise
+
+ # out2in
+ pkts = [Ether(dst=self.pg1.local_mac, src=self.pg1.remote_mac) /
+ IP(src=self.pg1.remote_ip4, dst=self.nat_addr) /
+ ICMP(type=3, code=13) /
+ packet[IP] for packet in capture_ip4]
+ self.pg1.add_stream(pkts)
+ self.pg_enable_capture(self.pg_interfaces)
+ self.pg_start()
+ capture = self.pg0.get_capture(len(pkts))
+ for packet in capture:
+ try:
+ self.assertEqual(packet[IPv6].src, ip.src)
+ self.assertEqual(packet[IPv6].dst, self.pg0.remote_ip6)
+ icmp = packet[ICMPv6DestUnreach]
+ self.assertEqual(icmp.code, 1)
+ inner = icmp[IPerror6]
+ self.assertEqual(inner.src, self.pg0.remote_ip6)
+ self.assertEqual(inner.dst, ip.src)
+ self.check_icmpv6_checksum(packet)
+ if inner.haslayer(TCPerror):
+ self.assertEqual(inner[TCPerror].sport, self.tcp_port_in)
+ elif inner.haslayer(UDPerror):
+ self.assertEqual(inner[UDPerror].sport, self.udp_port_in)
+ else:
+ self.assertEqual(inner[ICMPv6EchoRequest].id,
+ self.icmp_id_in)
+ except:
+ self.logger.error(ppp("Unexpected or invalid packet:", packet))
+ raise
+
+ def test_hairpinning(self):
+ """ NAT64 hairpinning """
+
+ client = self.pg0.remote_hosts[0]
+ server = self.pg0.remote_hosts[1]
+ server_tcp_in_port = 22
+ server_tcp_out_port = 4022
+ server_udp_in_port = 23
+ server_udp_out_port = 4023
+ client_tcp_in_port = 1234
+ client_udp_in_port = 1235
+ client_tcp_out_port = 0
+ client_udp_out_port = 0
+ ip = IPv6(src=''.join(['64:ff9b::', self.nat_addr]))
+ nat_addr_ip6 = ip.src
+
+ self.vapi.nat64_add_del_pool_addr_range(self.nat_addr_n,
+ self.nat_addr_n)
+ self.vapi.nat64_add_del_interface(self.pg0.sw_if_index)
+ self.vapi.nat64_add_del_interface(self.pg1.sw_if_index, is_inside=0)
+
+ self.vapi.nat64_add_del_static_bib(server.ip6n,
+ self.nat_addr_n,
+ server_tcp_in_port,
+ server_tcp_out_port,
+ IP_PROTOS.tcp)
+ self.vapi.nat64_add_del_static_bib(server.ip6n,
+ self.nat_addr_n,
+ server_udp_in_port,
+ server_udp_out_port,
+ IP_PROTOS.udp)
+
+ # client to server
+ pkts = []
+ p = (Ether(dst=self.pg0.local_mac, src=self.pg0.remote_mac) /
+ IPv6(src=client.ip6, dst=nat_addr_ip6) /
+ TCP(sport=client_tcp_in_port, dport=server_tcp_out_port))
+ pkts.append(p)
+ p = (Ether(dst=self.pg0.local_mac, src=self.pg0.remote_mac) /
+ IPv6(src=client.ip6, dst=nat_addr_ip6) /
+ UDP(sport=client_udp_in_port, dport=server_udp_out_port))
+ pkts.append(p)
+ self.pg0.add_stream(pkts)
+ self.pg_enable_capture(self.pg_interfaces)
+ self.pg_start()
+ capture = self.pg0.get_capture(len(pkts))
+ for packet in capture:
+ try:
+ self.assertEqual(packet[IPv6].src, nat_addr_ip6)
+ self.assertEqual(packet[IPv6].dst, server.ip6)
+ if packet.haslayer(TCP):
+ self.assertNotEqual(packet[TCP].sport, client_tcp_in_port)
+ self.assertEqual(packet[TCP].dport, server_tcp_in_port)
+ self.check_tcp_checksum(packet)
+ client_tcp_out_port = packet[TCP].sport
+ else:
+ self.assertNotEqual(packet[UDP].sport, client_udp_in_port)
+ self.assertEqual(packet[UDP].dport, server_udp_in_port)
+ self.check_udp_checksum(packet)
+ client_udp_out_port = packet[UDP].sport
+ except:
+ self.logger.error(ppp("Unexpected or invalid packet:", packet))
+ raise
+
+ # server to client
+ pkts = []
+ p = (Ether(dst=self.pg0.local_mac, src=self.pg0.remote_mac) /
+ IPv6(src=server.ip6, dst=nat_addr_ip6) /
+ TCP(sport=server_tcp_in_port, dport=client_tcp_out_port))
+ pkts.append(p)
+ p = (Ether(dst=self.pg0.local_mac, src=self.pg0.remote_mac) /
+ IPv6(src=server.ip6, dst=nat_addr_ip6) /
+ UDP(sport=server_udp_in_port, dport=client_udp_out_port))
+ pkts.append(p)
+ self.pg0.add_stream(pkts)
+ self.pg_enable_capture(self.pg_interfaces)
+ self.pg_start()
+ capture = self.pg0.get_capture(len(pkts))
+ for packet in capture:
+ try:
+ self.assertEqual(packet[IPv6].src, nat_addr_ip6)
+ self.assertEqual(packet[IPv6].dst, client.ip6)
+ if packet.haslayer(TCP):
+ self.assertEqual(packet[TCP].sport, server_tcp_out_port)
+ self.assertEqual(packet[TCP].dport, client_tcp_in_port)
+ self.check_tcp_checksum(packet)
+ else:
+ self.assertEqual(packet[UDP].sport, server_udp_out_port)
+ self.assertEqual(packet[UDP].dport, client_udp_in_port)
+ self.check_udp_checksum(packet)
+ except:
+ self.logger.error(ppp("Unexpected or invalid packet:", packet))
+ raise
+
+ # ICMP error
+ pkts = []
+ pkts = [Ether(dst=self.pg0.local_mac, src=self.pg0.remote_mac) /
+ IPv6(src=client.ip6, dst=nat_addr_ip6) /
+ ICMPv6DestUnreach(code=1) /
+ packet[IPv6] for packet in capture]
+ self.pg0.add_stream(pkts)
+ self.pg_enable_capture(self.pg_interfaces)
+ self.pg_start()
+ capture = self.pg0.get_capture(len(pkts))
+ for packet in capture:
+ try:
+ self.assertEqual(packet[IPv6].src, nat_addr_ip6)
+ self.assertEqual(packet[IPv6].dst, server.ip6)
+ icmp = packet[ICMPv6DestUnreach]
+ self.assertEqual(icmp.code, 1)
+ inner = icmp[IPerror6]
+ self.assertEqual(inner.src, server.ip6)
+ self.assertEqual(inner.dst, nat_addr_ip6)
+ self.check_icmpv6_checksum(packet)
+ if inner.haslayer(TCPerror):
+ self.assertEqual(inner[TCPerror].sport, server_tcp_in_port)
+ self.assertEqual(inner[TCPerror].dport,
+ client_tcp_out_port)
+ else:
+ self.assertEqual(inner[UDPerror].sport, server_udp_in_port)
+ self.assertEqual(inner[UDPerror].dport,
+ client_udp_out_port)
+ except:
+ self.logger.error(ppp("Unexpected or invalid packet:", packet))
+ raise
+
+ def test_prefix(self):
+ """ NAT64 Network-Specific Prefix """
+
+ self.vapi.nat64_add_del_pool_addr_range(self.nat_addr_n,
+ self.nat_addr_n)
+ self.vapi.nat64_add_del_interface(self.pg0.sw_if_index)
+ self.vapi.nat64_add_del_interface(self.pg1.sw_if_index, is_inside=0)
+ self.vapi.nat64_add_del_pool_addr_range(self.vrf1_nat_addr_n,
+ self.vrf1_nat_addr_n,
+ vrf_id=self.vrf1_id)
+ self.vapi.nat64_add_del_interface(self.pg2.sw_if_index)
+
+ # Add global prefix
+ global_pref64 = "2001:db8::"
+ global_pref64_n = socket.inet_pton(socket.AF_INET6, global_pref64)
+ global_pref64_len = 32
+ self.vapi.nat64_add_del_prefix(global_pref64_n, global_pref64_len)
+
+ prefix = self.vapi.nat64_prefix_dump()
+ self.assertEqual(len(prefix), 1)
+ self.assertEqual(prefix[0].prefix, global_pref64_n)
+ self.assertEqual(prefix[0].prefix_len, global_pref64_len)
+ self.assertEqual(prefix[0].vrf_id, 0)
+
+ # Add tenant specific prefix
+ vrf1_pref64 = "2001:db8:122:300::"
+ vrf1_pref64_n = socket.inet_pton(socket.AF_INET6, vrf1_pref64)
+ vrf1_pref64_len = 56
+ self.vapi.nat64_add_del_prefix(vrf1_pref64_n,
+ vrf1_pref64_len,
+ vrf_id=self.vrf1_id)
+ prefix = self.vapi.nat64_prefix_dump()
+ self.assertEqual(len(prefix), 2)
+
+ # Global prefix
+ pkts = self.create_stream_in_ip6(self.pg0,
+ self.pg1,
+ pref=global_pref64,
+ plen=global_pref64_len)
+ self.pg0.add_stream(pkts)
+ self.pg_enable_capture(self.pg_interfaces)
+ self.pg_start()
+ capture = self.pg1.get_capture(len(pkts))
+ self.verify_capture_out(capture, nat_ip=self.nat_addr,
+ dst_ip=self.pg1.remote_ip4)
+
+ pkts = self.create_stream_out(self.pg1, dst_ip=self.nat_addr)
+ self.pg1.add_stream(pkts)
+ self.pg_enable_capture(self.pg_interfaces)
+ self.pg_start()
+ capture = self.pg0.get_capture(len(pkts))
+ dst_ip = self.compose_ip6(self.pg1.remote_ip4,
+ global_pref64,
+ global_pref64_len)
+ self.verify_capture_in_ip6(capture, dst_ip, self.pg0.remote_ip6)
+
+ # Tenant specific prefix
+ pkts = self.create_stream_in_ip6(self.pg2,
+ self.pg1,
+ pref=vrf1_pref64,
+ plen=vrf1_pref64_len)
+ self.pg2.add_stream(pkts)
+ self.pg_enable_capture(self.pg_interfaces)
+ self.pg_start()
+ capture = self.pg1.get_capture(len(pkts))
+ self.verify_capture_out(capture, nat_ip=self.vrf1_nat_addr,
+ dst_ip=self.pg1.remote_ip4)
+
+ pkts = self.create_stream_out(self.pg1, dst_ip=self.vrf1_nat_addr)
+ self.pg1.add_stream(pkts)
+ self.pg_enable_capture(self.pg_interfaces)
+ self.pg_start()
+ capture = self.pg2.get_capture(len(pkts))
+ dst_ip = self.compose_ip6(self.pg1.remote_ip4,
+ vrf1_pref64,
+ vrf1_pref64_len)
+ self.verify_capture_in_ip6(capture, dst_ip, self.pg2.remote_ip6)
+
+ def test_unknown_proto(self):
+ """ NAT64 translate packet with unknown protocol """
+
+ self.vapi.nat64_add_del_pool_addr_range(self.nat_addr_n,
+ self.nat_addr_n)
+ self.vapi.nat64_add_del_interface(self.pg0.sw_if_index)
+ self.vapi.nat64_add_del_interface(self.pg1.sw_if_index, is_inside=0)
+ remote_ip6 = self.compose_ip6(self.pg1.remote_ip4, '64:ff9b::', 96)
+
+ # in2out
+ p = (Ether(dst=self.pg0.local_mac, src=self.pg0.remote_mac) /
+ IPv6(src=self.pg0.remote_ip6, dst=remote_ip6) /
+ TCP(sport=self.tcp_port_in, dport=20))
+ self.pg0.add_stream(p)
+ self.pg_enable_capture(self.pg_interfaces)
+ self.pg_start()
+ p = self.pg1.get_capture(1)
+
+ p = (Ether(dst=self.pg0.local_mac, src=self.pg0.remote_mac) /
+ IPv6(src=self.pg0.remote_ip6, dst=remote_ip6, nh=47) /
+ GRE() /
+ IP(src=self.pg2.local_ip4, dst=self.pg2.remote_ip4) /
+ TCP(sport=1234, dport=1234))
+ self.pg0.add_stream(p)
+ self.pg_enable_capture(self.pg_interfaces)
+ self.pg_start()
+ p = self.pg1.get_capture(1)
+ packet = p[0]
+ try:
+ self.assertEqual(packet[IP].src, self.nat_addr)
+ self.assertEqual(packet[IP].dst, self.pg1.remote_ip4)
+ self.assertTrue(packet.haslayer(GRE))
+ self.check_ip_checksum(packet)
+ except:
+ self.logger.error(ppp("Unexpected or invalid packet:", packet))
+ raise
+
+ # out2in
+ p = (Ether(dst=self.pg1.local_mac, src=self.pg1.remote_mac) /
+ IP(src=self.pg1.remote_ip4, dst=self.nat_addr) /
+ GRE() /
+ IP(src=self.pg2.remote_ip4, dst=self.pg2.local_ip4) /
+ TCP(sport=1234, dport=1234))
+ self.pg1.add_stream(p)
+ self.pg_enable_capture(self.pg_interfaces)
+ self.pg_start()
+ p = self.pg0.get_capture(1)
+ packet = p[0]
+ try:
+ self.assertEqual(packet[IPv6].src, remote_ip6)
+ self.assertEqual(packet[IPv6].dst, self.pg0.remote_ip6)
+ self.assertEqual(packet[IPv6].nh, 47)
+ except:
+ self.logger.error(ppp("Unexpected or invalid packet:", packet))
+ raise
+
+ def test_hairpinning_unknown_proto(self):
+ """ NAT64 translate packet with unknown protocol - hairpinning """
+
+ client = self.pg0.remote_hosts[0]
+ server = self.pg0.remote_hosts[1]
+ server_tcp_in_port = 22
+ server_tcp_out_port = 4022
+ client_tcp_in_port = 1234
+ client_tcp_out_port = 1235
+ server_nat_ip = "10.0.0.100"
+ client_nat_ip = "10.0.0.110"
+ server_nat_ip_n = socket.inet_pton(socket.AF_INET, server_nat_ip)
+ client_nat_ip_n = socket.inet_pton(socket.AF_INET, client_nat_ip)
+ server_nat_ip6 = self.compose_ip6(server_nat_ip, '64:ff9b::', 96)
+ client_nat_ip6 = self.compose_ip6(client_nat_ip, '64:ff9b::', 96)
+
+ self.vapi.nat64_add_del_pool_addr_range(server_nat_ip_n,
+ client_nat_ip_n)
+ self.vapi.nat64_add_del_interface(self.pg0.sw_if_index)
+ self.vapi.nat64_add_del_interface(self.pg1.sw_if_index, is_inside=0)
+
+ self.vapi.nat64_add_del_static_bib(server.ip6n,
+ server_nat_ip_n,
+ server_tcp_in_port,
+ server_tcp_out_port,
+ IP_PROTOS.tcp)
+
+ self.vapi.nat64_add_del_static_bib(server.ip6n,
+ server_nat_ip_n,
+ 0,
+ 0,
+ IP_PROTOS.gre)
+
+ self.vapi.nat64_add_del_static_bib(client.ip6n,
+ client_nat_ip_n,
+ client_tcp_in_port,
+ client_tcp_out_port,
+ IP_PROTOS.tcp)
+
+ # client to server
+ p = (Ether(dst=self.pg0.local_mac, src=self.pg0.remote_mac) /
+ IPv6(src=client.ip6, dst=server_nat_ip6) /
+ TCP(sport=client_tcp_in_port, dport=server_tcp_out_port))
+ self.pg0.add_stream(p)
+ self.pg_enable_capture(self.pg_interfaces)
+ self.pg_start()
+ p = self.pg0.get_capture(1)
+
+ p = (Ether(dst=self.pg0.local_mac, src=self.pg0.remote_mac) /
+ IPv6(src=client.ip6, dst=server_nat_ip6, nh=IP_PROTOS.gre) /
+ GRE() /
+ IP(src=self.pg2.local_ip4, dst=self.pg2.remote_ip4) /
+ TCP(sport=1234, dport=1234))
+ self.pg0.add_stream(p)
+ self.pg_enable_capture(self.pg_interfaces)
+ self.pg_start()
+ p = self.pg0.get_capture(1)
+ packet = p[0]
+ try:
+ self.assertEqual(packet[IPv6].src, client_nat_ip6)
+ self.assertEqual(packet[IPv6].dst, server.ip6)
+ self.assertEqual(packet[IPv6].nh, IP_PROTOS.gre)
+ except:
+ self.logger.error(ppp("Unexpected or invalid packet:", packet))
+ raise
+
+ # server to client
+ p = (Ether(dst=self.pg0.local_mac, src=self.pg0.remote_mac) /
+ IPv6(src=server.ip6, dst=client_nat_ip6, nh=IP_PROTOS.gre) /
+ GRE() /
+ IP(src=self.pg2.remote_ip4, dst=self.pg2.local_ip4) /
+ TCP(sport=1234, dport=1234))
+ self.pg0.add_stream(p)
+ self.pg_enable_capture(self.pg_interfaces)
+ self.pg_start()
+ p = self.pg0.get_capture(1)
+ packet = p[0]
+ try:
+ self.assertEqual(packet[IPv6].src, server_nat_ip6)
+ self.assertEqual(packet[IPv6].dst, client.ip6)
+ self.assertEqual(packet[IPv6].nh, IP_PROTOS.gre)
+ except:
+ self.logger.error(ppp("Unexpected or invalid packet:", packet))
+ raise
+
+ def nat64_get_ses_num(self):
+ """
+ Return number of active NAT64 sessions.
+ """
+ st = self.vapi.nat64_st_dump()
+ return len(st)
+
+ def clear_nat64(self):
+ """
+ Clear NAT64 configuration.
+ """
+ self.vapi.nat64_set_timeouts()
+
+ interfaces = self.vapi.nat64_interface_dump()
+ for intf in interfaces:
+ self.vapi.nat64_add_del_interface(intf.sw_if_index,
+ intf.is_inside,
+ is_add=0)
+
+ bib = self.vapi.nat64_bib_dump(IP_PROTOS.tcp)
+ for bibe in bib:
+ if bibe.is_static:
+ self.vapi.nat64_add_del_static_bib(bibe.i_addr,
+ bibe.o_addr,
+ bibe.i_port,
+ bibe.o_port,
+ bibe.proto,
+ bibe.vrf_id,
+ is_add=0)
+
+ bib = self.vapi.nat64_bib_dump(IP_PROTOS.udp)
+ for bibe in bib:
+ if bibe.is_static:
+ self.vapi.nat64_add_del_static_bib(bibe.i_addr,
+ bibe.o_addr,
+ bibe.i_port,
+ bibe.o_port,
+ bibe.proto,
+ bibe.vrf_id,
+ is_add=0)
+
+ bib = self.vapi.nat64_bib_dump(IP_PROTOS.icmp)
+ for bibe in bib:
+ if bibe.is_static:
+ self.vapi.nat64_add_del_static_bib(bibe.i_addr,
+ bibe.o_addr,
+ bibe.i_port,
+ bibe.o_port,
+ bibe.proto,
+ bibe.vrf_id,
+ is_add=0)
+
+ adresses = self.vapi.nat64_pool_addr_dump()
+ for addr in adresses:
+ self.vapi.nat64_add_del_pool_addr_range(addr.address,
+ addr.address,
+ vrf_id=addr.vrf_id,
+ is_add=0)
+
+ prefixes = self.vapi.nat64_prefix_dump()
+ for prefix in prefixes:
+ self.vapi.nat64_add_del_prefix(prefix.prefix,
+ prefix.prefix_len,
+ vrf_id=prefix.vrf_id,
+ is_add=0)
+
+ def tearDown(self):
+ super(TestNAT64, self).tearDown()
+ if not self.vpp_dead:
+ self.logger.info(self.vapi.cli("show nat64 pool"))
+ self.logger.info(self.vapi.cli("show nat64 interfaces"))
+ self.logger.info(self.vapi.cli("show nat64 prefix"))
+ self.logger.info(self.vapi.cli("show nat64 bib all"))
+ self.logger.info(self.vapi.cli("show nat64 session table all"))
+ self.clear_nat64()
+
+if __name__ == '__main__':
+ unittest.main(testRunner=VppTestRunner)
diff --git a/test/test_neighbor.py b/test/test_neighbor.py
new file mode 100644
index 00000000..68dde2fb
--- /dev/null
+++ b/test/test_neighbor.py
@@ -0,0 +1,1147 @@
+#!/usr/bin/env python
+
+import unittest
+from socket import AF_INET, AF_INET6, inet_pton
+
+from framework import VppTestCase, VppTestRunner
+from vpp_neighbor import VppNeighbor, find_nbr
+from vpp_ip_route import VppIpRoute, VppRoutePath, find_route, \
+ VppIpTable
+
+from scapy.packet import Raw
+from scapy.layers.l2 import Ether, ARP, Dot1Q
+from scapy.layers.inet import IP, UDP
+from scapy.contrib.mpls import MPLS
+
+# not exported by scapy, so redefined here
+arp_opts = {"who-has": 1, "is-at": 2}
+
+
+class ARPTestCase(VppTestCase):
+ """ ARP Test Case """
+
+ def setUp(self):
+ super(ARPTestCase, self).setUp()
+
+ # create 3 pg interfaces
+ self.create_pg_interfaces(range(4))
+
+ # pg0 configured with ip4 and 6 addresses used for input
+ # pg1 configured with ip4 and 6 addresses used for output
+ # pg2 is unnumbered to pg0
+ for i in self.pg_interfaces:
+ i.admin_up()
+
+ self.pg0.config_ip4()
+ self.pg0.config_ip6()
+ self.pg0.resolve_arp()
+
+ self.pg1.config_ip4()
+ self.pg1.config_ip6()
+
+ # pg3 in a different VRF
+ self.tbl = VppIpTable(self, 1)
+ self.tbl.add_vpp_config()
+
+ self.pg3.set_table_ip4(1)
+ self.pg3.config_ip4()
+
+ def tearDown(self):
+ self.pg0.unconfig_ip4()
+ self.pg0.unconfig_ip6()
+
+ self.pg1.unconfig_ip4()
+ self.pg1.unconfig_ip6()
+
+ self.pg3.unconfig_ip4()
+ self.pg3.set_table_ip4(0)
+
+ for i in self.pg_interfaces:
+ i.admin_down()
+
+ super(ARPTestCase, self).tearDown()
+
+ def verify_arp_req(self, rx, smac, sip, dip):
+ ether = rx[Ether]
+ self.assertEqual(ether.dst, "ff:ff:ff:ff:ff:ff")
+ self.assertEqual(ether.src, smac)
+
+ arp = rx[ARP]
+ self.assertEqual(arp.hwtype, 1)
+ self.assertEqual(arp.ptype, 0x800)
+ self.assertEqual(arp.hwlen, 6)
+ self.assertEqual(arp.plen, 4)
+ self.assertEqual(arp.op, arp_opts["who-has"])
+ self.assertEqual(arp.hwsrc, smac)
+ self.assertEqual(arp.hwdst, "00:00:00:00:00:00")
+ self.assertEqual(arp.psrc, sip)
+ self.assertEqual(arp.pdst, dip)
+
+ def verify_arp_resp(self, rx, smac, dmac, sip, dip):
+ ether = rx[Ether]
+ self.assertEqual(ether.dst, dmac)
+ self.assertEqual(ether.src, smac)
+
+ arp = rx[ARP]
+ self.assertEqual(arp.hwtype, 1)
+ self.assertEqual(arp.ptype, 0x800)
+ self.assertEqual(arp.hwlen, 6)
+ self.assertEqual(arp.plen, 4)
+ self.assertEqual(arp.op, arp_opts["is-at"])
+ self.assertEqual(arp.hwsrc, smac)
+ self.assertEqual(arp.hwdst, dmac)
+ self.assertEqual(arp.psrc, sip)
+ self.assertEqual(arp.pdst, dip)
+
+ def verify_arp_vrrp_resp(self, rx, smac, dmac, sip, dip):
+ ether = rx[Ether]
+ self.assertEqual(ether.dst, dmac)
+ self.assertEqual(ether.src, smac)
+
+ arp = rx[ARP]
+ self.assertEqual(arp.hwtype, 1)
+ self.assertEqual(arp.ptype, 0x800)
+ self.assertEqual(arp.hwlen, 6)
+ self.assertEqual(arp.plen, 4)
+ self.assertEqual(arp.op, arp_opts["is-at"])
+ self.assertNotEqual(arp.hwsrc, smac)
+ self.assertTrue("00:00:5e:00:01" in arp.hwsrc or
+ "00:00:5E:00:01" in arp.hwsrc)
+ self.assertEqual(arp.hwdst, dmac)
+ self.assertEqual(arp.psrc, sip)
+ self.assertEqual(arp.pdst, dip)
+
+ def verify_ip(self, rx, smac, dmac, sip, dip):
+ ether = rx[Ether]
+ self.assertEqual(ether.dst, dmac)
+ self.assertEqual(ether.src, smac)
+
+ ip = rx[IP]
+ self.assertEqual(ip.src, sip)
+ self.assertEqual(ip.dst, dip)
+
+ def verify_ip_o_mpls(self, rx, smac, dmac, label, sip, dip):
+ ether = rx[Ether]
+ self.assertEqual(ether.dst, dmac)
+ self.assertEqual(ether.src, smac)
+
+ mpls = rx[MPLS]
+ self.assertTrue(mpls.label, label)
+
+ ip = rx[IP]
+ self.assertEqual(ip.src, sip)
+ self.assertEqual(ip.dst, dip)
+
+ def send_and_assert_no_replies(self, intf, pkts, remark):
+ intf.add_stream(pkts)
+ self.pg_enable_capture(self.pg_interfaces)
+ self.pg_start()
+ timeout = 1
+ for i in self.pg_interfaces:
+ i.get_capture(0, timeout=timeout)
+ i.assert_nothing_captured(remark=remark)
+ timeout = 0.1
+
+ def test_arp(self):
+ """ ARP """
+
+ #
+ # Generate some hosts on the LAN
+ #
+ self.pg1.generate_remote_hosts(11)
+
+ #
+ # Send IP traffic to one of these unresolved hosts.
+ # expect the generation of an ARP request
+ #
+ p = (Ether(dst=self.pg0.local_mac, src=self.pg0.remote_mac) /
+ IP(src=self.pg0.remote_ip4, dst=self.pg1._remote_hosts[1].ip4) /
+ UDP(sport=1234, dport=1234) /
+ Raw())
+
+ self.pg0.add_stream(p)
+ self.pg_enable_capture(self.pg_interfaces)
+ self.pg_start()
+
+ rx = self.pg1.get_capture(1)
+
+ self.verify_arp_req(rx[0],
+ self.pg1.local_mac,
+ self.pg1.local_ip4,
+ self.pg1._remote_hosts[1].ip4)
+
+ #
+ # And a dynamic ARP entry for host 1
+ #
+ dyn_arp = VppNeighbor(self,
+ self.pg1.sw_if_index,
+ self.pg1.remote_hosts[1].mac,
+ self.pg1.remote_hosts[1].ip4)
+ dyn_arp.add_vpp_config()
+
+ #
+ # now we expect IP traffic forwarded
+ #
+ dyn_p = (Ether(dst=self.pg0.local_mac, src=self.pg0.remote_mac) /
+ IP(src=self.pg0.remote_ip4,
+ dst=self.pg1._remote_hosts[1].ip4) /
+ UDP(sport=1234, dport=1234) /
+ Raw())
+
+ self.pg0.add_stream(dyn_p)
+ self.pg_enable_capture(self.pg_interfaces)
+ self.pg_start()
+
+ rx = self.pg1.get_capture(1)
+
+ self.verify_ip(rx[0],
+ self.pg1.local_mac,
+ self.pg1.remote_hosts[1].mac,
+ self.pg0.remote_ip4,
+ self.pg1._remote_hosts[1].ip4)
+
+ #
+ # And a Static ARP entry for host 2
+ #
+ static_arp = VppNeighbor(self,
+ self.pg1.sw_if_index,
+ self.pg1.remote_hosts[2].mac,
+ self.pg1.remote_hosts[2].ip4,
+ is_static=1)
+ static_arp.add_vpp_config()
+
+ static_p = (Ether(dst=self.pg0.local_mac, src=self.pg0.remote_mac) /
+ IP(src=self.pg0.remote_ip4,
+ dst=self.pg1._remote_hosts[2].ip4) /
+ UDP(sport=1234, dport=1234) /
+ Raw())
+
+ self.pg0.add_stream(static_p)
+ self.pg_enable_capture(self.pg_interfaces)
+ self.pg_start()
+
+ rx = self.pg1.get_capture(1)
+
+ self.verify_ip(rx[0],
+ self.pg1.local_mac,
+ self.pg1.remote_hosts[2].mac,
+ self.pg0.remote_ip4,
+ self.pg1._remote_hosts[2].ip4)
+
+ #
+ # flap the link. dynamic ARPs get flush, statics don't
+ #
+ self.pg1.admin_down()
+ self.pg1.admin_up()
+
+ self.pg0.add_stream(static_p)
+ self.pg_enable_capture(self.pg_interfaces)
+ self.pg_start()
+ rx = self.pg1.get_capture(1)
+
+ self.verify_ip(rx[0],
+ self.pg1.local_mac,
+ self.pg1.remote_hosts[2].mac,
+ self.pg0.remote_ip4,
+ self.pg1._remote_hosts[2].ip4)
+
+ self.pg0.add_stream(dyn_p)
+ self.pg_enable_capture(self.pg_interfaces)
+ self.pg_start()
+
+ rx = self.pg1.get_capture(1)
+ self.verify_arp_req(rx[0],
+ self.pg1.local_mac,
+ self.pg1.local_ip4,
+ self.pg1._remote_hosts[1].ip4)
+
+ #
+ # Send an ARP request from one of the so-far unlearned remote hosts
+ #
+ p = (Ether(dst="ff:ff:ff:ff:ff:ff",
+ src=self.pg1._remote_hosts[3].mac) /
+ ARP(op="who-has",
+ hwsrc=self.pg1._remote_hosts[3].mac,
+ pdst=self.pg1.local_ip4,
+ psrc=self.pg1._remote_hosts[3].ip4))
+
+ self.pg1.add_stream(p)
+ self.pg_enable_capture(self.pg_interfaces)
+ self.pg_start()
+
+ rx = self.pg1.get_capture(1)
+ self.verify_arp_resp(rx[0],
+ self.pg1.local_mac,
+ self.pg1._remote_hosts[3].mac,
+ self.pg1.local_ip4,
+ self.pg1._remote_hosts[3].ip4)
+
+ #
+ # VPP should have learned the mapping for the remote host
+ #
+ self.assertTrue(find_nbr(self,
+ self.pg1.sw_if_index,
+ self.pg1._remote_hosts[3].ip4))
+ #
+ # Fire in an ARP request before the interface becomes IP enabled
+ #
+ self.pg2.generate_remote_hosts(4)
+
+ p = (Ether(dst="ff:ff:ff:ff:ff:ff", src=self.pg2.remote_mac) /
+ ARP(op="who-has",
+ hwsrc=self.pg2.remote_mac,
+ pdst=self.pg1.local_ip4,
+ psrc=self.pg2.remote_hosts[3].ip4))
+ pt = (Ether(dst="ff:ff:ff:ff:ff:ff", src=self.pg2.remote_mac) /
+ Dot1Q(vlan=0) /
+ ARP(op="who-has",
+ hwsrc=self.pg2.remote_mac,
+ pdst=self.pg1.local_ip4,
+ psrc=self.pg2.remote_hosts[3].ip4))
+ self.send_and_assert_no_replies(self.pg2, p,
+ "interface not IP enabled")
+
+ #
+ # Make pg2 un-numbered to pg1
+ #
+ self.pg2.set_unnumbered(self.pg1.sw_if_index)
+
+ #
+ # We should respond to ARP requests for the unnumbered to address
+ # once an attached route to the source is known
+ #
+ self.send_and_assert_no_replies(
+ self.pg2, p,
+ "ARP req for unnumbered address - no source")
+
+ attached_host = VppIpRoute(self, self.pg2.remote_hosts[3].ip4, 32,
+ [VppRoutePath("0.0.0.0",
+ self.pg2.sw_if_index)])
+ attached_host.add_vpp_config()
+
+ self.pg2.add_stream(p)
+ self.pg_enable_capture(self.pg_interfaces)
+ self.pg_start()
+
+ rx = self.pg2.get_capture(1)
+ self.verify_arp_resp(rx[0],
+ self.pg2.local_mac,
+ self.pg2.remote_mac,
+ self.pg1.local_ip4,
+ self.pg2.remote_hosts[3].ip4)
+
+ self.pg2.add_stream(pt)
+ self.pg_enable_capture(self.pg_interfaces)
+ self.pg_start()
+
+ rx = self.pg2.get_capture(1)
+ self.verify_arp_resp(rx[0],
+ self.pg2.local_mac,
+ self.pg2.remote_mac,
+ self.pg1.local_ip4,
+ self.pg2.remote_hosts[3].ip4)
+
+ #
+ # A neighbor entry that has no associated FIB-entry
+ #
+ arp_no_fib = VppNeighbor(self,
+ self.pg1.sw_if_index,
+ self.pg1.remote_hosts[4].mac,
+ self.pg1.remote_hosts[4].ip4,
+ is_no_fib_entry=1)
+ arp_no_fib.add_vpp_config()
+
+ #
+ # check we have the neighbor, but no route
+ #
+ self.assertTrue(find_nbr(self,
+ self.pg1.sw_if_index,
+ self.pg1._remote_hosts[4].ip4))
+ self.assertFalse(find_route(self,
+ self.pg1._remote_hosts[4].ip4,
+ 32))
+ #
+ # pg2 is unnumbered to pg1, so we can form adjacencies out of pg2
+ # from within pg1's subnet
+ #
+ arp_unnum = VppNeighbor(self,
+ self.pg2.sw_if_index,
+ self.pg1.remote_hosts[5].mac,
+ self.pg1.remote_hosts[5].ip4)
+ arp_unnum.add_vpp_config()
+
+ p = (Ether(dst=self.pg0.local_mac, src=self.pg0.remote_mac) /
+ IP(src=self.pg0.remote_ip4,
+ dst=self.pg1._remote_hosts[5].ip4) /
+ UDP(sport=1234, dport=1234) /
+ Raw())
+
+ self.pg0.add_stream(p)
+ self.pg_enable_capture(self.pg_interfaces)
+ self.pg_start()
+
+ rx = self.pg2.get_capture(1)
+
+ self.verify_ip(rx[0],
+ self.pg2.local_mac,
+ self.pg1.remote_hosts[5].mac,
+ self.pg0.remote_ip4,
+ self.pg1._remote_hosts[5].ip4)
+
+ #
+ # ARP requests from hosts in pg1's subnet sent on pg2 are replied to
+ # with the unnumbered interface's address as the source
+ #
+ p = (Ether(dst="ff:ff:ff:ff:ff:ff", src=self.pg2.remote_mac) /
+ ARP(op="who-has",
+ hwsrc=self.pg2.remote_mac,
+ pdst=self.pg1.local_ip4,
+ psrc=self.pg1.remote_hosts[6].ip4))
+
+ self.pg2.add_stream(p)
+ self.pg_enable_capture(self.pg_interfaces)
+ self.pg_start()
+
+ rx = self.pg2.get_capture(1)
+ self.verify_arp_resp(rx[0],
+ self.pg2.local_mac,
+ self.pg2.remote_mac,
+ self.pg1.local_ip4,
+ self.pg1.remote_hosts[6].ip4)
+
+ #
+ # An attached host route out of pg2 for an undiscovered hosts generates
+ # an ARP request with the unnumbered address as the source
+ #
+ att_unnum = VppIpRoute(self, self.pg1.remote_hosts[7].ip4, 32,
+ [VppRoutePath("0.0.0.0",
+ self.pg2.sw_if_index)])
+ att_unnum.add_vpp_config()
+
+ p = (Ether(dst=self.pg0.local_mac, src=self.pg0.remote_mac) /
+ IP(src=self.pg0.remote_ip4,
+ dst=self.pg1._remote_hosts[7].ip4) /
+ UDP(sport=1234, dport=1234) /
+ Raw())
+
+ self.pg0.add_stream(p)
+ self.pg_enable_capture(self.pg_interfaces)
+ self.pg_start()
+
+ rx = self.pg2.get_capture(1)
+
+ self.verify_arp_req(rx[0],
+ self.pg2.local_mac,
+ self.pg1.local_ip4,
+ self.pg1._remote_hosts[7].ip4)
+
+ p = (Ether(dst="ff:ff:ff:ff:ff:ff", src=self.pg2.remote_mac) /
+ ARP(op="who-has",
+ hwsrc=self.pg2.remote_mac,
+ pdst=self.pg1.local_ip4,
+ psrc=self.pg1.remote_hosts[7].ip4))
+
+ self.pg2.add_stream(p)
+ self.pg_enable_capture(self.pg_interfaces)
+ self.pg_start()
+
+ rx = self.pg2.get_capture(1)
+ self.verify_arp_resp(rx[0],
+ self.pg2.local_mac,
+ self.pg2.remote_mac,
+ self.pg1.local_ip4,
+ self.pg1.remote_hosts[7].ip4)
+
+ #
+ # An attached host route as yet unresolved out of pg2 for an
+ # undiscovered host, an ARP requests begets a response.
+ #
+ att_unnum1 = VppIpRoute(self, self.pg1.remote_hosts[8].ip4, 32,
+ [VppRoutePath("0.0.0.0",
+ self.pg2.sw_if_index)])
+ att_unnum1.add_vpp_config()
+
+ p = (Ether(dst="ff:ff:ff:ff:ff:ff", src=self.pg2.remote_mac) /
+ ARP(op="who-has",
+ hwsrc=self.pg2.remote_mac,
+ pdst=self.pg1.local_ip4,
+ psrc=self.pg1.remote_hosts[8].ip4))
+
+ self.pg2.add_stream(p)
+ self.pg_enable_capture(self.pg_interfaces)
+ self.pg_start()
+
+ rx = self.pg2.get_capture(1)
+ self.verify_arp_resp(rx[0],
+ self.pg2.local_mac,
+ self.pg2.remote_mac,
+ self.pg1.local_ip4,
+ self.pg1.remote_hosts[8].ip4)
+
+ #
+ # Send an ARP request from one of the so-far unlearned remote hosts
+ # with a VLAN0 tag
+ #
+ p = (Ether(dst="ff:ff:ff:ff:ff:ff",
+ src=self.pg1._remote_hosts[9].mac) /
+ Dot1Q(vlan=0) /
+ ARP(op="who-has",
+ hwsrc=self.pg1._remote_hosts[9].mac,
+ pdst=self.pg1.local_ip4,
+ psrc=self.pg1._remote_hosts[9].ip4))
+
+ self.pg1.add_stream(p)
+ self.pg_enable_capture(self.pg_interfaces)
+ self.pg_start()
+
+ rx = self.pg1.get_capture(1)
+ self.verify_arp_resp(rx[0],
+ self.pg1.local_mac,
+ self.pg1._remote_hosts[9].mac,
+ self.pg1.local_ip4,
+ self.pg1._remote_hosts[9].ip4)
+
+ #
+ # Add a hierachy of routes for a host in the sub-net.
+ # Should still get an ARP resp since the cover is attached
+ #
+ p = (Ether(dst="ff:ff:ff:ff:ff:ff", src=self.pg1.remote_mac) /
+ ARP(op="who-has",
+ hwsrc=self.pg1.remote_mac,
+ pdst=self.pg1.local_ip4,
+ psrc=self.pg1.remote_hosts[10].ip4))
+
+ r1 = VppIpRoute(self, self.pg1.remote_hosts[10].ip4, 30,
+ [VppRoutePath(self.pg1.remote_hosts[10].ip4,
+ self.pg1.sw_if_index)])
+ r1.add_vpp_config()
+
+ self.pg1.add_stream(p)
+ self.pg_enable_capture(self.pg_interfaces)
+ self.pg_start()
+ rx = self.pg1.get_capture(1)
+ self.verify_arp_resp(rx[0],
+ self.pg1.local_mac,
+ self.pg1.remote_mac,
+ self.pg1.local_ip4,
+ self.pg1.remote_hosts[10].ip4)
+
+ r2 = VppIpRoute(self, self.pg1.remote_hosts[10].ip4, 32,
+ [VppRoutePath(self.pg1.remote_hosts[10].ip4,
+ self.pg1.sw_if_index)])
+ r2.add_vpp_config()
+
+ self.pg1.add_stream(p)
+ self.pg_enable_capture(self.pg_interfaces)
+ self.pg_start()
+ rx = self.pg1.get_capture(1)
+ self.verify_arp_resp(rx[0],
+ self.pg1.local_mac,
+ self.pg1.remote_mac,
+ self.pg1.local_ip4,
+ self.pg1.remote_hosts[10].ip4)
+
+ #
+ # add an ARP entry that's not on the sub-net and so whose
+ # adj-fib fails the refinement check. then send an ARP request
+ # from that source
+ #
+ a1 = VppNeighbor(self,
+ self.pg0.sw_if_index,
+ self.pg0.remote_mac,
+ "100.100.100.50")
+ a1.add_vpp_config()
+
+ p = (Ether(dst="ff:ff:ff:ff:ff:ff", src=self.pg0.remote_mac) /
+ ARP(op="who-has",
+ hwsrc=self.pg0.remote_mac,
+ psrc="100.100.100.50",
+ pdst=self.pg0.remote_ip4))
+ self.send_and_assert_no_replies(self.pg0, p,
+ "ARP req for from failed adj-fib")
+
+ #
+ # ERROR Cases
+ # 1 - don't respond to ARP request for address not within the
+ # interface's sub-net
+ # 1b - nor within the unnumbered subnet
+ # 1c - nor within the subnet of a different interface
+ #
+ p = (Ether(dst="ff:ff:ff:ff:ff:ff", src=self.pg0.remote_mac) /
+ ARP(op="who-has",
+ hwsrc=self.pg0.remote_mac,
+ pdst="10.10.10.3",
+ psrc=self.pg0.remote_ip4))
+ self.send_and_assert_no_replies(self.pg0, p,
+ "ARP req for non-local destination")
+ self.assertFalse(find_nbr(self,
+ self.pg0.sw_if_index,
+ "10.10.10.3"))
+
+ p = (Ether(dst="ff:ff:ff:ff:ff:ff", src=self.pg2.remote_mac) /
+ ARP(op="who-has",
+ hwsrc=self.pg2.remote_mac,
+ pdst="10.10.10.3",
+ psrc=self.pg1.remote_hosts[7].ip4))
+ self.send_and_assert_no_replies(
+ self.pg0, p,
+ "ARP req for non-local destination - unnum")
+
+ p = (Ether(dst="ff:ff:ff:ff:ff:ff", src=self.pg0.remote_mac) /
+ ARP(op="who-has",
+ hwsrc=self.pg0.remote_mac,
+ pdst=self.pg1.local_ip4,
+ psrc=self.pg1.remote_ip4))
+ self.send_and_assert_no_replies(self.pg0, p,
+ "ARP req diff sub-net")
+ self.assertFalse(find_nbr(self,
+ self.pg0.sw_if_index,
+ self.pg1.remote_ip4))
+
+ #
+ # 2 - don't respond to ARP request from an address not within the
+ # interface's sub-net
+ # 2b - to a prxied address
+ # 2c - not within a differents interface's sub-net
+ p = (Ether(dst="ff:ff:ff:ff:ff:ff", src=self.pg0.remote_mac) /
+ ARP(op="who-has",
+ hwsrc=self.pg0.remote_mac,
+ psrc="10.10.10.3",
+ pdst=self.pg0.local_ip4))
+ self.send_and_assert_no_replies(self.pg0, p,
+ "ARP req for non-local source")
+ p = (Ether(dst="ff:ff:ff:ff:ff:ff", src=self.pg2.remote_mac) /
+ ARP(op="who-has",
+ hwsrc=self.pg2.remote_mac,
+ psrc="10.10.10.3",
+ pdst=self.pg0.local_ip4))
+ self.send_and_assert_no_replies(
+ self.pg0, p,
+ "ARP req for non-local source - unnum")
+ p = (Ether(dst="ff:ff:ff:ff:ff:ff", src=self.pg0.remote_mac) /
+ ARP(op="who-has",
+ hwsrc=self.pg0.remote_mac,
+ psrc=self.pg1.remote_ip4,
+ pdst=self.pg0.local_ip4))
+ self.send_and_assert_no_replies(self.pg0, p,
+ "ARP req for non-local source 2c")
+
+ #
+ # 3 - don't respond to ARP request from an address that belongs to
+ # the router
+ #
+ p = (Ether(dst="ff:ff:ff:ff:ff:ff", src=self.pg0.remote_mac) /
+ ARP(op="who-has",
+ hwsrc=self.pg0.remote_mac,
+ psrc=self.pg0.local_ip4,
+ pdst=self.pg0.local_ip4))
+ self.send_and_assert_no_replies(self.pg0, p,
+ "ARP req for non-local source")
+
+ #
+ # 4 - don't respond to ARP requests that has mac source different
+ # from ARP request HW source
+ # the router
+ #
+ p = (Ether(dst="ff:ff:ff:ff:ff:ff", src=self.pg0.remote_mac) /
+ ARP(op="who-has",
+ hwsrc="00:00:00:DE:AD:BE",
+ psrc=self.pg0.remote_ip4,
+ pdst=self.pg0.local_ip4))
+ self.send_and_assert_no_replies(self.pg0, p,
+ "ARP req for non-local source")
+
+ #
+ # cleanup
+ #
+ dyn_arp.remove_vpp_config()
+ static_arp.remove_vpp_config()
+ self.pg2.unset_unnumbered(self.pg1.sw_if_index)
+
+ # need this to flush the adj-fibs
+ self.pg2.unset_unnumbered(self.pg1.sw_if_index)
+ self.pg2.admin_down()
+ self.pg1.admin_down()
+
+ def test_proxy_mirror_arp(self):
+ """ Interface Mirror Proxy ARP """
+
+ #
+ # When VPP has an interface whose address is also applied to a TAP
+ # interface on the host, then VPP's TAP interface will be unnumbered
+ # to the 'real' interface and do proxy ARP from the host.
+ # the curious aspect of this setup is that ARP requests from the host
+ # will come from the VPP's own address.
+ #
+ self.pg0.generate_remote_hosts(2)
+
+ arp_req_from_me = (Ether(src=self.pg2.remote_mac,
+ dst="ff:ff:ff:ff:ff:ff") /
+ ARP(op="who-has",
+ hwsrc=self.pg2.remote_mac,
+ pdst=self.pg0.remote_hosts[1].ip4,
+ psrc=self.pg0.local_ip4))
+
+ #
+ # Configure Proxy ARP for the subnet on PG0addresses on pg0
+ #
+ self.vapi.proxy_arp_add_del(self.pg0._local_ip4n_subnet,
+ self.pg0._local_ip4n_bcast)
+
+ # Make pg2 un-numbered to pg0
+ #
+ self.pg2.set_unnumbered(self.pg0.sw_if_index)
+
+ #
+ # Enable pg2 for proxy ARP
+ #
+ self.pg2.set_proxy_arp()
+
+ #
+ # Send the ARP request with an originating address that
+ # is VPP's own address
+ #
+ self.pg2.add_stream(arp_req_from_me)
+ self.pg_enable_capture(self.pg_interfaces)
+ self.pg_start()
+
+ rx = self.pg2.get_capture(1)
+ self.verify_arp_resp(rx[0],
+ self.pg2.local_mac,
+ self.pg2.remote_mac,
+ self.pg0.remote_hosts[1].ip4,
+ self.pg0.local_ip4)
+
+ #
+ # validate we have not learned an ARP entry as a result of this
+ #
+ self.assertFalse(find_nbr(self,
+ self.pg2.sw_if_index,
+ self.pg0.local_ip4))
+
+ #
+ # cleanup
+ #
+ self.pg2.set_proxy_arp(0)
+ self.vapi.proxy_arp_add_del(self.pg0._local_ip4n_subnet,
+ self.pg0._local_ip4n_bcast,
+ is_add=0)
+
+ def test_proxy_arp(self):
+ """ Proxy ARP """
+
+ self.pg1.generate_remote_hosts(2)
+
+ #
+ # Proxy ARP rewquest packets for each interface
+ #
+ arp_req_pg0 = (Ether(src=self.pg0.remote_mac,
+ dst="ff:ff:ff:ff:ff:ff") /
+ ARP(op="who-has",
+ hwsrc=self.pg0.remote_mac,
+ pdst="10.10.10.3",
+ psrc=self.pg0.remote_ip4))
+ arp_req_pg0_tagged = (Ether(src=self.pg0.remote_mac,
+ dst="ff:ff:ff:ff:ff:ff") /
+ Dot1Q(vlan=0) /
+ ARP(op="who-has",
+ hwsrc=self.pg0.remote_mac,
+ pdst="10.10.10.3",
+ psrc=self.pg0.remote_ip4))
+ arp_req_pg1 = (Ether(src=self.pg1.remote_mac,
+ dst="ff:ff:ff:ff:ff:ff") /
+ ARP(op="who-has",
+ hwsrc=self.pg1.remote_mac,
+ pdst="10.10.10.3",
+ psrc=self.pg1.remote_ip4))
+ arp_req_pg2 = (Ether(src=self.pg2.remote_mac,
+ dst="ff:ff:ff:ff:ff:ff") /
+ ARP(op="who-has",
+ hwsrc=self.pg2.remote_mac,
+ pdst="10.10.10.3",
+ psrc=self.pg1.remote_hosts[1].ip4))
+ arp_req_pg3 = (Ether(src=self.pg3.remote_mac,
+ dst="ff:ff:ff:ff:ff:ff") /
+ ARP(op="who-has",
+ hwsrc=self.pg3.remote_mac,
+ pdst="10.10.10.3",
+ psrc=self.pg3.remote_ip4))
+
+ #
+ # Configure Proxy ARP for 10.10.10.0 -> 10.10.10.124
+ #
+ self.vapi.proxy_arp_add_del(inet_pton(AF_INET, "10.10.10.2"),
+ inet_pton(AF_INET, "10.10.10.124"))
+
+ #
+ # No responses are sent when the interfaces are not enabled for proxy
+ # ARP
+ #
+ self.send_and_assert_no_replies(self.pg0, arp_req_pg0,
+ "ARP req from unconfigured interface")
+ self.send_and_assert_no_replies(self.pg2, arp_req_pg2,
+ "ARP req from unconfigured interface")
+
+ #
+ # Make pg2 un-numbered to pg1
+ # still won't reply.
+ #
+ self.pg2.set_unnumbered(self.pg1.sw_if_index)
+
+ self.send_and_assert_no_replies(self.pg2, arp_req_pg2,
+ "ARP req from unnumbered interface")
+
+ #
+ # Enable each interface to reply to proxy ARPs
+ #
+ for i in self.pg_interfaces:
+ i.set_proxy_arp()
+
+ #
+ # Now each of the interfaces should reply to a request to a proxied
+ # address
+ #
+ self.pg0.add_stream(arp_req_pg0)
+ self.pg_enable_capture(self.pg_interfaces)
+ self.pg_start()
+
+ rx = self.pg0.get_capture(1)
+ self.verify_arp_resp(rx[0],
+ self.pg0.local_mac,
+ self.pg0.remote_mac,
+ "10.10.10.3",
+ self.pg0.remote_ip4)
+
+ self.pg0.add_stream(arp_req_pg0_tagged)
+ self.pg_enable_capture(self.pg_interfaces)
+ self.pg_start()
+
+ rx = self.pg0.get_capture(1)
+ self.verify_arp_resp(rx[0],
+ self.pg0.local_mac,
+ self.pg0.remote_mac,
+ "10.10.10.3",
+ self.pg0.remote_ip4)
+
+ self.pg1.add_stream(arp_req_pg1)
+ self.pg_enable_capture(self.pg_interfaces)
+ self.pg_start()
+
+ rx = self.pg1.get_capture(1)
+ self.verify_arp_resp(rx[0],
+ self.pg1.local_mac,
+ self.pg1.remote_mac,
+ "10.10.10.3",
+ self.pg1.remote_ip4)
+
+ self.pg2.add_stream(arp_req_pg2)
+ self.pg_enable_capture(self.pg_interfaces)
+ self.pg_start()
+
+ rx = self.pg2.get_capture(1)
+ self.verify_arp_resp(rx[0],
+ self.pg2.local_mac,
+ self.pg2.remote_mac,
+ "10.10.10.3",
+ self.pg1.remote_hosts[1].ip4)
+
+ #
+ # A request for an address out of the configured range
+ #
+ arp_req_pg1_hi = (Ether(src=self.pg1.remote_mac,
+ dst="ff:ff:ff:ff:ff:ff") /
+ ARP(op="who-has",
+ hwsrc=self.pg1.remote_mac,
+ pdst="10.10.10.125",
+ psrc=self.pg1.remote_ip4))
+ self.send_and_assert_no_replies(self.pg1, arp_req_pg1_hi,
+ "ARP req out of range HI")
+ arp_req_pg1_low = (Ether(src=self.pg1.remote_mac,
+ dst="ff:ff:ff:ff:ff:ff") /
+ ARP(op="who-has",
+ hwsrc=self.pg1.remote_mac,
+ pdst="10.10.10.1",
+ psrc=self.pg1.remote_ip4))
+ self.send_and_assert_no_replies(self.pg1, arp_req_pg1_low,
+ "ARP req out of range Low")
+
+ #
+ # Request for an address in the proxy range but from an interface
+ # in a different VRF
+ #
+ self.send_and_assert_no_replies(self.pg3, arp_req_pg3,
+ "ARP req from different VRF")
+
+ #
+ # Disable Each interface for proxy ARP
+ # - expect none to respond
+ #
+ for i in self.pg_interfaces:
+ i.set_proxy_arp(0)
+
+ self.send_and_assert_no_replies(self.pg0, arp_req_pg0,
+ "ARP req from disable")
+ self.send_and_assert_no_replies(self.pg1, arp_req_pg1,
+ "ARP req from disable")
+ self.send_and_assert_no_replies(self.pg2, arp_req_pg2,
+ "ARP req from disable")
+
+ #
+ # clean up on interface 2
+ #
+ self.pg2.unset_unnumbered(self.pg1.sw_if_index)
+
+ def test_mpls(self):
+ """ MPLS """
+
+ #
+ # Interface 2 does not yet have ip4 config
+ #
+ self.pg2.config_ip4()
+ self.pg2.generate_remote_hosts(2)
+
+ #
+ # Add a reoute with out going label via an ARP unresolved next-hop
+ #
+ ip_10_0_0_1 = VppIpRoute(self, "10.0.0.1", 32,
+ [VppRoutePath(self.pg2.remote_hosts[1].ip4,
+ self.pg2.sw_if_index,
+ labels=[55])])
+ ip_10_0_0_1.add_vpp_config()
+
+ #
+ # packets should generate an ARP request
+ #
+ p = (Ether(src=self.pg0.remote_mac,
+ dst=self.pg0.local_mac) /
+ IP(src=self.pg0.remote_ip4, dst="10.0.0.1") /
+ UDP(sport=1234, dport=1234) /
+ Raw('\xa5' * 100))
+
+ self.pg0.add_stream(p)
+ self.pg_enable_capture(self.pg_interfaces)
+ self.pg_start()
+
+ rx = self.pg2.get_capture(1)
+ self.verify_arp_req(rx[0],
+ self.pg2.local_mac,
+ self.pg2.local_ip4,
+ self.pg2._remote_hosts[1].ip4)
+
+ #
+ # now resolve the neighbours
+ #
+ self.pg2.configure_ipv4_neighbors()
+
+ #
+ # Now packet should be properly MPLS encapped.
+ # This verifies that MPLS link-type adjacencies are completed
+ # when the ARP entry resolves
+ #
+ self.pg0.add_stream(p)
+ self.pg_enable_capture(self.pg_interfaces)
+ self.pg_start()
+
+ rx = self.pg2.get_capture(1)
+ self.verify_ip_o_mpls(rx[0],
+ self.pg2.local_mac,
+ self.pg2.remote_hosts[1].mac,
+ 55,
+ self.pg0.remote_ip4,
+ "10.0.0.1")
+ self.pg2.unconfig_ip4()
+
+ def test_arp_vrrp(self):
+ """ ARP reply with VRRP virtual src hw addr """
+
+ #
+ # IP packet destined for pg1 remote host arrives on pg0 resulting
+ # in an ARP request for the address of the remote host on pg1
+ #
+ p0 = (Ether(dst=self.pg0.local_mac, src=self.pg0.remote_mac) /
+ IP(src=self.pg0.remote_ip4, dst=self.pg1.remote_ip4) /
+ UDP(sport=1234, dport=1234) /
+ Raw())
+
+ self.pg0.add_stream(p0)
+ self.pg_enable_capture(self.pg_interfaces)
+ self.pg_start()
+
+ rx1 = self.pg1.get_capture(1)
+
+ self.verify_arp_req(rx1[0],
+ self.pg1.local_mac,
+ self.pg1.local_ip4,
+ self.pg1.remote_ip4)
+
+ #
+ # ARP reply for address of pg1 remote host arrives on pg1 with
+ # the hw src addr set to a value in the VRRP IPv4 range of
+ # MAC addresses
+ #
+ p1 = (Ether(dst=self.pg1.local_mac, src=self.pg1.remote_mac) /
+ ARP(op="is-at", hwdst=self.pg1.local_mac,
+ hwsrc="00:00:5e:00:01:09", pdst=self.pg1.local_ip4,
+ psrc=self.pg1.remote_ip4))
+
+ self.pg1.add_stream(p1)
+ self.pg_enable_capture(self.pg_interfaces)
+ self.pg_start()
+
+ #
+ # IP packet destined for pg1 remote host arrives on pg0 again.
+ # VPP should have an ARP entry for that address now and the packet
+ # should be sent out pg1.
+ #
+ self.pg0.add_stream(p0)
+ self.pg_enable_capture(self.pg_interfaces)
+ self.pg_start()
+
+ rx1 = self.pg1.get_capture(1)
+
+ self.verify_ip(rx1[0],
+ self.pg1.local_mac,
+ "00:00:5e:00:01:09",
+ self.pg0.remote_ip4,
+ self.pg1.remote_ip4)
+
+ self.pg1.admin_down()
+ self.pg1.admin_up()
+
+ def test_arp_duplicates(self):
+ """ ARP Duplicates"""
+
+ #
+ # Generate some hosts on the LAN
+ #
+ self.pg1.generate_remote_hosts(3)
+
+ #
+ # Add host 1 on pg1 and pg2
+ #
+ arp_pg1 = VppNeighbor(self,
+ self.pg1.sw_if_index,
+ self.pg1.remote_hosts[1].mac,
+ self.pg1.remote_hosts[1].ip4)
+ arp_pg1.add_vpp_config()
+ arp_pg2 = VppNeighbor(self,
+ self.pg2.sw_if_index,
+ self.pg2.remote_mac,
+ self.pg1.remote_hosts[1].ip4)
+ arp_pg2.add_vpp_config()
+
+ #
+ # IP packet destined for pg1 remote host arrives on pg1 again.
+ #
+ p = (Ether(dst=self.pg0.local_mac,
+ src=self.pg0.remote_mac) /
+ IP(src=self.pg0.remote_ip4,
+ dst=self.pg1.remote_hosts[1].ip4) /
+ UDP(sport=1234, dport=1234) /
+ Raw())
+
+ self.pg0.add_stream(p)
+ self.pg_enable_capture(self.pg_interfaces)
+ self.pg_start()
+
+ rx1 = self.pg1.get_capture(1)
+
+ self.verify_ip(rx1[0],
+ self.pg1.local_mac,
+ self.pg1.remote_hosts[1].mac,
+ self.pg0.remote_ip4,
+ self.pg1.remote_hosts[1].ip4)
+
+ #
+ # remove the duplicate on pg1
+ # packet stream shoud generate ARPs out of pg1
+ #
+ arp_pg1.remove_vpp_config()
+
+ self.pg0.add_stream(p)
+ self.pg_enable_capture(self.pg_interfaces)
+ self.pg_start()
+
+ rx1 = self.pg1.get_capture(1)
+
+ self.verify_arp_req(rx1[0],
+ self.pg1.local_mac,
+ self.pg1.local_ip4,
+ self.pg1.remote_hosts[1].ip4)
+
+ #
+ # Add it back
+ #
+ arp_pg1.add_vpp_config()
+
+ self.pg0.add_stream(p)
+ self.pg_enable_capture(self.pg_interfaces)
+ self.pg_start()
+
+ rx1 = self.pg1.get_capture(1)
+
+ self.verify_ip(rx1[0],
+ self.pg1.local_mac,
+ self.pg1.remote_hosts[1].mac,
+ self.pg0.remote_ip4,
+ self.pg1.remote_hosts[1].ip4)
+
+ def test_arp_static(self):
+ """ ARP Static"""
+ self.pg2.generate_remote_hosts(3)
+
+ #
+ # Add a static ARP entry
+ #
+ static_arp = VppNeighbor(self,
+ self.pg2.sw_if_index,
+ self.pg2.remote_hosts[1].mac,
+ self.pg2.remote_hosts[1].ip4,
+ is_static=1)
+ static_arp.add_vpp_config()
+
+ #
+ # Add the connected prefix to the interface
+ #
+ self.pg2.config_ip4()
+
+ #
+ # We should now find the adj-fib
+ #
+ self.assertTrue(find_nbr(self,
+ self.pg2.sw_if_index,
+ self.pg2.remote_hosts[1].ip4,
+ is_static=1))
+ self.assertTrue(find_route(self,
+ self.pg2.remote_hosts[1].ip4,
+ 32))
+
+ #
+ # remove the connected
+ #
+ self.pg2.unconfig_ip4()
+
+ #
+ # put the interface into table 1
+ #
+ self.pg2.set_table_ip4(1)
+
+ #
+ # configure the same connected and expect to find the
+ # adj fib in the new table
+ #
+ self.pg2.config_ip4()
+ self.assertTrue(find_route(self,
+ self.pg2.remote_hosts[1].ip4,
+ 32,
+ table_id=1))
+
+ #
+ # clean-up
+ #
+ self.pg2.unconfig_ip4()
+ self.pg2.set_table_ip4(0)
+
+
+if __name__ == '__main__':
+ unittest.main(testRunner=VppTestRunner)
diff --git a/test/test_p2p_ethernet.py b/test/test_p2p_ethernet.py
new file mode 100644
index 00000000..8688f7e6
--- /dev/null
+++ b/test/test_p2p_ethernet.py
@@ -0,0 +1,538 @@
+#!/usr/bin/env python
+import random
+import unittest
+import datetime
+import re
+
+from scapy.packet import Raw
+from scapy.layers.l2 import Ether
+from scapy.layers.inet import IP, UDP
+from scapy.layers.inet6 import IPv6
+
+from framework import VppTestCase, VppTestRunner, running_extended_tests
+from vpp_sub_interface import VppP2PSubint
+from vpp_ip_route import VppIpRoute, VppRoutePath, DpoProto
+from util import mactobinary
+
+
+class P2PEthernetAPI(VppTestCase):
+ """P2P Ethernet tests"""
+
+ p2p_sub_ifs = []
+
+ @classmethod
+ def setUpClass(cls):
+ super(P2PEthernetAPI, cls).setUpClass()
+
+ # Create pg interfaces
+ cls.create_pg_interfaces(range(4))
+
+ # Set up all interfaces
+ for i in cls.pg_interfaces:
+ i.admin_up()
+
+ def create_p2p_ethernet(self, parent_if, sub_id, remote_mac):
+ p2p = VppP2PSubint(self, parent_if, sub_id, mactobinary(remote_mac))
+ self.p2p_sub_ifs.append(p2p)
+
+ def delete_p2p_ethernet(self, parent_if, remote_mac):
+ self.vapi.delete_p2pethernet_subif(parent_if.sw_if_index,
+ mactobinary(remote_mac))
+
+ def test_api(self):
+ """delete/create p2p subif"""
+ self.logger.info("FFP_TEST_START_0000")
+
+ self.create_p2p_ethernet(self.pg0, 1, "de:ad:00:00:00:01")
+ self.create_p2p_ethernet(self.pg0, 2, "de:ad:00:00:00:02")
+ intfs = self.vapi.cli("show interface")
+
+ self.assertNotEqual(intfs.find('pg0.1'), -1)
+ self.assertNotEqual(intfs.find('pg0.2'), -1)
+ self.assertEqual(intfs.find('pg0.5'), -1)
+
+ # create pg2.5 subif
+ self.create_p2p_ethernet(self.pg0, 5, "de:ad:00:00:00:ff")
+ intfs = self.vapi.cli("show interface")
+ self.assertNotEqual(intfs.find('pg0.5'), -1)
+ # delete pg2.5 subif
+ self.delete_p2p_ethernet(self.pg0, "de:ad:00:00:00:ff")
+
+ intfs = self.vapi.cli("show interface")
+
+ self.assertNotEqual(intfs.find('pg0.1'), -1)
+ self.assertNotEqual(intfs.find('pg0.2'), -1)
+ self.assertEqual(intfs.find('pg0.5'), -1)
+
+ self.logger.info("FFP_TEST_FINISH_0000")
+
+ def test_p2p_subif_creation_1k(self):
+ """create 1k of p2p subifs"""
+ self.logger.info("FFP_TEST_START_0001")
+
+ macs = []
+ clients = 1000
+ mac = int("dead00000000", 16)
+
+ for i in range(1, clients+1):
+ try:
+ macs.append(':'.join(re.findall('..', '{:02x}'.format(mac+i))))
+ self.vapi.create_p2pethernet_subif(self.pg2.sw_if_index,
+ mactobinary(macs[i-1]),
+ i)
+ except Exception:
+ print "Failed to create subif %d %s" % (i, macs[i-1])
+ raise
+
+ intfs = self.vapi.cli("show interface").split("\n")
+ count = 0
+ for intf in intfs:
+ if intf.startswith('pg2.'):
+ count += 1
+ self.assertEqual(count, clients)
+
+ self.logger.info("FFP_TEST_FINISH_0001")
+
+ @unittest.skipUnless(running_extended_tests(), "part of extended tests")
+ def test_p2p_subif_creation_10k(self):
+ """create 100k of p2p subifs"""
+ self.logger.info("FFP_TEST_START_0001")
+
+ macs = []
+ clients = 100000
+ mac = int("dead00000000", 16)
+
+ s_time = datetime.datetime.now()
+ for i in range(1, clients+1):
+ if i % 1000 == 0:
+ e_time = datetime.datetime.now()
+ print "Created 1000 subifs in %s secs" % (e_time - s_time)
+ s_time = e_time
+ try:
+ macs.append(':'.join(re.findall('..', '{:02x}'.format(mac+i))))
+ self.vapi.create_p2pethernet_subif(self.pg3.sw_if_index,
+ mactobinary(macs[i-1]),
+ i)
+ except Exception:
+ print "Failed to create subif %d %s" % (i, macs[i-1])
+ raise
+
+ intfs = self.vapi.cli("show interface").split("\n")
+ count = 0
+ for intf in intfs:
+ if intf.startswith('pg3.'):
+ count += 1
+ self.assertEqual(count, clients)
+
+ self.logger.info("FFP_TEST_FINISH_0001")
+
+
+class P2PEthernetIPV6(VppTestCase):
+ """P2P Ethernet IPv6 tests"""
+
+ p2p_sub_ifs = []
+ packets = []
+
+ @classmethod
+ def setUpClass(cls):
+ super(P2PEthernetIPV6, cls).setUpClass()
+
+ # Create pg interfaces
+ cls.create_pg_interfaces(range(3))
+
+ # Packet sizes
+ cls.pg_if_packet_sizes = [64, 512, 1518, 9018]
+
+ # Set up all interfaces
+ for i in cls.pg_interfaces:
+ i.admin_up()
+
+ cls.pg0.generate_remote_hosts(3)
+ cls.pg0.configure_ipv6_neighbors()
+
+ cls.pg1.config_ip6()
+ cls.pg1.generate_remote_hosts(3)
+ cls.pg1.configure_ipv6_neighbors()
+ cls.pg1.disable_ipv6_ra()
+
+ def setUp(self):
+ super(P2PEthernetIPV6, self).setUp()
+ for p in self.packets:
+ self.packets.remove(p)
+ self.create_p2p_ethernet(self.pg0, 1, self.pg0._remote_hosts[0].mac)
+ self.create_p2p_ethernet(self.pg0, 2, self.pg0._remote_hosts[1].mac)
+ self.p2p_sub_ifs[0].config_ip6()
+ self.p2p_sub_ifs[1].config_ip6()
+ self.vapi.cli("trace add p2p-ethernet-input 50")
+
+ def tearDown(self):
+ self.delete_p2p_ethernet(self.pg0, self.pg0._remote_hosts[0].mac)
+ self.delete_p2p_ethernet(self.pg0, self.pg0._remote_hosts[1].mac)
+ super(P2PEthernetIPV6, self).tearDown()
+
+ def create_p2p_ethernet(self, parent_if, sub_id, remote_mac):
+ p2p = VppP2PSubint(self, parent_if, sub_id, mactobinary(remote_mac))
+ p2p.admin_up()
+ p2p.config_ip6()
+ p2p.disable_ipv6_ra()
+ self.p2p_sub_ifs.append(p2p)
+
+ def delete_p2p_ethernet(self, parent_if, remote_mac):
+ self.vapi.delete_p2pethernet_subif(parent_if.sw_if_index,
+ mactobinary(remote_mac))
+
+ def create_stream(self, src_mac=None, dst_mac=None,
+ src_ip=None, dst_ip=None, size=None):
+ pkt_size = size
+ if size is None:
+ pkt_size = random.choice(self.pg_if_packet_sizes)
+ p = Ether(src=src_mac, dst=dst_mac)
+ p /= IPv6(src=src_ip, dst=dst_ip)
+ p /= (UDP(sport=1234, dport=4321) / Raw('\xa5' * 20))
+ self.extend_packet(p, pkt_size)
+ return p
+
+ def send_packets(self, src_if=None, dst_if=None, packets=None, count=None):
+ self.pg_enable_capture([dst_if])
+ if packets is None:
+ packets = self.packets
+ src_if.add_stream(packets)
+ self.pg_start()
+ if count is None:
+ count = len(packets)
+ return dst_if.get_capture(count)
+
+ def verify_counters(self, counter_id, expected_value):
+ counters = self.vapi.cli("sh errors").split('\n')
+ counter_value = -1
+ for i in range(1, len(counters)-1):
+ results = counters[i].split()
+ if results[1] == counter_id:
+ counter_value = int(results[0])
+ break
+ self.assertEqual(counter_value, expected_value)
+
+ def test_no_p2p_subif(self):
+ """standard routing without p2p subinterfaces"""
+ self.logger.info("FFP_TEST_START_0001")
+
+ route_8000 = VppIpRoute(self, "8000::", 64,
+ [VppRoutePath(self.pg0.remote_ip6,
+ self.pg0.sw_if_index,
+ proto=DpoProto.DPO_PROTO_IP6)],
+ is_ip6=1)
+ route_8000.add_vpp_config()
+
+ self.packets = [(Ether(dst=self.pg1.local_mac,
+ src=self.pg1.remote_mac) /
+ IPv6(src="3001::1", dst="8000::100") /
+ UDP(sport=1234, dport=1234) /
+ Raw('\xa5' * 100))]
+ self.send_packets(self.pg1, self.pg0)
+
+ self.logger.info("FFP_TEST_FINISH_0001")
+
+ def test_ip6_rx_p2p_subif(self):
+ """receive ipv6 packet via p2p subinterface"""
+ self.logger.info("FFP_TEST_START_0002")
+
+ route_9001 = VppIpRoute(self, "9001::", 64,
+ [VppRoutePath(self.pg1.remote_ip6,
+ self.pg1.sw_if_index,
+ proto=DpoProto.DPO_PROTO_IP6)],
+ is_ip6=1)
+ route_9001.add_vpp_config()
+
+ self.packets.append(
+ self.create_stream(src_mac=self.pg0._remote_hosts[0].mac,
+ dst_mac=self.pg0.local_mac,
+ src_ip=self.p2p_sub_ifs[0].remote_ip6,
+ dst_ip="9001::100"))
+
+ self.send_packets(self.pg0, self.pg1, self.packets)
+ self.verify_counters('p2p-ethernet-input', 1)
+
+ route_9001.remove_vpp_config()
+ self.logger.info("FFP_TEST_FINISH_0002")
+
+ def test_ip6_rx_p2p_subif_route(self):
+ """route rx ip6 packet not matching p2p subinterface"""
+ self.logger.info("FFP_TEST_START_0003")
+
+ self.pg0.config_ip6()
+
+ route_3 = VppIpRoute(self, "9000::", 64,
+ [VppRoutePath(self.pg1._remote_hosts[0].ip6,
+ self.pg1.sw_if_index,
+ proto=DpoProto.DPO_PROTO_IP6)],
+ is_ip6=1)
+ route_3.add_vpp_config()
+
+ self.packets.append(
+ self.create_stream(src_mac="02:03:00:00:ff:ff",
+ dst_mac=self.pg0.local_mac,
+ src_ip="a000::100",
+ dst_ip="9000::100"))
+
+ self.send_packets(self.pg0, self.pg1)
+
+ self.pg0.unconfig_ip6()
+
+ route_3.remove_vpp_config()
+
+ self.logger.info("FFP_TEST_FINISH_0003")
+
+ def test_ip6_rx_p2p_subif_drop(self):
+ """drop rx packet not matching p2p subinterface"""
+ self.logger.info("FFP_TEST_START_0004")
+
+ route_9001 = VppIpRoute(self, "9000::", 64,
+ [VppRoutePath(self.pg1._remote_hosts[0].ip6,
+ self.pg1.sw_if_index,
+ proto=DpoProto.DPO_PROTO_IP6)],
+ is_ip6=1)
+ route_9001.add_vpp_config()
+
+ self.packets.append(
+ self.create_stream(src_mac="02:03:00:00:ff:ff",
+ dst_mac=self.pg0.local_mac,
+ src_ip="a000::100",
+ dst_ip="9000::100"))
+
+ # no packet received
+ self.send_packets(self.pg0, self.pg1, count=0)
+ self.logger.info("FFP_TEST_FINISH_0004")
+
+ def test_ip6_tx_p2p_subif(self):
+ """send packet via p2p subinterface"""
+ self.logger.info("FFP_TEST_START_0005")
+
+ route_8000 = VppIpRoute(self, "8000::", 64,
+ [VppRoutePath(self.pg0.remote_ip6,
+ self.pg0.sw_if_index,
+ proto=DpoProto.DPO_PROTO_IP6)],
+ is_ip6=1)
+ route_8000.add_vpp_config()
+ route_8001 = VppIpRoute(self, "8001::", 64,
+ [VppRoutePath(self.p2p_sub_ifs[0].remote_ip6,
+ self.p2p_sub_ifs[0].sw_if_index,
+ proto=DpoProto.DPO_PROTO_IP6)],
+ is_ip6=1)
+ route_8001.add_vpp_config()
+ route_8002 = VppIpRoute(self, "8002::", 64,
+ [VppRoutePath(self.p2p_sub_ifs[1].remote_ip6,
+ self.p2p_sub_ifs[1].sw_if_index,
+ proto=DpoProto.DPO_PROTO_IP6)],
+ is_ip6=1)
+ route_8002.add_vpp_config()
+
+ for i in range(0, 3):
+ self.packets.append(
+ self.create_stream(src_mac=self.pg1.remote_mac,
+ dst_mac=self.pg1.local_mac,
+ src_ip=self.pg1.remote_ip6,
+ dst_ip="800%d::100" % i))
+
+ self.send_packets(self.pg1, self.pg0, count=3)
+
+ route_8000.remove_vpp_config()
+ route_8001.remove_vpp_config()
+ route_8002.remove_vpp_config()
+
+ self.logger.info("FFP_TEST_FINISH_0005")
+
+ def test_ip6_tx_p2p_subif_drop(self):
+ """drop tx ip6 packet not matching p2p subinterface"""
+ self.logger.info("FFP_TEST_START_0006")
+
+ self.packets.append(
+ self.create_stream(src_mac="02:03:00:00:ff:ff",
+ dst_mac=self.pg0.local_mac,
+ src_ip="a000::100",
+ dst_ip="9000::100"))
+
+ # no packet received
+ self.send_packets(self.pg0, self.pg1, count=0)
+ self.logger.info("FFP_TEST_FINISH_0006")
+
+
+class P2PEthernetIPV4(VppTestCase):
+ """P2P Ethernet IPv4 tests"""
+
+ p2p_sub_ifs = []
+ packets = []
+
+ @classmethod
+ def setUpClass(cls):
+ super(P2PEthernetIPV4, cls).setUpClass()
+
+ # Create pg interfaces
+ cls.create_pg_interfaces(range(3))
+
+ # Packet sizes
+ cls.pg_if_packet_sizes = [64, 512, 1518, 9018]
+
+ # Set up all interfaces
+ for i in cls.pg_interfaces:
+ i.admin_up()
+
+ cls.pg0.config_ip4()
+ cls.pg0.generate_remote_hosts(5)
+ cls.pg0.configure_ipv4_neighbors()
+
+ cls.pg1.config_ip4()
+ cls.pg1.generate_remote_hosts(5)
+ cls.pg1.configure_ipv4_neighbors()
+
+ def setUp(self):
+ super(P2PEthernetIPV4, self).setUp()
+ for p in self.packets:
+ self.packets.remove(p)
+ self.create_p2p_ethernet(self.pg0, 1, self.pg0._remote_hosts[0].mac)
+ self.create_p2p_ethernet(self.pg0, 2, self.pg0._remote_hosts[1].mac)
+ self.p2p_sub_ifs[0].config_ip4()
+ self.p2p_sub_ifs[1].config_ip4()
+ self.vapi.cli("trace add p2p-ethernet-input 50")
+
+ def tearDown(self):
+ self.delete_p2p_ethernet(self.pg0, self.pg0._remote_hosts[0].mac)
+ self.delete_p2p_ethernet(self.pg0, self.pg0._remote_hosts[1].mac)
+ super(P2PEthernetIPV4, self).tearDown()
+
+ def create_stream(self, src_mac=None, dst_mac=None,
+ src_ip=None, dst_ip=None, size=None):
+ pkt_size = size
+ if size is None:
+ pkt_size = random.choice(self.pg_if_packet_sizes)
+ p = Ether(src=src_mac, dst=dst_mac)
+ p /= IP(src=src_ip, dst=dst_ip)
+ p /= (UDP(sport=1234, dport=4321) / Raw('\xa5' * 20))
+ self.extend_packet(p, pkt_size)
+ return p
+
+ def send_packets(self, src_if=None, dst_if=None, packets=None, count=None):
+ self.pg_enable_capture([dst_if])
+ if packets is None:
+ packets = self.packets
+ src_if.add_stream(packets)
+ self.pg_start()
+ if count is None:
+ count = len(packets)
+ return dst_if.get_capture(count)
+
+ def verify_counters(self, counter_id, expected_value):
+ counters = self.vapi.cli("sh errors").split('\n')
+ counter_value = -1
+ for i in range(1, len(counters)-1):
+ results = counters[i].split()
+ if results[1] == counter_id:
+ counter_value = int(results[0])
+ break
+ self.assertEqual(counter_value, expected_value)
+
+ def create_p2p_ethernet(self, parent_if, sub_id, remote_mac):
+ p2p = VppP2PSubint(self, parent_if, sub_id, mactobinary(remote_mac))
+ p2p.admin_up()
+ p2p.config_ip4()
+ self.p2p_sub_ifs.append(p2p)
+
+ def delete_p2p_ethernet(self, parent_if, remote_mac):
+ self.vapi.delete_p2pethernet_subif(parent_if.sw_if_index,
+ mactobinary(remote_mac))
+
+ def test_ip4_rx_p2p_subif(self):
+ """receive ipv4 packet via p2p subinterface"""
+ self.logger.info("FFP_TEST_START_0002")
+
+ route_9000 = VppIpRoute(self, "9.0.0.0", 16,
+ [VppRoutePath(self.pg1.remote_ip4,
+ self.pg1.sw_if_index)])
+ route_9000.add_vpp_config()
+
+ self.packets.append(
+ self.create_stream(src_mac=self.pg0._remote_hosts[0].mac,
+ dst_mac=self.pg0.local_mac,
+ src_ip=self.p2p_sub_ifs[0].remote_ip4,
+ dst_ip="9.0.0.100"))
+
+ self.send_packets(self.pg0, self.pg1, self.packets)
+
+ self.verify_counters('p2p-ethernet-input', 1)
+
+ route_9000.remove_vpp_config()
+ self.logger.info("FFP_TEST_FINISH_0002")
+
+ def test_ip4_rx_p2p_subif_route(self):
+ """route rx packet not matching p2p subinterface"""
+ self.logger.info("FFP_TEST_START_0003")
+
+ route_9001 = VppIpRoute(self, "9.0.0.0", 24,
+ [VppRoutePath(self.pg1.remote_ip4,
+ self.pg1.sw_if_index)])
+ route_9001.add_vpp_config()
+
+ self.packets.append(
+ self.create_stream(src_mac="02:01:00:00:ff:ff",
+ dst_mac=self.pg0.local_mac,
+ src_ip="8.0.0.100",
+ dst_ip="9.0.0.100"))
+
+ self.send_packets(self.pg0, self.pg1)
+
+ route_9001.remove_vpp_config()
+
+ self.logger.info("FFP_TEST_FINISH_0003")
+
+ def test_ip4_tx_p2p_subif(self):
+ """send ip4 packet via p2p subinterface"""
+ self.logger.info("FFP_TEST_START_0005")
+
+ route_9100 = VppIpRoute(self, "9.1.0.100", 24,
+ [VppRoutePath(self.pg0.remote_ip4,
+ self.pg0.sw_if_index,
+ )])
+ route_9100.add_vpp_config()
+ route_9200 = VppIpRoute(self, "9.2.0.100", 24,
+ [VppRoutePath(self.p2p_sub_ifs[0].remote_ip4,
+ self.p2p_sub_ifs[0].sw_if_index,
+ )])
+ route_9200.add_vpp_config()
+ route_9300 = VppIpRoute(self, "9.3.0.100", 24,
+ [VppRoutePath(self.p2p_sub_ifs[1].remote_ip4,
+ self.p2p_sub_ifs[1].sw_if_index
+ )])
+ route_9300.add_vpp_config()
+
+ for i in range(0, 3):
+ self.packets.append(
+ self.create_stream(src_mac=self.pg1.remote_mac,
+ dst_mac=self.pg1.local_mac,
+ src_ip=self.pg1.remote_ip4,
+ dst_ip="9.%d.0.100" % (i+1)))
+
+ self.send_packets(self.pg1, self.pg0)
+
+ # route_7000.remove_vpp_config()
+ route_9100.remove_vpp_config()
+ route_9200.remove_vpp_config()
+ route_9300.remove_vpp_config()
+
+ self.logger.info("FFP_TEST_FINISH_0005")
+
+ def test_ip4_tx_p2p_subif_drop(self):
+ """drop tx ip4 packet not matching p2p subinterface"""
+ self.logger.info("FFP_TEST_START_0006")
+
+ self.packets.append(
+ self.create_stream(src_mac="02:01:00:00:ff:ff",
+ dst_mac=self.pg0.local_mac,
+ src_ip="8.0.0.100",
+ dst_ip="9.0.0.100"))
+
+ # no packet received
+ self.send_packets(self.pg0, self.pg1, count=0)
+ self.logger.info("FFP_TEST_FINISH_0006")
+
+
+if __name__ == '__main__':
+ unittest.main(testRunner=VppTestRunner)
diff --git a/test/test_papi.py b/test/test_papi.py
new file mode 100644
index 00000000..1a5f6ae6
--- /dev/null
+++ b/test/test_papi.py
@@ -0,0 +1,31 @@
+import binascii
+from framework import VppTestCase
+
+""" TestPAPI is a subclass of VPPTestCase classes.
+
+Basic test for sanity check of the Python API binding.
+
+"""
+
+
+class TestPAPI(VppTestCase):
+ """ PAPI Test Case """
+
+ @classmethod
+ def setUpClass(cls):
+ super(TestPAPI, cls).setUpClass()
+ cls.v = cls.vapi.papi
+
+ def test_show_version(self):
+ rv = self.v.show_version()
+ self.assertEqual(rv.retval, 0)
+
+ def test_show_version_invalid_param(self):
+ self.assertRaises(ValueError, self.v.show_version, foobar='foo')
+
+ def test_u8_array(self):
+ rv = self.v.get_node_index(node_name='ip4-lookup')
+ self.assertEqual(rv.retval, 0)
+ node_name = 'X' * 100
+ self.assertRaises(ValueError, self.v.get_node_index,
+ node_name=node_name)
diff --git a/test/test_ping.py b/test/test_ping.py
new file mode 100644
index 00000000..4f3921e9
--- /dev/null
+++ b/test/test_ping.py
@@ -0,0 +1,118 @@
+import socket
+
+from scapy.layers.inet import IP, UDP, ICMP
+from scapy.layers.inet6 import IPv6
+from scapy.layers.l2 import Ether, GRE
+from scapy.packet import Raw
+
+from framework import VppTestCase
+from util import ppp
+
+""" TestPing is a subclass of VPPTestCase classes.
+
+Basic test for sanity check of the ping.
+
+"""
+
+
+class TestPing(VppTestCase):
+ """ Ping Test Case """
+
+ @classmethod
+ def setUpClass(cls):
+ super(TestPing, cls).setUpClass()
+ try:
+ cls.create_pg_interfaces(range(2))
+ cls.interfaces = list(cls.pg_interfaces)
+
+ for i in cls.interfaces:
+ i.admin_up()
+ i.config_ip4()
+ i.config_ip6()
+ i.disable_ipv6_ra()
+ i.resolve_arp()
+ i.resolve_ndp()
+ except Exception:
+ super(TestPing, cls).tearDownClass()
+ raise
+
+ def tearDown(self):
+ super(TestPing, self).tearDown()
+ if not self.vpp_dead:
+ self.vapi.cli("show hardware")
+
+ def test_ping_basic(self):
+ """ basic ping test """
+ try:
+ self.pg_enable_capture(self.pg_interfaces)
+ self.pg_start()
+ self.logger.info(self.vapi.cli("show ip arp"))
+ self.logger.info(self.vapi.cli("show ip6 neighbors"))
+
+ remote_ip4 = self.pg1.remote_ip4
+ ping_cmd = "ping " + remote_ip4 + " interval 0.01 repeat 10"
+ ret = self.vapi.cli(ping_cmd)
+ self.logger.info(ret)
+ out = self.pg1.get_capture(10)
+ icmp_id = None
+ icmp_seq = 1
+ for p in out:
+ ip = p[IP]
+ self.assertEqual(ip.version, 4)
+ self.assertEqual(ip.flags, 0)
+ self.assertEqual(ip.src, self.pg1.local_ip4)
+ self.assertEqual(ip.dst, self.pg1.remote_ip4)
+ self.assertEqual(ip.proto, 1)
+ self.assertEqual(len(ip.options), 0)
+ self.assertGreaterEqual(ip.ttl, 254)
+ icmp = p[ICMP]
+ self.assertEqual(icmp.type, 8)
+ self.assertEqual(icmp.code, 0)
+ self.assertEqual(icmp.seq, icmp_seq)
+ icmp_seq = icmp_seq + 1
+ if icmp_id is None:
+ icmp_id = icmp.id
+ else:
+ self.assertEqual(icmp.id, icmp_id)
+ finally:
+ self.vapi.cli("show error")
+
+ def test_ping_burst(self):
+ """ burst ping test """
+ try:
+ self.pg_enable_capture(self.pg_interfaces)
+ self.pg_start()
+ self.logger.info(self.vapi.cli("show ip arp"))
+ self.logger.info(self.vapi.cli("show ip6 neighbors"))
+
+ remote_ip4 = self.pg1.remote_ip4
+ ping_cmd = "ping " + remote_ip4 + " interval 0.01 burst 3"
+ ret = self.vapi.cli(ping_cmd)
+ self.logger.info(ret)
+ out = self.pg1.get_capture(3*5)
+ icmp_id = None
+ icmp_seq = 1
+ count = 0
+ for p in out:
+ ip = p[IP]
+ self.assertEqual(ip.version, 4)
+ self.assertEqual(ip.flags, 0)
+ self.assertEqual(ip.src, self.pg1.local_ip4)
+ self.assertEqual(ip.dst, self.pg1.remote_ip4)
+ self.assertEqual(ip.proto, 1)
+ self.assertEqual(len(ip.options), 0)
+ self.assertGreaterEqual(ip.ttl, 254)
+ icmp = p[ICMP]
+ self.assertEqual(icmp.type, 8)
+ self.assertEqual(icmp.code, 0)
+ self.assertEqual(icmp.seq, icmp_seq)
+ count = count + 1
+ if count >= 3:
+ icmp_seq = icmp_seq + 1
+ count = 0
+ if icmp_id is None:
+ icmp_id = icmp.id
+ else:
+ self.assertEqual(icmp.id, icmp_id)
+ finally:
+ self.vapi.cli("show error")
diff --git a/test/test_pppoe.py b/test/test_pppoe.py
new file mode 100644
index 00000000..1d0aeffd
--- /dev/null
+++ b/test/test_pppoe.py
@@ -0,0 +1,606 @@
+#!/usr/bin/env python
+
+import unittest
+from logging import *
+
+from framework import VppTestCase, VppTestRunner
+from vpp_ip_route import VppIpRoute, VppRoutePath
+from vpp_pppoe_interface import VppPppoeInterface, VppPppoe6Interface
+from vpp_papi_provider import L2_VTR_OP
+
+from scapy.packet import Raw
+from scapy.layers.l2 import Ether
+from scapy.layers.ppp import PPPoE, PPPoED, PPP
+from scapy.layers.inet import IP, UDP
+from scapy.layers.inet6 import IPv6
+from scapy.volatile import RandMAC, RandIP
+
+from util import ppp, ppc, mactobinary
+import socket
+
+
+class TestPPPoE(VppTestCase):
+ """ PPPoE Test Case """
+
+ @classmethod
+ def setUpClass(cls):
+ super(TestPPPoE, cls).setUpClass()
+
+ cls.session_id = 1
+ cls.dst_ip = "100.1.1.100"
+ cls.dst_ipn = socket.inet_pton(socket.AF_INET, cls.dst_ip)
+
+ def setUp(self):
+ super(TestPPPoE, self).setUp()
+
+ # create 2 pg interfaces
+ self.create_pg_interfaces(range(3))
+
+ for i in self.pg_interfaces:
+ i.admin_up()
+ i.config_ip4()
+ i.resolve_arp()
+
+ def tearDown(self):
+ super(TestPPPoE, self).tearDown()
+
+ self.logger.info(self.vapi.cli("show int"))
+ self.logger.info(self.vapi.cli("show pppoe fib"))
+ self.logger.info(self.vapi.cli("show pppoe session"))
+ self.logger.info(self.vapi.cli("show ip fib"))
+ self.logger.info(self.vapi.cli("show trace"))
+
+ for i in self.pg_interfaces:
+ i.unconfig_ip4()
+ i.admin_down()
+
+ def create_stream_pppoe_discovery(self, src_if, dst_if,
+ client_mac, count=1):
+ packets = []
+ for i in range(count):
+ # create packet info stored in the test case instance
+ info = self.create_packet_info(src_if, dst_if)
+ # convert the info into packet payload
+ payload = self.info_to_payload(info)
+ # create the packet itself
+ p = (Ether(dst=src_if.local_mac, src=client_mac) /
+ PPPoED(sessionid=0) /
+ Raw(payload))
+ # store a copy of the packet in the packet info
+ info.data = p.copy()
+ # append the packet to the list
+ packets.append(p)
+
+ # return the created packet list
+ return packets
+
+ def create_stream_pppoe_lcp(self, src_if, dst_if,
+ client_mac, session_id, count=1):
+ packets = []
+ for i in range(count):
+ # create packet info stored in the test case instance
+ info = self.create_packet_info(src_if, dst_if)
+ # convert the info into packet payload
+ payload = self.info_to_payload(info)
+ # create the packet itself
+ p = (Ether(dst=src_if.local_mac, src=client_mac) /
+ PPPoE(sessionid=session_id) /
+ PPP(proto=0xc021) /
+ Raw(payload))
+ # store a copy of the packet in the packet info
+ info.data = p.copy()
+ # append the packet to the list
+ packets.append(p)
+
+ # return the created packet list
+ return packets
+
+ def create_stream_pppoe_ip4(self, src_if, dst_if,
+ client_mac, session_id, client_ip, count=1):
+ packets = []
+ for i in range(count):
+ # create packet info stored in the test case instance
+ info = self.create_packet_info(src_if, dst_if)
+ # convert the info into packet payload
+ payload = self.info_to_payload(info)
+ # create the packet itself
+ p = (Ether(dst=src_if.local_mac, src=client_mac) /
+ PPPoE(sessionid=session_id) /
+ PPP(proto=0x0021) /
+ IP(src=client_ip, dst=self.dst_ip) /
+ Raw(payload))
+ # store a copy of the packet in the packet info
+ info.data = p.copy()
+ # append the packet to the list
+ packets.append(p)
+
+ # return the created packet list
+ return packets
+
+ def create_stream_ip4(self, src_if, dst_if, client_ip, dst_ip, count=1):
+ pkts = []
+ for i in range(count):
+ # create packet info stored in the test case instance
+ info = self.create_packet_info(src_if, dst_if)
+ # convert the info into packet payload
+ payload = self.info_to_payload(info)
+ # create the packet itself
+ p = (Ether(dst=src_if.local_mac, src=src_if.remote_mac) /
+ IP(src=dst_ip, dst=client_ip) /
+ Raw(payload))
+ # store a copy of the packet in the packet info
+ info.data = p.copy()
+ # append the packet to the list
+ pkts.append(p)
+
+ # return the created packet list
+ return pkts
+
+ def verify_decapped_pppoe(self, src_if, capture, sent):
+ self.assertEqual(len(capture), len(sent))
+
+ for i in range(len(capture)):
+ try:
+ tx = sent[i]
+ rx = capture[i]
+
+ tx_ip = tx[IP]
+ rx_ip = rx[IP]
+
+ self.assertEqual(rx_ip.src, tx_ip.src)
+ self.assertEqual(rx_ip.dst, tx_ip.dst)
+
+ except:
+ self.logger.error(ppp("Rx:", rx))
+ self.logger.error(ppp("Tx:", tx))
+ raise
+
+ def verify_encaped_pppoe(self, src_if, capture, sent, session_id):
+
+ self.assertEqual(len(capture), len(sent))
+
+ for i in range(len(capture)):
+ try:
+ tx = sent[i]
+ rx = capture[i]
+
+ tx_ip = tx[IP]
+ rx_ip = rx[IP]
+
+ self.assertEqual(rx_ip.src, tx_ip.src)
+ self.assertEqual(rx_ip.dst, tx_ip.dst)
+
+ rx_pppoe = rx[PPPoE]
+
+ self.assertEqual(rx_pppoe.sessionid, session_id)
+
+ except:
+ self.logger.error(ppp("Rx:", rx))
+ self.logger.error(ppp("Tx:", tx))
+ raise
+
+ def test_PPPoE_Decap(self):
+ """ PPPoE Decap Test """
+
+ self.vapi.cli("clear trace")
+
+ #
+ # Add a route that resolves the server's destination
+ #
+ route_sever_dst = VppIpRoute(self, "100.1.1.100", 32,
+ [VppRoutePath(self.pg1.remote_ip4,
+ self.pg1.sw_if_index)])
+ route_sever_dst.add_vpp_config()
+
+ # Send PPPoE Discovery
+ tx0 = self.create_stream_pppoe_discovery(self.pg0, self.pg1,
+ self.pg0.remote_mac)
+ self.pg0.add_stream(tx0)
+ self.pg_start()
+
+ # Send PPPoE PPP LCP
+ tx1 = self.create_stream_pppoe_lcp(self.pg0, self.pg1,
+ self.pg0.remote_mac,
+ self.session_id)
+ self.pg0.add_stream(tx1)
+ self.pg_start()
+
+ # Create PPPoE session
+ pppoe_if = VppPppoeInterface(self,
+ self.pg0.remote_ip4,
+ self.pg0.remote_mac,
+ self.session_id)
+ pppoe_if.add_vpp_config()
+
+ #
+ # Send tunneled packets that match the created tunnel and
+ # are decapped and forwarded
+ #
+ tx2 = self.create_stream_pppoe_ip4(self.pg0, self.pg1,
+ self.pg0.remote_mac,
+ self.session_id,
+ self.pg0.remote_ip4)
+ self.pg0.add_stream(tx2)
+
+ self.pg_enable_capture(self.pg_interfaces)
+ self.pg_start()
+
+ rx2 = self.pg1.get_capture(len(tx2))
+ self.verify_decapped_pppoe(self.pg0, rx2, tx2)
+
+ self.logger.info(self.vapi.cli("show pppoe fib"))
+ self.logger.info(self.vapi.cli("show pppoe session"))
+ self.logger.info(self.vapi.cli("show ip fib"))
+
+ #
+ # test case cleanup
+ #
+
+ # Delete PPPoE session
+ pppoe_if.remove_vpp_config()
+
+ # Delete a route that resolves the server's destination
+ route_sever_dst.remove_vpp_config()
+
+ def test_PPPoE_Encap(self):
+ """ PPPoE Encap Test """
+
+ self.vapi.cli("clear trace")
+
+ #
+ # Add a route that resolves the server's destination
+ #
+ route_sever_dst = VppIpRoute(self, "100.1.1.100", 32,
+ [VppRoutePath(self.pg1.remote_ip4,
+ self.pg1.sw_if_index)])
+ route_sever_dst.add_vpp_config()
+
+ # Send PPPoE Discovery
+ tx0 = self.create_stream_pppoe_discovery(self.pg0, self.pg1,
+ self.pg0.remote_mac)
+ self.pg0.add_stream(tx0)
+ self.pg_start()
+
+ # Send PPPoE PPP LCP
+ tx1 = self.create_stream_pppoe_lcp(self.pg0, self.pg1,
+ self.pg0.remote_mac,
+ self.session_id)
+ self.pg0.add_stream(tx1)
+ self.pg_start()
+
+ # Create PPPoE session
+ pppoe_if = VppPppoeInterface(self,
+ self.pg0.remote_ip4,
+ self.pg0.remote_mac,
+ self.session_id)
+ pppoe_if.add_vpp_config()
+
+ #
+ # Send a packet stream that is routed into the session
+ # - packets are PPPoE encapped
+ #
+ self.vapi.cli("clear trace")
+ tx2 = self.create_stream_ip4(self.pg1, self.pg0,
+ self.pg0.remote_ip4, self.dst_ip, 65)
+ self.pg1.add_stream(tx2)
+
+ self.pg_enable_capture(self.pg_interfaces)
+ self.pg_start()
+
+ rx2 = self.pg0.get_capture(len(tx2))
+ self.verify_encaped_pppoe(self.pg1, rx2, tx2, self.session_id)
+
+ self.logger.info(self.vapi.cli("show pppoe fib"))
+ self.logger.info(self.vapi.cli("show pppoe session"))
+ self.logger.info(self.vapi.cli("show ip fib"))
+ self.logger.info(self.vapi.cli("show adj"))
+
+ #
+ # test case cleanup
+ #
+
+ # Delete PPPoE session
+ pppoe_if.remove_vpp_config()
+
+ # Delete a route that resolves the server's destination
+ route_sever_dst.remove_vpp_config()
+
+ def test_PPPoE_Add_Twice(self):
+ """ PPPoE Add Same Session Twice Test """
+
+ self.vapi.cli("clear trace")
+
+ #
+ # Add a route that resolves the server's destination
+ #
+ route_sever_dst = VppIpRoute(self, "100.1.1.100", 32,
+ [VppRoutePath(self.pg1.remote_ip4,
+ self.pg1.sw_if_index)])
+ route_sever_dst.add_vpp_config()
+
+ # Send PPPoE Discovery
+ tx0 = self.create_stream_pppoe_discovery(self.pg0, self.pg1,
+ self.pg0.remote_mac)
+ self.pg0.add_stream(tx0)
+ self.pg_start()
+
+ # Send PPPoE PPP LCP
+ tx1 = self.create_stream_pppoe_lcp(self.pg0, self.pg1,
+ self.pg0.remote_mac,
+ self.session_id)
+ self.pg0.add_stream(tx1)
+ self.pg_start()
+
+ # Create PPPoE session
+ pppoe_if = VppPppoeInterface(self,
+ self.pg0.remote_ip4,
+ self.pg0.remote_mac,
+ self.session_id)
+ pppoe_if.add_vpp_config()
+
+ #
+ # The double create (create the same session twice) should fail,
+ # and we should still be able to use the original
+ #
+ try:
+ gre_if.add_vpp_config()
+ except Exception:
+ pass
+ else:
+ self.fail("Double GRE tunnel add does not fail")
+
+ #
+ # test case cleanup
+ #
+
+ # Delete PPPoE session
+ pppoe_if.remove_vpp_config()
+
+ # Delete a route that resolves the server's destination
+ route_sever_dst.remove_vpp_config()
+
+ def test_PPPoE_Del_Twice(self):
+ """ PPPoE Delete Same Session Twice Test """
+
+ self.vapi.cli("clear trace")
+
+ #
+ # Add a route that resolves the server's destination
+ #
+ route_sever_dst = VppIpRoute(self, "100.1.1.100", 32,
+ [VppRoutePath(self.pg1.remote_ip4,
+ self.pg1.sw_if_index)])
+ route_sever_dst.add_vpp_config()
+
+ # Send PPPoE Discovery
+ tx0 = self.create_stream_pppoe_discovery(self.pg0, self.pg1,
+ self.pg0.remote_mac)
+ self.pg0.add_stream(tx0)
+ self.pg_start()
+
+ # Send PPPoE PPP LCP
+ tx1 = self.create_stream_pppoe_lcp(self.pg0, self.pg1,
+ self.pg0.remote_mac,
+ self.session_id)
+ self.pg0.add_stream(tx1)
+ self.pg_start()
+
+ # Create PPPoE session
+ pppoe_if = VppPppoeInterface(self,
+ self.pg0.remote_ip4,
+ self.pg0.remote_mac,
+ self.session_id)
+ pppoe_if.add_vpp_config()
+
+ # Delete PPPoE session
+ pppoe_if.remove_vpp_config()
+
+ #
+ # The double del (del the same session twice) should fail,
+ # and we should still be able to use the original
+ #
+ try:
+ gre_if.remove_vpp_config()
+ except Exception:
+ pass
+ else:
+ self.fail("Double GRE tunnel del does not fail")
+
+ #
+ # test case cleanup
+ #
+
+ # Delete a route that resolves the server's destination
+ route_sever_dst.remove_vpp_config()
+
+ def test_PPPoE_Decap_Multiple(self):
+ """ PPPoE Decap Multiple Sessions Test """
+
+ self.vapi.cli("clear trace")
+
+ #
+ # Add a route that resolves the server's destination
+ #
+ route_sever_dst = VppIpRoute(self, "100.1.1.100", 32,
+ [VppRoutePath(self.pg1.remote_ip4,
+ self.pg1.sw_if_index)])
+ route_sever_dst.add_vpp_config()
+
+ # Send PPPoE Discovery 1
+ tx0 = self.create_stream_pppoe_discovery(self.pg0, self.pg1,
+ self.pg0.remote_mac)
+ self.pg0.add_stream(tx0)
+ self.pg_start()
+
+ # Send PPPoE PPP LCP 1
+ tx1 = self.create_stream_pppoe_lcp(self.pg0, self.pg1,
+ self.pg0.remote_mac,
+ self.session_id)
+ self.pg0.add_stream(tx1)
+ self.pg_start()
+
+ # Create PPPoE session 1
+ pppoe_if1 = VppPppoeInterface(self,
+ self.pg0.remote_ip4,
+ self.pg0.remote_mac,
+ self.session_id)
+ pppoe_if1.add_vpp_config()
+
+ # Send PPPoE Discovery 2
+ tx3 = self.create_stream_pppoe_discovery(self.pg2, self.pg1,
+ self.pg2.remote_mac)
+ self.pg2.add_stream(tx3)
+ self.pg_start()
+
+ # Send PPPoE PPP LCP 2
+ tx4 = self.create_stream_pppoe_lcp(self.pg2, self.pg1,
+ self.pg2.remote_mac,
+ self.session_id + 1)
+ self.pg2.add_stream(tx4)
+ self.pg_start()
+
+ # Create PPPoE session 2
+ pppoe_if2 = VppPppoeInterface(self,
+ self.pg2.remote_ip4,
+ self.pg2.remote_mac,
+ self.session_id + 1)
+ pppoe_if2.add_vpp_config()
+
+ #
+ # Send tunneled packets that match the created tunnel and
+ # are decapped and forwarded
+ #
+ tx2 = self.create_stream_pppoe_ip4(self.pg0, self.pg1,
+ self.pg0.remote_mac,
+ self.session_id,
+ self.pg0.remote_ip4)
+ self.pg0.add_stream(tx2)
+
+ self.pg_enable_capture(self.pg_interfaces)
+ self.pg_start()
+
+ rx2 = self.pg1.get_capture(len(tx2))
+ self.verify_decapped_pppoe(self.pg0, rx2, tx2)
+
+ tx5 = self.create_stream_pppoe_ip4(self.pg2, self.pg1,
+ self.pg2.remote_mac,
+ self.session_id + 1,
+ self.pg2.remote_ip4)
+ self.pg2.add_stream(tx5)
+
+ self.pg_enable_capture(self.pg_interfaces)
+ self.pg_start()
+
+ rx5 = self.pg1.get_capture(len(tx5))
+ self.verify_decapped_pppoe(self.pg2, rx5, tx5)
+
+ self.logger.info(self.vapi.cli("show pppoe fib"))
+ self.logger.info(self.vapi.cli("show pppoe session"))
+ self.logger.info(self.vapi.cli("show ip fib"))
+
+ #
+ # test case cleanup
+ #
+
+ # Delete PPPoE session
+ pppoe_if1.remove_vpp_config()
+ pppoe_if2.remove_vpp_config()
+
+ # Delete a route that resolves the server's destination
+ route_sever_dst.remove_vpp_config()
+
+ def test_PPPoE_Encap_Multiple(self):
+ """ PPPoE Encap Multiple Sessions Test """
+
+ self.vapi.cli("clear trace")
+
+ #
+ # Add a route that resolves the server's destination
+ #
+ route_sever_dst = VppIpRoute(self, "100.1.1.100", 32,
+ [VppRoutePath(self.pg1.remote_ip4,
+ self.pg1.sw_if_index)])
+ route_sever_dst.add_vpp_config()
+
+ # Send PPPoE Discovery 1
+ tx0 = self.create_stream_pppoe_discovery(self.pg0, self.pg1,
+ self.pg0.remote_mac)
+ self.pg0.add_stream(tx0)
+ self.pg_start()
+
+ # Send PPPoE PPP LCP 1
+ tx1 = self.create_stream_pppoe_lcp(self.pg0, self.pg1,
+ self.pg0.remote_mac,
+ self.session_id)
+ self.pg0.add_stream(tx1)
+ self.pg_start()
+
+ # Create PPPoE session 1
+ pppoe_if1 = VppPppoeInterface(self,
+ self.pg0.remote_ip4,
+ self.pg0.remote_mac,
+ self.session_id)
+ pppoe_if1.add_vpp_config()
+
+ # Send PPPoE Discovery 2
+ tx3 = self.create_stream_pppoe_discovery(self.pg2, self.pg1,
+ self.pg2.remote_mac)
+ self.pg2.add_stream(tx3)
+ self.pg_start()
+
+ # Send PPPoE PPP LCP 2
+ tx4 = self.create_stream_pppoe_lcp(self.pg2, self.pg1,
+ self.pg2.remote_mac,
+ self.session_id + 1)
+ self.pg2.add_stream(tx4)
+ self.pg_start()
+
+ # Create PPPoE session 2
+ pppoe_if2 = VppPppoeInterface(self,
+ self.pg2.remote_ip4,
+ self.pg2.remote_mac,
+ self.session_id + 1)
+ pppoe_if2.add_vpp_config()
+
+ #
+ # Send a packet stream that is routed into the session
+ # - packets are PPPoE encapped
+ #
+ self.vapi.cli("clear trace")
+ tx2 = self.create_stream_ip4(self.pg1, self.pg0,
+ self.pg0.remote_ip4, self.dst_ip)
+ self.pg1.add_stream(tx2)
+
+ self.pg_enable_capture(self.pg_interfaces)
+ self.pg_start()
+
+ rx2 = self.pg0.get_capture(len(tx2))
+ self.verify_encaped_pppoe(self.pg1, rx2, tx2, self.session_id)
+
+ tx5 = self.create_stream_ip4(self.pg1, self.pg2,
+ self.pg2.remote_ip4, self.dst_ip)
+ self.pg1.add_stream(tx5)
+
+ self.pg_enable_capture(self.pg_interfaces)
+ self.pg_start()
+
+ rx5 = self.pg2.get_capture(len(tx5))
+ self.verify_encaped_pppoe(self.pg1, rx5, tx5, self.session_id + 1)
+
+ self.logger.info(self.vapi.cli("show pppoe fib"))
+ self.logger.info(self.vapi.cli("show pppoe session"))
+ self.logger.info(self.vapi.cli("show ip fib"))
+
+ #
+ # test case cleanup
+ #
+
+ # Delete PPPoE session
+ pppoe_if1.remove_vpp_config()
+ pppoe_if2.remove_vpp_config()
+
+ # Delete a route that resolves the server's destination
+ route_sever_dst.remove_vpp_config()
+
+if __name__ == '__main__':
+ unittest.main(testRunner=VppTestRunner)
diff --git a/test/test_span.py b/test/test_span.py
new file mode 100644
index 00000000..f2529e8f
--- /dev/null
+++ b/test/test_span.py
@@ -0,0 +1,481 @@
+#!/usr/bin/env python
+
+import unittest
+
+from scapy.packet import Raw
+from scapy.layers.l2 import Ether, Dot1Q, GRE
+from scapy.layers.inet import IP, UDP
+from scapy.layers.vxlan import VXLAN
+
+from framework import VppTestCase, VppTestRunner
+from util import Host, ppp
+from vpp_sub_interface import VppDot1QSubint, VppDot1ADSubint
+from vpp_gre_interface import VppGreInterface, VppGre6Interface
+from vpp_papi_provider import L2_VTR_OP
+from collections import namedtuple
+
+Tag = namedtuple('Tag', ['dot1', 'vlan'])
+DOT1AD = 0x88A8
+DOT1Q = 0x8100
+
+
+class TestSpan(VppTestCase):
+ """ SPAN Test Case """
+
+ @classmethod
+ def setUpClass(cls):
+ super(TestSpan, cls).setUpClass()
+ # Test variables
+ cls.hosts_nr = 10 # Number of hosts
+ cls.pkts_per_burst = 257 # Number of packets per burst
+ # create 3 pg interfaces
+ cls.create_pg_interfaces(range(3))
+
+ cls.bd_id = 55
+ cls.sub_if = VppDot1QSubint(cls, cls.pg0, 100)
+ cls.dst_sub_if = VppDot1QSubint(cls, cls.pg2, 300)
+ cls.dst_sub_if.set_vtr(L2_VTR_OP.L2_POP_1, tag=300)
+ # packet flows mapping pg0 -> pg1, pg2 -> pg3, etc.
+ cls.flows = dict()
+ cls.flows[cls.pg0] = [cls.pg1]
+
+ # packet sizes
+ cls.pg_if_packet_sizes = [64, 512] # , 1518, 9018]
+
+ cls.interfaces = list(cls.pg_interfaces)
+
+ # Create host MAC and IPv4 lists
+ # cls.MY_MACS = dict()
+ # cls.MY_IP4S = dict()
+ cls.create_host_lists(cls.hosts_nr)
+
+ # setup all interfaces
+ for i in cls.interfaces:
+ i.admin_up()
+ i.config_ip4()
+ i.resolve_arp()
+
+ cls.vxlan = cls.vapi.vxlan_add_del_tunnel(
+ src_addr=cls.pg2.local_ip4n,
+ dst_addr=cls.pg2.remote_ip4n,
+ vni=1111,
+ is_add=1)
+
+ def setUp(self):
+ super(TestSpan, self).setUp()
+ self.reset_packet_infos()
+
+ def tearDown(self):
+ super(TestSpan, self).tearDown()
+ if not self.vpp_dead:
+ self.logger.info(self.vapi.ppcli("show interface span"))
+
+ def xconnect(self, a, b, is_add=1):
+ self.vapi.sw_interface_set_l2_xconnect(a, b, enable=is_add)
+ self.vapi.sw_interface_set_l2_xconnect(b, a, enable=is_add)
+
+ def bridge(self, sw_if_index, is_add=1):
+ self.vapi.sw_interface_set_l2_bridge(
+ sw_if_index, bd_id=self.bd_id, enable=is_add)
+
+ def _remove_tag(self, packet, vlan, tag_type):
+ self.assertEqual(packet.type, tag_type)
+ payload = packet.payload
+ self.assertEqual(payload.vlan, vlan)
+ inner_type = payload.type
+ payload = payload.payload
+ packet.remove_payload()
+ packet.add_payload(payload)
+ packet.type = inner_type
+
+ def remove_tags(self, packet, tags):
+ for t in tags:
+ self._remove_tag(packet, t.vlan, t.dot1)
+ return packet
+
+ def decap_gre(self, pkt):
+ """
+ Decapsulate the original payload frame by removing GRE header
+ """
+ self.assertEqual(pkt[Ether].src, self.pg2.local_mac)
+ self.assertEqual(pkt[Ether].dst, self.pg2.remote_mac)
+
+ self.assertEqual(pkt[IP].src, self.pg2.local_ip4)
+ self.assertEqual(pkt[IP].dst, self.pg2.remote_ip4)
+
+ return pkt[GRE].payload
+
+ def decap_vxlan(self, pkt):
+ """
+ Decapsulate the original payload frame by removing VXLAN header
+ """
+ self.assertEqual(pkt[Ether].src, self.pg2.local_mac)
+ self.assertEqual(pkt[Ether].dst, self.pg2.remote_mac)
+
+ self.assertEqual(pkt[IP].src, self.pg2.local_ip4)
+ self.assertEqual(pkt[IP].dst, self.pg2.remote_ip4)
+
+ return pkt[VXLAN].payload
+
+ @classmethod
+ def create_host_lists(self, count):
+ """ Method to create required number of MAC and IPv4 addresses.
+ Create required number of host MAC addresses and distribute them among
+ interfaces. Create host IPv4 address for every host MAC address too.
+
+ :param count: Number of hosts to create MAC and IPv4 addresses for.
+ """
+ # mapping between packet-generator index and lists of test hosts
+ self.hosts_by_pg_idx = dict()
+
+ for pg_if in self.pg_interfaces:
+ # self.MY_MACS[i.sw_if_index] = []
+ # self.MY_IP4S[i.sw_if_index] = []
+ self.hosts_by_pg_idx[pg_if.sw_if_index] = []
+ hosts = self.hosts_by_pg_idx[pg_if.sw_if_index]
+ for j in range(0, count):
+ host = Host(
+ "00:00:00:ff:%02x:%02x" % (pg_if.sw_if_index, j),
+ "172.17.1%02x.%u" % (pg_if.sw_if_index, j))
+ hosts.append(host)
+
+ def create_stream(self, src_if, packet_sizes, do_dot1=False):
+ pkts = []
+ for i in range(0, self.pkts_per_burst):
+ dst_if = self.flows[src_if][0]
+ pkt_info = self.create_packet_info(src_if, dst_if)
+ payload = self.info_to_payload(pkt_info)
+ p = (Ether(dst=src_if.local_mac, src=src_if.remote_mac) /
+ IP(src=src_if.remote_ip4, dst=dst_if.remote_ip4) /
+ UDP(sport=1234, dport=1234) /
+ Raw(payload))
+ if do_dot1:
+ p = self.sub_if.add_dot1_layer(p)
+ pkt_info.data = p.copy()
+ size = packet_sizes[(i / 2) % len(packet_sizes)]
+ self.extend_packet(p, size)
+ pkts.append(p)
+ return pkts
+
+ def verify_capture(self, dst_if, capture_pg1, capture_pg2):
+ last_info = dict()
+ for i in self.interfaces:
+ last_info[i.sw_if_index] = None
+ dst_sw_if_index = dst_if.sw_if_index
+ self.assertEqual(
+ len(capture_pg1),
+ len(capture_pg2),
+ "Different number of outgoing and mirrored packets : %u != %u" %
+ (len(capture_pg1),
+ len(capture_pg2)))
+ for pkt_pg1, pkt_pg2 in zip(capture_pg1, capture_pg2):
+ try:
+ ip1 = pkt_pg1[IP]
+ udp1 = pkt_pg1[UDP]
+ raw1 = pkt_pg1[Raw]
+
+ if pkt_pg1[Ether] != pkt_pg2[Ether]:
+ self.logger.error("Different ethernet header of "
+ "outgoing and mirrored packet")
+ raise
+ if ip1 != pkt_pg2[IP]:
+ self.logger.error(
+ "Different ip header of outgoing and mirrored packet")
+ raise
+ if udp1 != pkt_pg2[UDP]:
+ self.logger.error(
+ "Different udp header of outgoing and mirrored packet")
+ raise
+ if raw1 != pkt_pg2[Raw]:
+ self.logger.error(
+ "Different raw data of outgoing and mirrored packet")
+ raise
+
+ payload_info = self.payload_to_info(str(raw1))
+ packet_index = payload_info.index
+ self.assertEqual(payload_info.dst, dst_sw_if_index)
+ self.logger.debug(
+ "Got packet on port %s: src=%u (id=%u)" %
+ (dst_if.name, payload_info.src, packet_index))
+ next_info = self.get_next_packet_info_for_interface2(
+ payload_info.src, dst_sw_if_index,
+ last_info[payload_info.src])
+ last_info[payload_info.src] = next_info
+ self.assertTrue(next_info is not None)
+ self.assertEqual(packet_index, next_info.index)
+ saved_packet = next_info.data
+ # Check standard fields
+ self.assertEqual(ip1.src, saved_packet[IP].src)
+ self.assertEqual(ip1.dst, saved_packet[IP].dst)
+ self.assertEqual(udp1.sport, saved_packet[UDP].sport)
+ self.assertEqual(udp1.dport, saved_packet[UDP].dport)
+ except:
+ self.logger.error("Unexpected or invalid packets:")
+ self.logger.error(ppp("pg1 packet:", pkt_pg1))
+ self.logger.error(ppp("pg2 packet:", pkt_pg2))
+ raise
+ for i in self.interfaces:
+ remaining_packet = self.get_next_packet_info_for_interface2(
+ i, dst_sw_if_index, last_info[i.sw_if_index])
+ self.assertTrue(remaining_packet is None,
+ "Port %u: Packet expected from source %u didn't"
+ " arrive" % (dst_sw_if_index, i.sw_if_index))
+
+ def test_device_span(self):
+ """ SPAN device rx mirror test
+
+ Test scenario:
+ 1. config
+ 3 interfaces, pg0 l2xconnected with pg1
+ 2. sending l2 eth packets between 2 interfaces (pg0, pg1) and
+ mirrored to pg2
+ 64B, 512B, 1518B, 9018B (ether_size)
+ burst of packets per interface
+ """
+
+ # Create bi-directional cross-connects between pg0 and pg1
+ self.xconnect(self.pg0.sw_if_index, self.pg1.sw_if_index)
+ # Create incoming packet streams for packet-generator interfaces
+ pkts = self.create_stream(self.pg0, self.pg_if_packet_sizes)
+ self.pg0.add_stream(pkts)
+
+ # Enable SPAN on pg0 (mirrored to pg2)
+ self.vapi.sw_interface_span_enable_disable(
+ self.pg0.sw_if_index, self.pg2.sw_if_index)
+
+ self.logger.info(self.vapi.ppcli("show interface span"))
+ # Enable packet capturing and start packet sending
+ self.pg_enable_capture(self.pg_interfaces)
+ self.pg_start()
+
+ # Verify packets outgoing packet streams on mirrored interface (pg2)
+ self.logger.info("Verifying capture on interfaces %s and %s" %
+ (self.pg1.name, self.pg2.name))
+ pg2_expected = self.get_packet_count_for_if_idx(self.pg1.sw_if_index)
+ self.verify_capture(
+ self.pg1,
+ self.pg1.get_capture(),
+ self.pg2.get_capture(pg2_expected))
+
+ # Disable SPAN on pg0 (mirrored to pg2)
+ self.vapi.sw_interface_span_enable_disable(
+ self.pg0.sw_if_index, self.pg2.sw_if_index, state=0)
+ self.xconnect(self.pg0.sw_if_index, self.pg1.sw_if_index, is_add=0)
+
+ def test_span_l2_rx(self):
+ """ SPAN l2 rx mirror test """
+
+ self.sub_if.admin_up()
+
+ self.bridge(self.pg2.sw_if_index)
+ # Create bi-directional cross-connects between pg0 and pg1
+ self.xconnect(self.sub_if.sw_if_index, self.pg1.sw_if_index)
+ # Create incoming packet streams for packet-generator interfaces
+ pkts = self.create_stream(
+ self.pg0, self.pg_if_packet_sizes, do_dot1=True)
+ self.pg0.add_stream(pkts)
+
+ # Enable SPAN on pg0 (mirrored to pg2)
+ self.vapi.sw_interface_span_enable_disable(
+ self.sub_if.sw_if_index, self.pg2.sw_if_index, is_l2=1)
+
+ self.logger.info(self.vapi.ppcli("show interface span"))
+ # Enable packet capturing and start packet sending
+ self.pg_enable_capture(self.pg_interfaces)
+ self.pg_start()
+
+ # Verify packets outgoing packet streams on mirrored interface (pg2)
+ self.logger.info("Verifying capture on interfaces %s and %s" %
+ (self.pg1.name, self.pg2.name))
+ pg2_expected = self.get_packet_count_for_if_idx(self.pg1.sw_if_index)
+ pg1_pkts = self.pg1.get_capture()
+ pg2_pkts = self.pg2.get_capture(pg2_expected)
+ self.verify_capture(
+ self.pg1,
+ pg1_pkts,
+ pg2_pkts)
+
+ self.bridge(self.pg2.sw_if_index, is_add=0)
+ # Disable SPAN on pg0 (mirrored to pg2)
+ self.vapi.sw_interface_span_enable_disable(
+ self.sub_if.sw_if_index, self.pg2.sw_if_index, state=0, is_l2=1)
+ self.xconnect(self.sub_if.sw_if_index, self.pg1.sw_if_index, is_add=0)
+
+ def test_span_l2_rx_dst_vxlan(self):
+ """ SPAN l2 rx mirror into vxlan test """
+
+ self.sub_if.admin_up()
+ self.vapi.sw_interface_set_flags(self.vxlan.sw_if_index,
+ admin_up_down=1)
+
+ self.bridge(self.vxlan.sw_if_index, is_add=1)
+ # Create bi-directional cross-connects between pg0 and pg1
+ self.xconnect(self.sub_if.sw_if_index, self.pg1.sw_if_index)
+ # Create incoming packet streams for packet-generator interfaces
+ pkts = self.create_stream(
+ self.pg0, self.pg_if_packet_sizes, do_dot1=True)
+ self.pg0.add_stream(pkts)
+
+ # Enable SPAN on pg0 sub if (mirrored to vxlan)
+ self.vapi.sw_interface_span_enable_disable(
+ self.sub_if.sw_if_index, self.vxlan.sw_if_index, is_l2=1)
+
+ self.logger.info(self.vapi.ppcli("show interface span"))
+ # Enable packet capturing and start packet sending
+ self.pg_enable_capture(self.pg_interfaces)
+ self.pg_start()
+
+ # Verify packets outgoing packet streams on mirrored interface (pg2)
+ self.logger.info("Verifying capture on interfaces %s and %s" %
+ (self.pg1.name, self.pg2.name))
+ pg2_expected = self.get_packet_count_for_if_idx(self.pg1.sw_if_index)
+ pg1_pkts = self.pg1.get_capture()
+ pg2_pkts = [self.decap_vxlan(p)
+ for p in self.pg2.get_capture(pg2_expected)]
+ self.verify_capture(
+ self.pg1,
+ pg1_pkts,
+ pg2_pkts)
+
+ self.bridge(self.vxlan.sw_if_index, is_add=0)
+ # Disable SPAN on pg0 sub if (mirrored to vxlan)
+ self.vapi.sw_interface_span_enable_disable(
+ self.sub_if.sw_if_index, self.vxlan.sw_if_index, state=0, is_l2=1)
+ self.xconnect(self.sub_if.sw_if_index, self.pg1.sw_if_index, is_add=0)
+
+ def test_span_l2_rx_dst_gre_subif_vtr(self):
+ """ SPAN l2 rx mirror into gre-subif+vtr """
+
+ self.sub_if.admin_up()
+
+ gre_if = VppGreInterface(self, self.pg2.local_ip4,
+ self.pg2.remote_ip4,
+ is_teb=1)
+
+ gre_if.add_vpp_config()
+ gre_if.admin_up()
+
+ gre_sub_if = VppDot1QSubint(self, gre_if, 500)
+ gre_sub_if.set_vtr(L2_VTR_OP.L2_POP_1, tag=500)
+ gre_sub_if.admin_up()
+
+ self.bridge(gre_sub_if.sw_if_index)
+ # Create bi-directional cross-connects between pg0 and pg1
+ self.xconnect(self.sub_if.sw_if_index, self.pg1.sw_if_index, is_add=1)
+
+ # Create incoming packet streams for packet-generator interfaces
+ pkts = self.create_stream(
+ self.pg0, self.pg_if_packet_sizes, do_dot1=True)
+ self.pg0.add_stream(pkts)
+
+ self.vapi.sw_interface_span_enable_disable(
+ self.sub_if.sw_if_index, gre_sub_if.sw_if_index, is_l2=1)
+
+ # Enable packet capturing and start packet sending
+ self.pg_enable_capture(self.pg_interfaces)
+ self.pg_start()
+
+ # Verify packets outgoing packet streams on mirrored interface (pg2)
+ self.logger.info("Verifying capture on interfaces %s and %s" %
+ (self.pg1.name, self.pg2.name))
+ pg2_expected = self.get_packet_count_for_if_idx(self.pg1.sw_if_index)
+ pg1_pkts = self.pg1.get_capture()
+ pg2_pkts = self.pg2.get_capture(pg2_expected)
+ pg2_decaped = [self.remove_tags(self.decap_gre(
+ p), [Tag(dot1=DOT1Q, vlan=500)]) for p in pg2_pkts]
+ self.verify_capture(
+ self.pg1,
+ pg1_pkts,
+ pg2_decaped)
+
+ self.bridge(gre_sub_if.sw_if_index, is_add=0)
+ # Disable SPAN on pg0 sub if
+ self.vapi.sw_interface_span_enable_disable(
+ self.sub_if.sw_if_index, gre_sub_if.sw_if_index, state=0,
+ is_l2=1)
+ gre_if.remove_vpp_config()
+ self.xconnect(self.sub_if.sw_if_index, self.pg1.sw_if_index, is_add=0)
+
+ def test_span_l2_rx_dst_vtr(self):
+ """ SPAN l2 rx mirror into subif+vtr """
+
+ self.sub_if.admin_up()
+ self.dst_sub_if.admin_up()
+
+ self.bridge(self.dst_sub_if.sw_if_index)
+ # Create bi-directional cross-connects between pg0 and pg1
+ self.xconnect(self.sub_if.sw_if_index, self.pg1.sw_if_index, is_add=1)
+
+ # Create incoming packet streams for packet-generator interfaces
+ pkts = self.create_stream(
+ self.pg0, self.pg_if_packet_sizes, do_dot1=True)
+ self.pg0.add_stream(pkts)
+
+ self.vapi.sw_interface_span_enable_disable(
+ self.sub_if.sw_if_index, self.dst_sub_if.sw_if_index, is_l2=1)
+
+ # Enable packet capturing and start packet sending
+ self.pg_enable_capture(self.pg_interfaces)
+ self.pg_start()
+
+ # Verify packets outgoing packet streams on mirrored interface (pg2)
+ self.logger.info("Verifying capture on interfaces %s and %s" %
+ (self.pg1.name, self.pg2.name))
+ pg2_expected = self.get_packet_count_for_if_idx(self.pg1.sw_if_index)
+ pg1_pkts = self.pg1.get_capture()
+ pg2_pkts = self.pg2.get_capture(pg2_expected)
+ pg2_untagged = [self.remove_tags(p, [Tag(dot1=DOT1Q, vlan=300)])
+ for p in pg2_pkts]
+ self.verify_capture(
+ self.pg1,
+ pg1_pkts,
+ pg2_untagged)
+
+ self.bridge(self.dst_sub_if.sw_if_index, is_add=0)
+ # Disable SPAN on pg0 sub if (mirrored to vxlan)
+ self.vapi.sw_interface_span_enable_disable(
+ self.sub_if.sw_if_index, self.dst_sub_if.sw_if_index, state=0,
+ is_l2=1)
+ self.xconnect(self.sub_if.sw_if_index, self.pg1.sw_if_index, is_add=0)
+
+ def test_l2_tx_span(self):
+ """ SPAN l2 tx mirror test """
+
+ self.sub_if.admin_up()
+ self.bridge(self.pg2.sw_if_index)
+ # Create bi-directional cross-connects between pg0 and pg1
+ self.xconnect(self.sub_if.sw_if_index, self.pg1.sw_if_index)
+ # Create incoming packet streams for packet-generator interfaces
+ pkts = self.create_stream(
+ self.pg0, self.pg_if_packet_sizes, do_dot1=True)
+ self.pg0.add_stream(pkts)
+
+ # Enable SPAN on pg0 (mirrored to pg2)
+ self.vapi.sw_interface_span_enable_disable(
+ self.pg1.sw_if_index, self.pg2.sw_if_index, is_l2=1, state=2)
+
+ self.logger.info(self.vapi.ppcli("show interface span"))
+ # Enable packet capturing and start packet sending
+ self.pg_enable_capture(self.pg_interfaces)
+ self.pg_start()
+
+ # Verify packets outgoing packet streams on mirrored interface (pg2)
+ self.logger.info("Verifying capture on interfaces %s and %s" %
+ (self.pg1.name, self.pg2.name))
+ pg2_expected = self.get_packet_count_for_if_idx(self.pg1.sw_if_index)
+ pg1_pkts = self.pg1.get_capture()
+ pg2_pkts = self.pg2.get_capture(pg2_expected)
+ self.verify_capture(
+ self.pg1,
+ pg1_pkts,
+ pg2_pkts)
+
+ self.bridge(self.pg2.sw_if_index, is_add=0)
+ # Disable SPAN on pg0 (mirrored to pg2)
+ self.vapi.sw_interface_span_enable_disable(
+ self.pg1.sw_if_index, self.pg2.sw_if_index, state=0, is_l2=1)
+ self.xconnect(self.sub_if.sw_if_index, self.pg1.sw_if_index, is_add=0)
+
+
+if __name__ == '__main__':
+ unittest.main(testRunner=VppTestRunner)
diff --git a/test/test_srv6.py b/test/test_srv6.py
new file mode 100644
index 00000000..a31b30eb
--- /dev/null
+++ b/test/test_srv6.py
@@ -0,0 +1,1997 @@
+#!/usr/bin/env python
+
+import unittest
+from socket import AF_INET6
+
+from framework import VppTestCase, VppTestRunner
+from vpp_ip_route import VppIpRoute, VppRoutePath, DpoProto
+from vpp_srv6 import SRv6LocalSIDBehaviors, VppSRv6LocalSID, VppSRv6Policy, \
+ SRv6PolicyType, VppSRv6Steering, SRv6PolicySteeringTypes
+
+from scapy.packet import Raw
+from scapy.layers.l2 import Ether, Dot1Q
+from scapy.layers.inet6 import IPv6, UDP, IPv6ExtHdrSegmentRouting
+from scapy.layers.inet import IP, UDP
+
+from scapy.utils import inet_pton, inet_ntop
+
+from util import ppp
+
+
+class TestSRv6(VppTestCase):
+ """ SRv6 Test Case """
+
+ @classmethod
+ def setUpClass(self):
+ super(TestSRv6, self).setUpClass()
+
+ def setUp(self):
+ """ Perform test setup before each test case.
+ """
+ super(TestSRv6, self).setUp()
+
+ # packet sizes, inclusive L2 overhead
+ self.pg_packet_sizes = [64, 512, 1518, 9018]
+
+ # reset packet_infos
+ self.reset_packet_infos()
+
+ def tearDown(self):
+ """ Clean up test setup after each test case.
+ """
+ self.teardown_interfaces()
+
+ super(TestSRv6, self).tearDown()
+
+ def configure_interface(self,
+ interface,
+ ipv6=False, ipv4=False,
+ ipv6_table_id=0, ipv4_table_id=0):
+ """ Configure interface.
+ :param ipv6: configure IPv6 on interface
+ :param ipv4: configure IPv4 on interface
+ :param ipv6_table_id: FIB table_id for IPv6
+ :param ipv4_table_id: FIB table_id for IPv4
+ """
+ self.logger.debug("Configuring interface %s" % (interface.name))
+ if ipv6:
+ self.logger.debug("Configuring IPv6")
+ interface.set_table_ip6(ipv6_table_id)
+ interface.config_ip6()
+ interface.resolve_ndp(timeout=5)
+ if ipv4:
+ self.logger.debug("Configuring IPv4")
+ interface.set_table_ip4(ipv4_table_id)
+ interface.config_ip4()
+ interface.resolve_arp()
+ interface.admin_up()
+
+ def setup_interfaces(self, ipv6=[], ipv4=[],
+ ipv6_table_id=[], ipv4_table_id=[]):
+ """ Create and configure interfaces.
+
+ :param ipv6: list of interface IPv6 capabilities
+ :param ipv4: list of interface IPv4 capabilities
+ :param ipv6_table_id: list of intf IPv6 FIB table_ids
+ :param ipv4_table_id: list of intf IPv4 FIB table_ids
+ :returns: List of created interfaces.
+ """
+ # how many interfaces?
+ if len(ipv6):
+ count = len(ipv6)
+ else:
+ count = len(ipv4)
+ self.logger.debug("Creating and configuring %d interfaces" % (count))
+
+ # fill up ipv6 and ipv4 lists if needed
+ # not enabled (False) is the default
+ if len(ipv6) < count:
+ ipv6 += (count - len(ipv6)) * [False]
+ if len(ipv4) < count:
+ ipv4 += (count - len(ipv4)) * [False]
+
+ # fill up table_id lists if needed
+ # table_id 0 (global) is the default
+ if len(ipv6_table_id) < count:
+ ipv6_table_id += (count - len(ipv6_table_id)) * [0]
+ if len(ipv4_table_id) < count:
+ ipv4_table_id += (count - len(ipv4_table_id)) * [0]
+
+ # create 'count' pg interfaces
+ self.create_pg_interfaces(range(count))
+
+ # setup all interfaces
+ for i in range(count):
+ intf = self.pg_interfaces[i]
+ self.configure_interface(intf,
+ ipv6[i], ipv4[i],
+ ipv6_table_id[i], ipv4_table_id[i])
+
+ if any(ipv6):
+ self.logger.debug(self.vapi.cli("show ip6 neighbors"))
+ if any(ipv4):
+ self.logger.debug(self.vapi.cli("show ip arp"))
+ self.logger.debug(self.vapi.cli("show interface"))
+ self.logger.debug(self.vapi.cli("show hardware"))
+
+ return self.pg_interfaces
+
+ def teardown_interfaces(self):
+ """ Unconfigure and bring down interface.
+ """
+ self.logger.debug("Tearing down interfaces")
+ # tear down all interfaces
+ # AFAIK they cannot be deleted
+ for i in self.pg_interfaces:
+ self.logger.debug("Tear down interface %s" % (i.name))
+ i.admin_down()
+ i.unconfig()
+
+ def test_SRv6_T_Encaps(self):
+ """ Test SRv6 Transit.Encaps behavior for IPv6.
+ """
+ # send traffic to one destination interface
+ # source and destination are IPv6 only
+ self.setup_interfaces(ipv6=[True, True])
+
+ # configure FIB entries
+ route = VppIpRoute(self, "a4::", 64,
+ [VppRoutePath(self.pg1.remote_ip6,
+ self.pg1.sw_if_index,
+ proto=DpoProto.DPO_PROTO_IP6)],
+ is_ip6=1)
+ route.add_vpp_config()
+
+ # configure encaps IPv6 source address
+ # needs to be done before SR Policy config
+ # TODO: API?
+ self.vapi.cli("set sr encaps source addr a3::")
+
+ bsid = 'a3::9999:1'
+ # configure SRv6 Policy
+ # Note: segment list order: first -> last
+ sr_policy = VppSRv6Policy(
+ self, bsid=bsid,
+ is_encap=1,
+ sr_type=SRv6PolicyType.SR_POLICY_TYPE_DEFAULT,
+ weight=1, fib_table=0,
+ segments=['a4::', 'a5::', 'a6::c7'],
+ source='a3::')
+ sr_policy.add_vpp_config()
+ self.sr_policy = sr_policy
+
+ # log the sr policies
+ self.logger.info(self.vapi.cli("show sr policies"))
+
+ # steer IPv6 traffic to a7::/64 into SRv6 Policy
+ # use the bsid of the above self.sr_policy
+ pol_steering = VppSRv6Steering(
+ self,
+ bsid=self.sr_policy.bsid,
+ prefix="a7::", mask_width=64,
+ traffic_type=SRv6PolicySteeringTypes.SR_STEER_IPV6,
+ sr_policy_index=0, table_id=0,
+ sw_if_index=0)
+ pol_steering.add_vpp_config()
+
+ # log the sr steering policies
+ self.logger.info(self.vapi.cli("show sr steering policies"))
+
+ # create packets
+ count = len(self.pg_packet_sizes)
+ dst_inner = 'a7::1234'
+ pkts = []
+
+ # create IPv6 packets without SRH
+ packet_header = self.create_packet_header_IPv6(dst_inner)
+ # create traffic stream pg0->pg1
+ pkts.extend(self.create_stream(self.pg0, self.pg1, packet_header,
+ self.pg_packet_sizes, count))
+
+ # create IPv6 packets with SRH
+ # packets with segments-left 1, active segment a7::
+ packet_header = self.create_packet_header_IPv6_SRH(
+ sidlist=['a8::', 'a7::', 'a6::'],
+ segleft=1)
+ # create traffic stream pg0->pg1
+ pkts.extend(self.create_stream(self.pg0, self.pg1, packet_header,
+ self.pg_packet_sizes, count))
+
+ # create IPv6 packets with SRH and IPv6
+ # packets with segments-left 1, active segment a7::
+ packet_header = self.create_packet_header_IPv6_SRH_IPv6(
+ dst_inner,
+ sidlist=['a8::', 'a7::', 'a6::'],
+ segleft=1)
+ # create traffic stream pg0->pg1
+ pkts.extend(self.create_stream(self.pg0, self.pg1, packet_header,
+ self.pg_packet_sizes, count))
+
+ # send packets and verify received packets
+ self.send_and_verify_pkts(self.pg0, pkts, self.pg1,
+ self.compare_rx_tx_packet_T_Encaps)
+
+ # log the localsid counters
+ self.logger.info(self.vapi.cli("show sr localsid"))
+
+ # remove SR steering
+ pol_steering.remove_vpp_config()
+ self.logger.info(self.vapi.cli("show sr steering policies"))
+
+ # remove SR Policies
+ self.sr_policy.remove_vpp_config()
+ self.logger.info(self.vapi.cli("show sr policies"))
+
+ # remove FIB entries
+ # done by tearDown
+
+ # cleanup interfaces
+ self.teardown_interfaces()
+
+ def test_SRv6_T_Insert(self):
+ """ Test SRv6 Transit.Insert behavior (IPv6 only).
+ """
+ # send traffic to one destination interface
+ # source and destination are IPv6 only
+ self.setup_interfaces(ipv6=[True, True])
+
+ # configure FIB entries
+ route = VppIpRoute(self, "a4::", 64,
+ [VppRoutePath(self.pg1.remote_ip6,
+ self.pg1.sw_if_index,
+ proto=DpoProto.DPO_PROTO_IP6)],
+ is_ip6=1)
+ route.add_vpp_config()
+
+ # configure encaps IPv6 source address
+ # needs to be done before SR Policy config
+ # TODO: API?
+ self.vapi.cli("set sr encaps source addr a3::")
+
+ bsid = 'a3::9999:1'
+ # configure SRv6 Policy
+ # Note: segment list order: first -> last
+ sr_policy = VppSRv6Policy(
+ self, bsid=bsid,
+ is_encap=0,
+ sr_type=SRv6PolicyType.SR_POLICY_TYPE_DEFAULT,
+ weight=1, fib_table=0,
+ segments=['a4::', 'a5::', 'a6::c7'],
+ source='a3::')
+ sr_policy.add_vpp_config()
+ self.sr_policy = sr_policy
+
+ # log the sr policies
+ self.logger.info(self.vapi.cli("show sr policies"))
+
+ # steer IPv6 traffic to a7::/64 into SRv6 Policy
+ # use the bsid of the above self.sr_policy
+ pol_steering = VppSRv6Steering(
+ self,
+ bsid=self.sr_policy.bsid,
+ prefix="a7::", mask_width=64,
+ traffic_type=SRv6PolicySteeringTypes.SR_STEER_IPV6,
+ sr_policy_index=0, table_id=0,
+ sw_if_index=0)
+ pol_steering.add_vpp_config()
+
+ # log the sr steering policies
+ self.logger.info(self.vapi.cli("show sr steering policies"))
+
+ # create packets
+ count = len(self.pg_packet_sizes)
+ dst_inner = 'a7::1234'
+ pkts = []
+
+ # create IPv6 packets without SRH
+ packet_header = self.create_packet_header_IPv6(dst_inner)
+ # create traffic stream pg0->pg1
+ pkts.extend(self.create_stream(self.pg0, self.pg1, packet_header,
+ self.pg_packet_sizes, count))
+
+ # create IPv6 packets with SRH
+ # packets with segments-left 1, active segment a7::
+ packet_header = self.create_packet_header_IPv6_SRH(
+ sidlist=['a8::', 'a7::', 'a6::'],
+ segleft=1)
+ # create traffic stream pg0->pg1
+ pkts.extend(self.create_stream(self.pg0, self.pg1, packet_header,
+ self.pg_packet_sizes, count))
+
+ # send packets and verify received packets
+ self.send_and_verify_pkts(self.pg0, pkts, self.pg1,
+ self.compare_rx_tx_packet_T_Insert)
+
+ # log the localsid counters
+ self.logger.info(self.vapi.cli("show sr localsid"))
+
+ # remove SR steering
+ pol_steering.remove_vpp_config()
+ self.logger.info(self.vapi.cli("show sr steering policies"))
+
+ # remove SR Policies
+ self.sr_policy.remove_vpp_config()
+ self.logger.info(self.vapi.cli("show sr policies"))
+
+ # remove FIB entries
+ # done by tearDown
+
+ # cleanup interfaces
+ self.teardown_interfaces()
+
+ def test_SRv6_T_Encaps_IPv4(self):
+ """ Test SRv6 Transit.Encaps behavior for IPv4.
+ """
+ # send traffic to one destination interface
+ # source interface is IPv4 only
+ # destination interface is IPv6 only
+ self.setup_interfaces(ipv6=[False, True], ipv4=[True, False])
+
+ # configure FIB entries
+ route = VppIpRoute(self, "a4::", 64,
+ [VppRoutePath(self.pg1.remote_ip6,
+ self.pg1.sw_if_index,
+ proto=DpoProto.DPO_PROTO_IP6)],
+ is_ip6=1)
+ route.add_vpp_config()
+
+ # configure encaps IPv6 source address
+ # needs to be done before SR Policy config
+ # TODO: API?
+ self.vapi.cli("set sr encaps source addr a3::")
+
+ bsid = 'a3::9999:1'
+ # configure SRv6 Policy
+ # Note: segment list order: first -> last
+ sr_policy = VppSRv6Policy(
+ self, bsid=bsid,
+ is_encap=1,
+ sr_type=SRv6PolicyType.SR_POLICY_TYPE_DEFAULT,
+ weight=1, fib_table=0,
+ segments=['a4::', 'a5::', 'a6::c7'],
+ source='a3::')
+ sr_policy.add_vpp_config()
+ self.sr_policy = sr_policy
+
+ # log the sr policies
+ self.logger.info(self.vapi.cli("show sr policies"))
+
+ # steer IPv4 traffic to 7.1.1.0/24 into SRv6 Policy
+ # use the bsid of the above self.sr_policy
+ pol_steering = VppSRv6Steering(
+ self,
+ bsid=self.sr_policy.bsid,
+ prefix="7.1.1.0", mask_width=24,
+ traffic_type=SRv6PolicySteeringTypes.SR_STEER_IPV4,
+ sr_policy_index=0, table_id=0,
+ sw_if_index=0)
+ pol_steering.add_vpp_config()
+
+ # log the sr steering policies
+ self.logger.info(self.vapi.cli("show sr steering policies"))
+
+ # create packets
+ count = len(self.pg_packet_sizes)
+ dst_inner = '7.1.1.123'
+ pkts = []
+
+ # create IPv4 packets
+ packet_header = self.create_packet_header_IPv4(dst_inner)
+ # create traffic stream pg0->pg1
+ pkts.extend(self.create_stream(self.pg0, self.pg1, packet_header,
+ self.pg_packet_sizes, count))
+
+ # send packets and verify received packets
+ self.send_and_verify_pkts(self.pg0, pkts, self.pg1,
+ self.compare_rx_tx_packet_T_Encaps_IPv4)
+
+ # log the localsid counters
+ self.logger.info(self.vapi.cli("show sr localsid"))
+
+ # remove SR steering
+ pol_steering.remove_vpp_config()
+ self.logger.info(self.vapi.cli("show sr steering policies"))
+
+ # remove SR Policies
+ self.sr_policy.remove_vpp_config()
+ self.logger.info(self.vapi.cli("show sr policies"))
+
+ # remove FIB entries
+ # done by tearDown
+
+ # cleanup interfaces
+ self.teardown_interfaces()
+
+ @unittest.skip("VPP crashes after running this test")
+ def test_SRv6_T_Encaps_L2(self):
+ """ Test SRv6 Transit.Encaps behavior for L2.
+ """
+ # send traffic to one destination interface
+ # source interface is IPv4 only TODO?
+ # destination interface is IPv6 only
+ self.setup_interfaces(ipv6=[False, True], ipv4=[False, False])
+
+ # configure FIB entries
+ route = VppIpRoute(self, "a4::", 64,
+ [VppRoutePath(self.pg1.remote_ip6,
+ self.pg1.sw_if_index,
+ proto=DpoProto.DPO_PROTO_IP6)],
+ is_ip6=1)
+ route.add_vpp_config()
+
+ # configure encaps IPv6 source address
+ # needs to be done before SR Policy config
+ # TODO: API?
+ self.vapi.cli("set sr encaps source addr a3::")
+
+ bsid = 'a3::9999:1'
+ # configure SRv6 Policy
+ # Note: segment list order: first -> last
+ sr_policy = VppSRv6Policy(
+ self, bsid=bsid,
+ is_encap=1,
+ sr_type=SRv6PolicyType.SR_POLICY_TYPE_DEFAULT,
+ weight=1, fib_table=0,
+ segments=['a4::', 'a5::', 'a6::c7'],
+ source='a3::')
+ sr_policy.add_vpp_config()
+ self.sr_policy = sr_policy
+
+ # log the sr policies
+ self.logger.info(self.vapi.cli("show sr policies"))
+
+ # steer L2 traffic into SRv6 Policy
+ # use the bsid of the above self.sr_policy
+ pol_steering = VppSRv6Steering(
+ self,
+ bsid=self.sr_policy.bsid,
+ prefix="::", mask_width=0,
+ traffic_type=SRv6PolicySteeringTypes.SR_STEER_L2,
+ sr_policy_index=0, table_id=0,
+ sw_if_index=self.pg0.sw_if_index)
+ pol_steering.add_vpp_config()
+
+ # log the sr steering policies
+ self.logger.info(self.vapi.cli("show sr steering policies"))
+
+ # create packets
+ count = len(self.pg_packet_sizes)
+ pkts = []
+
+ # create L2 packets without dot1q header
+ packet_header = self.create_packet_header_L2()
+ # create traffic stream pg0->pg1
+ pkts.extend(self.create_stream(self.pg0, self.pg1, packet_header,
+ self.pg_packet_sizes, count))
+
+ # create L2 packets with dot1q header
+ packet_header = self.create_packet_header_L2(vlan=123)
+ # create traffic stream pg0->pg1
+ pkts.extend(self.create_stream(self.pg0, self.pg1, packet_header,
+ self.pg_packet_sizes, count))
+
+ # send packets and verify received packets
+ self.send_and_verify_pkts(self.pg0, pkts, self.pg1,
+ self.compare_rx_tx_packet_T_Encaps_L2)
+
+ # log the localsid counters
+ self.logger.info(self.vapi.cli("show sr localsid"))
+
+ # remove SR steering
+ pol_steering.remove_vpp_config()
+ self.logger.info(self.vapi.cli("show sr steering policies"))
+
+ # remove SR Policies
+ self.sr_policy.remove_vpp_config()
+ self.logger.info(self.vapi.cli("show sr policies"))
+
+ # remove FIB entries
+ # done by tearDown
+
+ # cleanup interfaces
+ self.teardown_interfaces()
+
+ def test_SRv6_End(self):
+ """ Test SRv6 End (without PSP) behavior.
+ """
+ # send traffic to one destination interface
+ # source and destination interfaces are IPv6 only
+ self.setup_interfaces(ipv6=[True, True])
+
+ # configure FIB entries
+ route = VppIpRoute(self, "a4::", 64,
+ [VppRoutePath(self.pg1.remote_ip6,
+ self.pg1.sw_if_index,
+ proto=DpoProto.DPO_PROTO_IP6)],
+ is_ip6=1)
+ route.add_vpp_config()
+
+ # configure SRv6 localSID End without PSP behavior
+ localsid = VppSRv6LocalSID(
+ self, localsid_addr='A3::0',
+ behavior=SRv6LocalSIDBehaviors.SR_BEHAVIOR_END,
+ nh_addr='::',
+ end_psp=0,
+ sw_if_index=0,
+ vlan_index=0,
+ fib_table=0)
+ localsid.add_vpp_config()
+ # log the localsids
+ self.logger.debug(self.vapi.cli("show sr localsid"))
+
+ # create IPv6 packets with SRH (SL=2, SL=1, SL=0)
+ # send one packet per SL value per packet size
+ # SL=0 packet with localSID End with USP needs 2nd SRH
+ count = len(self.pg_packet_sizes)
+ dst_inner = 'a4::1234'
+ pkts = []
+
+ # packets with segments-left 2, active segment a3::
+ packet_header = self.create_packet_header_IPv6_SRH_IPv6(
+ dst_inner,
+ sidlist=['a5::', 'a4::', 'a3::'],
+ segleft=2)
+ # create traffic stream pg0->pg1
+ pkts.extend(self.create_stream(self.pg0, self.pg1, packet_header,
+ self.pg_packet_sizes, count))
+
+ # packets with segments-left 1, active segment a3::
+ packet_header = self.create_packet_header_IPv6_SRH_IPv6(
+ dst_inner,
+ sidlist=['a4::', 'a3::', 'a2::'],
+ segleft=1)
+ # add to traffic stream pg0->pg1
+ pkts.extend(self.create_stream(self.pg0, self.pg1, packet_header,
+ self.pg_packet_sizes, count))
+
+ # TODO: test behavior with SL=0 packet (needs 2*SRH?)
+
+ # send packets and verify received packets
+ self.send_and_verify_pkts(self.pg0, pkts, self.pg1,
+ self.compare_rx_tx_packet_End)
+
+ # log the localsid counters
+ self.logger.info(self.vapi.cli("show sr localsid"))
+
+ # remove SRv6 localSIDs
+ localsid.remove_vpp_config()
+
+ # remove FIB entries
+ # done by tearDown
+
+ # cleanup interfaces
+ self.teardown_interfaces()
+
+ def test_SRv6_End_with_PSP(self):
+ """ Test SRv6 End with PSP behavior.
+ """
+ # send traffic to one destination interface
+ # source and destination interfaces are IPv6 only
+ self.setup_interfaces(ipv6=[True, True])
+
+ # configure FIB entries
+ route = VppIpRoute(self, "a4::", 64,
+ [VppRoutePath(self.pg1.remote_ip6,
+ self.pg1.sw_if_index,
+ proto=DpoProto.DPO_PROTO_IP6)],
+ is_ip6=1)
+ route.add_vpp_config()
+
+ # configure SRv6 localSID End with PSP behavior
+ localsid = VppSRv6LocalSID(
+ self, localsid_addr='A3::0',
+ behavior=SRv6LocalSIDBehaviors.SR_BEHAVIOR_END,
+ nh_addr='::',
+ end_psp=1,
+ sw_if_index=0,
+ vlan_index=0,
+ fib_table=0)
+ localsid.add_vpp_config()
+ # log the localsids
+ self.logger.debug(self.vapi.cli("show sr localsid"))
+
+ # create IPv6 packets with SRH (SL=2, SL=1)
+ # send one packet per SL value per packet size
+ # SL=0 packet with localSID End with PSP is dropped
+ count = len(self.pg_packet_sizes)
+ dst_inner = 'a4::1234'
+ pkts = []
+
+ # packets with segments-left 2, active segment a3::
+ packet_header = self.create_packet_header_IPv6_SRH_IPv6(
+ dst_inner,
+ sidlist=['a5::', 'a4::', 'a3::'],
+ segleft=2)
+ # create traffic stream pg0->pg1
+ pkts.extend(self.create_stream(self.pg0, self.pg1, packet_header,
+ self.pg_packet_sizes, count))
+
+ # packets with segments-left 1, active segment a3::
+ packet_header = self.create_packet_header_IPv6_SRH_IPv6(
+ dst_inner,
+ sidlist=['a4::', 'a3::', 'a2::'],
+ segleft=1)
+ # add to traffic stream pg0->pg1
+ pkts.extend(self.create_stream(self.pg0, self.pg1, packet_header,
+ self.pg_packet_sizes, count))
+
+ # send packets and verify received packets
+ self.send_and_verify_pkts(self.pg0, pkts, self.pg1,
+ self.compare_rx_tx_packet_End_PSP)
+
+ # log the localsid counters
+ self.logger.info(self.vapi.cli("show sr localsid"))
+
+ # remove SRv6 localSIDs
+ localsid.remove_vpp_config()
+
+ # remove FIB entries
+ # done by tearDown
+
+ # cleanup interfaces
+ self.teardown_interfaces()
+
+ def test_SRv6_End_X(self):
+ """ Test SRv6 End.X (without PSP) behavior.
+ """
+ # create three interfaces (1 source, 2 destinations)
+ # source and destination interfaces are IPv6 only
+ self.setup_interfaces(ipv6=[True, True, True])
+
+ # configure FIB entries
+ # a4::/64 via pg1 and pg2
+ route = VppIpRoute(self, "a4::", 64,
+ [VppRoutePath(self.pg1.remote_ip6,
+ self.pg1.sw_if_index,
+ proto=DpoProto.DPO_PROTO_IP6),
+ VppRoutePath(self.pg2.remote_ip6,
+ self.pg2.sw_if_index,
+ proto=DpoProto.DPO_PROTO_IP6)],
+ is_ip6=1)
+ route.add_vpp_config()
+ self.logger.debug(self.vapi.cli("show ip6 fib"))
+
+ # configure SRv6 localSID End.X without PSP behavior
+ # End.X points to interface pg1
+ localsid = VppSRv6LocalSID(
+ self, localsid_addr='A3::C4',
+ behavior=SRv6LocalSIDBehaviors.SR_BEHAVIOR_X,
+ nh_addr=self.pg1.remote_ip6,
+ end_psp=0,
+ sw_if_index=self.pg1.sw_if_index,
+ vlan_index=0,
+ fib_table=0)
+ localsid.add_vpp_config()
+ # log the localsids
+ self.logger.debug(self.vapi.cli("show sr localsid"))
+
+ # create IPv6 packets with SRH (SL=2, SL=1)
+ # send one packet per SL value per packet size
+ # SL=0 packet with localSID End with PSP is dropped
+ count = len(self.pg_packet_sizes)
+ dst_inner = 'a4::1234'
+ pkts = []
+
+ # packets with segments-left 2, active segment a3::c4
+ packet_header = self.create_packet_header_IPv6_SRH_IPv6(
+ dst_inner,
+ sidlist=['a5::', 'a4::', 'a3::c4'],
+ segleft=2)
+ # create traffic stream pg0->pg1
+ pkts.extend(self.create_stream(self.pg0, self.pg1, packet_header,
+ self.pg_packet_sizes, count))
+
+ # packets with segments-left 1, active segment a3::c4
+ packet_header = self.create_packet_header_IPv6_SRH_IPv6(
+ dst_inner,
+ sidlist=['a4::', 'a3::c4', 'a2::'],
+ segleft=1)
+ # add to traffic stream pg0->pg1
+ pkts.extend(self.create_stream(self.pg0, self.pg1, packet_header,
+ self.pg_packet_sizes, count))
+
+ # send packets and verify received packets
+ # using same comparison function as End (no PSP)
+ self.send_and_verify_pkts(self.pg0, pkts, self.pg1,
+ self.compare_rx_tx_packet_End)
+
+ # assert nothing was received on the other interface (pg2)
+ self.pg2.assert_nothing_captured("mis-directed packet(s)")
+
+ # log the localsid counters
+ self.logger.info(self.vapi.cli("show sr localsid"))
+
+ # remove SRv6 localSIDs
+ localsid.remove_vpp_config()
+
+ # remove FIB entries
+ # done by tearDown
+
+ # cleanup interfaces
+ self.teardown_interfaces()
+
+ def test_SRv6_End_X_with_PSP(self):
+ """ Test SRv6 End.X with PSP behavior.
+ """
+ # create three interfaces (1 source, 2 destinations)
+ # source and destination interfaces are IPv6 only
+ self.setup_interfaces(ipv6=[True, True, True])
+
+ # configure FIB entries
+ # a4::/64 via pg1 and pg2
+ route = VppIpRoute(self, "a4::", 64,
+ [VppRoutePath(self.pg1.remote_ip6,
+ self.pg1.sw_if_index,
+ proto=DpoProto.DPO_PROTO_IP6),
+ VppRoutePath(self.pg2.remote_ip6,
+ self.pg2.sw_if_index,
+ proto=DpoProto.DPO_PROTO_IP6)],
+ is_ip6=1)
+ route.add_vpp_config()
+
+ # configure SRv6 localSID End with PSP behavior
+ localsid = VppSRv6LocalSID(
+ self, localsid_addr='A3::C4',
+ behavior=SRv6LocalSIDBehaviors.SR_BEHAVIOR_X,
+ nh_addr=self.pg1.remote_ip6,
+ end_psp=1,
+ sw_if_index=self.pg1.sw_if_index,
+ vlan_index=0,
+ fib_table=0)
+ localsid.add_vpp_config()
+ # log the localsids
+ self.logger.debug(self.vapi.cli("show sr localsid"))
+
+ # create IPv6 packets with SRH (SL=2, SL=1)
+ # send one packet per SL value per packet size
+ # SL=0 packet with localSID End with PSP is dropped
+ count = len(self.pg_packet_sizes)
+ dst_inner = 'a4::1234'
+ pkts = []
+
+ # packets with segments-left 2, active segment a3::
+ packet_header = self.create_packet_header_IPv6_SRH_IPv6(
+ dst_inner,
+ sidlist=['a5::', 'a4::', 'a3::c4'],
+ segleft=2)
+ # create traffic stream pg0->pg1
+ pkts.extend(self.create_stream(self.pg0, self.pg1, packet_header,
+ self.pg_packet_sizes, count))
+
+ # packets with segments-left 1, active segment a3::
+ packet_header = self.create_packet_header_IPv6_SRH_IPv6(
+ dst_inner,
+ sidlist=['a4::', 'a3::c4', 'a2::'],
+ segleft=1)
+ # add to traffic stream pg0->pg1
+ pkts.extend(self.create_stream(self.pg0, self.pg1, packet_header,
+ self.pg_packet_sizes, count))
+
+ # send packets and verify received packets
+ # using same comparison function as End with PSP
+ self.send_and_verify_pkts(self.pg0, pkts, self.pg1,
+ self.compare_rx_tx_packet_End_PSP)
+
+ # assert nothing was received on the other interface (pg2)
+ self.pg2.assert_nothing_captured("mis-directed packet(s)")
+
+ # log the localsid counters
+ self.logger.info(self.vapi.cli("show sr localsid"))
+
+ # remove SRv6 localSIDs
+ localsid.remove_vpp_config()
+
+ # remove FIB entries
+ # done by tearDown
+
+ # cleanup interfaces
+ self.teardown_interfaces()
+
+ def test_SRv6_End_DX6(self):
+ """ Test SRv6 End.DX6 behavior.
+ """
+ # send traffic to one destination interface
+ # source and destination interfaces are IPv6 only
+ self.setup_interfaces(ipv6=[True, True])
+
+ # configure SRv6 localSID End.DX6 behavior
+ localsid = VppSRv6LocalSID(
+ self, localsid_addr='a3::c4',
+ behavior=SRv6LocalSIDBehaviors.SR_BEHAVIOR_DX6,
+ nh_addr=self.pg1.remote_ip6,
+ end_psp=0,
+ sw_if_index=self.pg1.sw_if_index,
+ vlan_index=0,
+ fib_table=0)
+ localsid.add_vpp_config()
+ # log the localsids
+ self.logger.debug(self.vapi.cli("show sr localsid"))
+
+ # create IPv6 packets with SRH (SL=0)
+ # send one packet per packet size
+ count = len(self.pg_packet_sizes)
+ dst_inner = 'a4::1234' # inner header destination address
+ pkts = []
+
+ # packets with SRH, segments-left 0, active segment a3::c4
+ packet_header = self.create_packet_header_IPv6_SRH_IPv6(
+ dst_inner,
+ sidlist=['a3::c4', 'a2::', 'a1::'],
+ segleft=0)
+ # add to traffic stream pg0->pg1
+ pkts.extend(self.create_stream(self.pg0, self.pg1, packet_header,
+ self.pg_packet_sizes, count))
+
+ # packets without SRH, IPv6 in IPv6
+ # outer IPv6 dest addr is the localsid End.DX6
+ packet_header = self.create_packet_header_IPv6_IPv6(
+ dst_inner,
+ dst_outer='a3::c4')
+ # add to traffic stream pg0->pg1
+ pkts.extend(self.create_stream(self.pg0, self.pg1, packet_header,
+ self.pg_packet_sizes, count))
+
+ # send packets and verify received packets
+ self.send_and_verify_pkts(self.pg0, pkts, self.pg1,
+ self.compare_rx_tx_packet_End_DX6)
+
+ # log the localsid counters
+ self.logger.info(self.vapi.cli("show sr localsid"))
+
+ # remove SRv6 localSIDs
+ localsid.remove_vpp_config()
+
+ # cleanup interfaces
+ self.teardown_interfaces()
+
+ def test_SRv6_End_DT6(self):
+ """ Test SRv6 End.DT6 behavior.
+ """
+ # create three interfaces (1 source, 2 destinations)
+ # all interfaces are IPv6 only
+ # source interface in global FIB (0)
+ # destination interfaces in global and vrf
+ vrf_1 = 1
+ self.setup_interfaces(ipv6=[True, True, True],
+ ipv6_table_id=[0, 0, vrf_1])
+
+ # configure FIB entries
+ # a4::/64 is reachable
+ # via pg1 in table 0 (global)
+ # and via pg2 in table vrf_1
+ route0 = VppIpRoute(self, "a4::", 64,
+ [VppRoutePath(self.pg1.remote_ip6,
+ self.pg1.sw_if_index,
+ proto=DpoProto.DPO_PROTO_IP6,
+ nh_table_id=0)],
+ table_id=0,
+ is_ip6=1)
+ route0.add_vpp_config()
+ route1 = VppIpRoute(self, "a4::", 64,
+ [VppRoutePath(self.pg2.remote_ip6,
+ self.pg2.sw_if_index,
+ proto=DpoProto.DPO_PROTO_IP6,
+ nh_table_id=vrf_1)],
+ table_id=vrf_1,
+ is_ip6=1)
+ route1.add_vpp_config()
+ self.logger.debug(self.vapi.cli("show ip6 fib"))
+
+ # configure SRv6 localSID End.DT6 behavior
+ # Note:
+ # fib_table: where the localsid is installed
+ # sw_if_index: in T-variants of localsid this is the vrf table_id
+ localsid = VppSRv6LocalSID(
+ self, localsid_addr='a3::c4',
+ behavior=SRv6LocalSIDBehaviors.SR_BEHAVIOR_DT6,
+ nh_addr='::',
+ end_psp=0,
+ sw_if_index=vrf_1,
+ vlan_index=0,
+ fib_table=0)
+ localsid.add_vpp_config()
+ # log the localsids
+ self.logger.debug(self.vapi.cli("show sr localsid"))
+
+ # create IPv6 packets with SRH (SL=0)
+ # send one packet per packet size
+ count = len(self.pg_packet_sizes)
+ dst_inner = 'a4::1234' # inner header destination address
+ pkts = []
+
+ # packets with SRH, segments-left 0, active segment a3::c4
+ packet_header = self.create_packet_header_IPv6_SRH_IPv6(
+ dst_inner,
+ sidlist=['a3::c4', 'a2::', 'a1::'],
+ segleft=0)
+ # add to traffic stream pg0->pg1
+ pkts.extend(self.create_stream(self.pg0, self.pg2, packet_header,
+ self.pg_packet_sizes, count))
+
+ # packets without SRH, IPv6 in IPv6
+ # outer IPv6 dest addr is the localsid End.DT6
+ packet_header = self.create_packet_header_IPv6_IPv6(
+ dst_inner,
+ dst_outer='a3::c4')
+ # add to traffic stream pg0->pg1
+ pkts.extend(self.create_stream(self.pg0, self.pg2, packet_header,
+ self.pg_packet_sizes, count))
+
+ # send packets and verify received packets
+ # using same comparison function as End.DX6
+ self.send_and_verify_pkts(self.pg0, pkts, self.pg2,
+ self.compare_rx_tx_packet_End_DX6)
+
+ # assert nothing was received on the other interface (pg2)
+ self.pg1.assert_nothing_captured("mis-directed packet(s)")
+
+ # log the localsid counters
+ self.logger.info(self.vapi.cli("show sr localsid"))
+
+ # remove SRv6 localSIDs
+ localsid.remove_vpp_config()
+
+ # remove FIB entries
+ # done by tearDown
+
+ # cleanup interfaces
+ self.teardown_interfaces()
+
+ def test_SRv6_End_DX4(self):
+ """ Test SRv6 End.DX4 behavior.
+ """
+ # send traffic to one destination interface
+ # source interface is IPv6 only
+ # destination interface is IPv4 only
+ self.setup_interfaces(ipv6=[True, False], ipv4=[False, True])
+
+ # configure SRv6 localSID End.DX4 behavior
+ localsid = VppSRv6LocalSID(
+ self, localsid_addr='a3::c4',
+ behavior=SRv6LocalSIDBehaviors.SR_BEHAVIOR_DX4,
+ nh_addr=self.pg1.remote_ip4,
+ end_psp=0,
+ sw_if_index=self.pg1.sw_if_index,
+ vlan_index=0,
+ fib_table=0)
+ localsid.add_vpp_config()
+ # log the localsids
+ self.logger.debug(self.vapi.cli("show sr localsid"))
+
+ # send one packet per packet size
+ count = len(self.pg_packet_sizes)
+ dst_inner = '4.1.1.123' # inner header destination address
+ pkts = []
+
+ # packets with SRH, segments-left 0, active segment a3::c4
+ packet_header = self.create_packet_header_IPv6_SRH_IPv4(
+ dst_inner,
+ sidlist=['a3::c4', 'a2::', 'a1::'],
+ segleft=0)
+ # add to traffic stream pg0->pg1
+ pkts.extend(self.create_stream(self.pg0, self.pg1, packet_header,
+ self.pg_packet_sizes, count))
+
+ # packets without SRH, IPv4 in IPv6
+ # outer IPv6 dest addr is the localsid End.DX4
+ packet_header = self.create_packet_header_IPv6_IPv4(
+ dst_inner,
+ dst_outer='a3::c4')
+ # add to traffic stream pg0->pg1
+ pkts.extend(self.create_stream(self.pg0, self.pg1, packet_header,
+ self.pg_packet_sizes, count))
+
+ # send packets and verify received packets
+ self.send_and_verify_pkts(self.pg0, pkts, self.pg1,
+ self.compare_rx_tx_packet_End_DX4)
+
+ # log the localsid counters
+ self.logger.info(self.vapi.cli("show sr localsid"))
+
+ # remove SRv6 localSIDs
+ localsid.remove_vpp_config()
+
+ # cleanup interfaces
+ self.teardown_interfaces()
+
+ def test_SRv6_End_DT4(self):
+ """ Test SRv6 End.DT4 behavior.
+ """
+ # create three interfaces (1 source, 2 destinations)
+ # source interface is IPv6-only
+ # destination interfaces are IPv4 only
+ # source interface in global FIB (0)
+ # destination interfaces in global and vrf
+ vrf_1 = 1
+ self.setup_interfaces(ipv6=[True, False, False],
+ ipv4=[False, True, True],
+ ipv6_table_id=[0, 0, 0],
+ ipv4_table_id=[0, 0, vrf_1])
+
+ # configure FIB entries
+ # 4.1.1.0/24 is reachable
+ # via pg1 in table 0 (global)
+ # and via pg2 in table vrf_1
+ route0 = VppIpRoute(self, "4.1.1.0", 24,
+ [VppRoutePath(self.pg1.remote_ip4,
+ self.pg1.sw_if_index,
+ nh_table_id=0)],
+ table_id=0,
+ is_ip6=0)
+ route0.add_vpp_config()
+ route1 = VppIpRoute(self, "4.1.1.0", 24,
+ [VppRoutePath(self.pg2.remote_ip4,
+ self.pg2.sw_if_index,
+ nh_table_id=vrf_1)],
+ table_id=vrf_1,
+ is_ip6=0)
+ route1.add_vpp_config()
+ self.logger.debug(self.vapi.cli("show ip fib"))
+
+ # configure SRv6 localSID End.DT6 behavior
+ # Note:
+ # fib_table: where the localsid is installed
+ # sw_if_index: in T-variants of localsid: vrf table_id
+ localsid = VppSRv6LocalSID(
+ self, localsid_addr='a3::c4',
+ behavior=SRv6LocalSIDBehaviors.SR_BEHAVIOR_DT4,
+ nh_addr='::',
+ end_psp=0,
+ sw_if_index=vrf_1,
+ vlan_index=0,
+ fib_table=0)
+ localsid.add_vpp_config()
+ # log the localsids
+ self.logger.debug(self.vapi.cli("show sr localsid"))
+
+ # create IPv6 packets with SRH (SL=0)
+ # send one packet per packet size
+ count = len(self.pg_packet_sizes)
+ dst_inner = '4.1.1.123' # inner header destination address
+ pkts = []
+
+ # packets with SRH, segments-left 0, active segment a3::c4
+ packet_header = self.create_packet_header_IPv6_SRH_IPv4(
+ dst_inner,
+ sidlist=['a3::c4', 'a2::', 'a1::'],
+ segleft=0)
+ # add to traffic stream pg0->pg1
+ pkts.extend(self.create_stream(self.pg0, self.pg2, packet_header,
+ self.pg_packet_sizes, count))
+
+ # packets without SRH, IPv6 in IPv6
+ # outer IPv6 dest addr is the localsid End.DX4
+ packet_header = self.create_packet_header_IPv6_IPv4(
+ dst_inner,
+ dst_outer='a3::c4')
+ # add to traffic stream pg0->pg1
+ pkts.extend(self.create_stream(self.pg0, self.pg2, packet_header,
+ self.pg_packet_sizes, count))
+
+ # send packets and verify received packets
+ # using same comparison function as End.DX4
+ self.send_and_verify_pkts(self.pg0, pkts, self.pg2,
+ self.compare_rx_tx_packet_End_DX4)
+
+ # assert nothing was received on the other interface (pg2)
+ self.pg1.assert_nothing_captured("mis-directed packet(s)")
+
+ # log the localsid counters
+ self.logger.info(self.vapi.cli("show sr localsid"))
+
+ # remove SRv6 localSIDs
+ localsid.remove_vpp_config()
+
+ # remove FIB entries
+ # done by tearDown
+
+ # cleanup interfaces
+ self.teardown_interfaces()
+
+ def test_SRv6_End_DX2(self):
+ """ Test SRv6 End.DX2 behavior.
+ """
+ # send traffic to one destination interface
+ # source interface is IPv6 only
+ self.setup_interfaces(ipv6=[True, False], ipv4=[False, False])
+
+ # configure SRv6 localSID End.DX2 behavior
+ localsid = VppSRv6LocalSID(
+ self, localsid_addr='a3::c4',
+ behavior=SRv6LocalSIDBehaviors.SR_BEHAVIOR_DX2,
+ nh_addr='::',
+ end_psp=0,
+ sw_if_index=self.pg1.sw_if_index,
+ vlan_index=0,
+ fib_table=0)
+ localsid.add_vpp_config()
+ # log the localsids
+ self.logger.debug(self.vapi.cli("show sr localsid"))
+
+ # send one packet per packet size
+ count = len(self.pg_packet_sizes)
+ pkts = []
+
+ # packets with SRH, segments-left 0, active segment a3::c4
+ # L2 has no dot1q header
+ packet_header = self.create_packet_header_IPv6_SRH_L2(
+ sidlist=['a3::c4', 'a2::', 'a1::'],
+ segleft=0,
+ vlan=0)
+ # add to traffic stream pg0->pg1
+ pkts.extend(self.create_stream(self.pg0, self.pg1, packet_header,
+ self.pg_packet_sizes, count))
+
+ # packets with SRH, segments-left 0, active segment a3::c4
+ # L2 has dot1q header
+ packet_header = self.create_packet_header_IPv6_SRH_L2(
+ sidlist=['a3::c4', 'a2::', 'a1::'],
+ segleft=0,
+ vlan=123)
+ # add to traffic stream pg0->pg1
+ pkts.extend(self.create_stream(self.pg0, self.pg1, packet_header,
+ self.pg_packet_sizes, count))
+
+ # packets without SRH, L2 in IPv6
+ # outer IPv6 dest addr is the localsid End.DX2
+ # L2 has no dot1q header
+ packet_header = self.create_packet_header_IPv6_L2(
+ dst_outer='a3::c4',
+ vlan=0)
+ # add to traffic stream pg0->pg1
+ pkts.extend(self.create_stream(self.pg0, self.pg1, packet_header,
+ self.pg_packet_sizes, count))
+
+ # packets without SRH, L2 in IPv6
+ # outer IPv6 dest addr is the localsid End.DX2
+ # L2 has dot1q header
+ packet_header = self.create_packet_header_IPv6_L2(
+ dst_outer='a3::c4',
+ vlan=123)
+ # add to traffic stream pg0->pg1
+ pkts.extend(self.create_stream(self.pg0, self.pg1, packet_header,
+ self.pg_packet_sizes, count))
+
+ # send packets and verify received packets
+ self.send_and_verify_pkts(self.pg0, pkts, self.pg1,
+ self.compare_rx_tx_packet_End_DX2)
+
+ # log the localsid counters
+ self.logger.info(self.vapi.cli("show sr localsid"))
+
+ # remove SRv6 localSIDs
+ localsid.remove_vpp_config()
+
+ # cleanup interfaces
+ self.teardown_interfaces()
+
+ def compare_rx_tx_packet_T_Encaps(self, tx_pkt, rx_pkt):
+ """ Compare input and output packet after passing T.Encaps
+
+ :param tx_pkt: transmitted packet
+ :param rx_pkt: received packet
+ """
+ # T.Encaps updates the headers as follows:
+ # SR Policy seglist (S3, S2, S1)
+ # SR Policy source C
+ # IPv6:
+ # in: IPv6(A, B2)
+ # out: IPv6(C, S1)SRH(S3, S2, S1; SL=2)IPv6(A, B2)
+ # IPv6 + SRH:
+ # in: IPv6(A, B2)SRH(B3, B2, B1; SL=1)
+ # out: IPv6(C, S1)SRH(S3, S2, S1; SL=2)IPv6(a, B2)SRH(B3, B2, B1; SL=1)
+
+ # get first (outer) IPv6 header of rx'ed packet
+ rx_ip = rx_pkt.getlayer(IPv6)
+ rx_srh = None
+
+ tx_ip = tx_pkt.getlayer(IPv6)
+
+ # expected segment-list
+ seglist = self.sr_policy.segments
+ # reverse list to get order as in SRH
+ tx_seglist = seglist[::-1]
+
+ # get source address of SR Policy
+ sr_policy_source = self.sr_policy.source
+
+ # rx'ed packet should have SRH
+ self.assertTrue(rx_pkt.haslayer(IPv6ExtHdrSegmentRouting))
+ # get SRH
+ rx_srh = rx_pkt.getlayer(IPv6ExtHdrSegmentRouting)
+
+ # received ip.src should be equal to SR Policy source
+ self.assertEqual(rx_ip.src, sr_policy_source)
+ # received ip.dst should be equal to expected sidlist[lastentry]
+ self.assertEqual(rx_ip.dst, tx_seglist[-1])
+ # rx'ed seglist should be equal to expected seglist
+ self.assertEqual(rx_srh.addresses, tx_seglist)
+ # segleft should be equal to size expected seglist-1
+ self.assertEqual(rx_srh.segleft, len(tx_seglist)-1)
+ # segleft should be equal to lastentry
+ self.assertEqual(rx_srh.segleft, rx_srh.lastentry)
+
+ # the whole rx'ed pkt beyond SRH should be equal to tx'ed pkt
+ # except for the hop-limit field
+ # -> update tx'ed hlim to the expected hlim
+ tx_ip.hlim = tx_ip.hlim - 1
+
+ self.assertEqual(rx_srh.payload, tx_ip)
+
+ self.logger.debug("packet verification: SUCCESS")
+
+ def compare_rx_tx_packet_T_Encaps_IPv4(self, tx_pkt, rx_pkt):
+ """ Compare input and output packet after passing T.Encaps for IPv4
+
+ :param tx_pkt: transmitted packet
+ :param rx_pkt: received packet
+ """
+ # T.Encaps for IPv4 updates the headers as follows:
+ # SR Policy seglist (S3, S2, S1)
+ # SR Policy source C
+ # IPv4:
+ # in: IPv4(A, B2)
+ # out: IPv6(C, S1)SRH(S3, S2, S1; SL=2)IPv4(A, B2)
+
+ # get first (outer) IPv6 header of rx'ed packet
+ rx_ip = rx_pkt.getlayer(IPv6)
+ rx_srh = None
+
+ tx_ip = tx_pkt.getlayer(IP)
+
+ # expected segment-list
+ seglist = self.sr_policy.segments
+ # reverse list to get order as in SRH
+ tx_seglist = seglist[::-1]
+
+ # get source address of SR Policy
+ sr_policy_source = self.sr_policy.source
+
+ # checks common to cases tx with and without SRH
+ # rx'ed packet should have SRH and IPv4 header
+ self.assertTrue(rx_pkt.haslayer(IPv6ExtHdrSegmentRouting))
+ self.assertTrue(rx_ip.payload.haslayer(IP))
+ # get SRH
+ rx_srh = rx_pkt.getlayer(IPv6ExtHdrSegmentRouting)
+
+ # received ip.src should be equal to SR Policy source
+ self.assertEqual(rx_ip.src, sr_policy_source)
+ # received ip.dst should be equal to sidlist[lastentry]
+ self.assertEqual(rx_ip.dst, tx_seglist[-1])
+ # rx'ed seglist should be equal to seglist
+ self.assertEqual(rx_srh.addresses, tx_seglist)
+ # segleft should be equal to size seglist-1
+ self.assertEqual(rx_srh.segleft, len(tx_seglist)-1)
+ # segleft should be equal to lastentry
+ self.assertEqual(rx_srh.segleft, rx_srh.lastentry)
+
+ # the whole rx'ed pkt beyond SRH should be equal to tx'ed pkt
+ # except for the ttl field and ip checksum
+ # -> adjust tx'ed ttl to expected ttl
+ tx_ip.ttl = tx_ip.ttl - 1
+ # -> set tx'ed ip checksum to None and let scapy recompute
+ tx_ip.chksum = None
+ # read back the pkt (with str()) to force computing these fields
+ # probably other ways to accomplish this are possible
+ tx_ip = IP(str(tx_ip))
+
+ self.assertEqual(rx_srh.payload, tx_ip)
+
+ self.logger.debug("packet verification: SUCCESS")
+
+ def compare_rx_tx_packet_T_Encaps_L2(self, tx_pkt, rx_pkt):
+ """ Compare input and output packet after passing T.Encaps for L2
+
+ :param tx_pkt: transmitted packet
+ :param rx_pkt: received packet
+ """
+ # T.Encaps for L2 updates the headers as follows:
+ # SR Policy seglist (S3, S2, S1)
+ # SR Policy source C
+ # L2:
+ # in: L2
+ # out: IPv6(C, S1)SRH(S3, S2, S1; SL=2)L2
+
+ # get first (outer) IPv6 header of rx'ed packet
+ rx_ip = rx_pkt.getlayer(IPv6)
+ rx_srh = None
+
+ tx_ether = tx_pkt.getlayer(Ether)
+
+ # expected segment-list
+ seglist = self.sr_policy.segments
+ # reverse list to get order as in SRH
+ tx_seglist = seglist[::-1]
+
+ # get source address of SR Policy
+ sr_policy_source = self.sr_policy.source
+
+ # rx'ed packet should have SRH
+ self.assertTrue(rx_pkt.haslayer(IPv6ExtHdrSegmentRouting))
+ # get SRH
+ rx_srh = rx_pkt.getlayer(IPv6ExtHdrSegmentRouting)
+
+ # received ip.src should be equal to SR Policy source
+ self.assertEqual(rx_ip.src, sr_policy_source)
+ # received ip.dst should be equal to sidlist[lastentry]
+ self.assertEqual(rx_ip.dst, tx_seglist[-1])
+ # rx'ed seglist should be equal to seglist
+ self.assertEqual(rx_srh.addresses, tx_seglist)
+ # segleft should be equal to size seglist-1
+ self.assertEqual(rx_srh.segleft, len(tx_seglist)-1)
+ # segleft should be equal to lastentry
+ self.assertEqual(rx_srh.segleft, rx_srh.lastentry)
+ # nh should be "No Next Header" (59)
+ self.assertEqual(rx_srh.nh, 59)
+
+ # the whole rx'ed pkt beyond SRH should be equal to tx'ed pkt
+ self.assertEqual(Ether(str(rx_srh.payload)), tx_ether)
+
+ self.logger.debug("packet verification: SUCCESS")
+
+ def compare_rx_tx_packet_T_Insert(self, tx_pkt, rx_pkt):
+ """ Compare input and output packet after passing T.Insert
+
+ :param tx_pkt: transmitted packet
+ :param rx_pkt: received packet
+ """
+ # T.Insert updates the headers as follows:
+ # IPv6:
+ # in: IPv6(A, B2)
+ # out: IPv6(A, S1)SRH(B2, S3, S2, S1; SL=3)
+ # IPv6 + SRH:
+ # in: IPv6(A, B2)SRH(B3, B2, B1; SL=1)
+ # out: IPv6(A, S1)SRH(B2, S3, S2, S1; SL=3)SRH(B3, B2, B1; SL=1)
+
+ # get first (outer) IPv6 header of rx'ed packet
+ rx_ip = rx_pkt.getlayer(IPv6)
+ rx_srh = None
+ rx_ip2 = None
+ rx_srh2 = None
+ rx_ip3 = None
+ rx_udp = rx_pkt[UDP]
+
+ tx_ip = tx_pkt.getlayer(IPv6)
+ tx_srh = None
+ tx_ip2 = None
+ # some packets have been tx'ed with an SRH, some without it
+ # get SRH if tx'ed packet has it
+ if tx_pkt.haslayer(IPv6ExtHdrSegmentRouting):
+ tx_srh = tx_pkt.getlayer(IPv6ExtHdrSegmentRouting)
+ tx_ip2 = tx_pkt.getlayer(IPv6, 2)
+ tx_udp = tx_pkt[UDP]
+
+ # expected segment-list (make copy of SR Policy segment list)
+ seglist = self.sr_policy.segments[:]
+ # expected seglist has initial dest addr as last segment
+ seglist.append(tx_ip.dst)
+ # reverse list to get order as in SRH
+ tx_seglist = seglist[::-1]
+
+ # get source address of SR Policy
+ sr_policy_source = self.sr_policy.source
+
+ # checks common to cases tx with and without SRH
+ # rx'ed packet should have SRH and only one IPv6 header
+ self.assertTrue(rx_pkt.haslayer(IPv6ExtHdrSegmentRouting))
+ self.assertFalse(rx_ip.payload.haslayer(IPv6))
+ # get SRH
+ rx_srh = rx_pkt.getlayer(IPv6ExtHdrSegmentRouting)
+
+ # rx'ed ip.src should be equal to tx'ed ip.src
+ self.assertEqual(rx_ip.src, tx_ip.src)
+ # rx'ed ip.dst should be equal to sidlist[lastentry]
+ self.assertEqual(rx_ip.dst, tx_seglist[-1])
+
+ # rx'ed seglist should be equal to expected seglist
+ self.assertEqual(rx_srh.addresses, tx_seglist)
+ # segleft should be equal to size(expected seglist)-1
+ self.assertEqual(rx_srh.segleft, len(tx_seglist)-1)
+ # segleft should be equal to lastentry
+ self.assertEqual(rx_srh.segleft, rx_srh.lastentry)
+
+ if tx_srh: # packet was tx'ed with SRH
+ # packet should have 2nd SRH
+ self.assertTrue(rx_srh.payload.haslayer(IPv6ExtHdrSegmentRouting))
+ # get 2nd SRH
+ rx_srh2 = rx_pkt.getlayer(IPv6ExtHdrSegmentRouting, 2)
+
+ # rx'ed srh2.addresses should be equal to tx'ed srh.addresses
+ self.assertEqual(rx_srh2.addresses, tx_srh.addresses)
+ # rx'ed srh2.segleft should be equal to tx'ed srh.segleft
+ self.assertEqual(rx_srh2.segleft, tx_srh.segleft)
+ # rx'ed srh2.lastentry should be equal to tx'ed srh.lastentry
+ self.assertEqual(rx_srh2.lastentry, tx_srh.lastentry)
+
+ else: # packet was tx'ed without SRH
+ # rx packet should have no other SRH
+ self.assertFalse(rx_srh.payload.haslayer(IPv6ExtHdrSegmentRouting))
+
+ # UDP layer should be unchanged
+ self.assertEqual(rx_udp, tx_udp)
+
+ self.logger.debug("packet verification: SUCCESS")
+
+ def compare_rx_tx_packet_End(self, tx_pkt, rx_pkt):
+ """ Compare input and output packet after passing End (without PSP)
+
+ :param tx_pkt: transmitted packet
+ :param rx_pkt: received packet
+ """
+ # End (no PSP) updates the headers as follows:
+ # IPv6 + SRH:
+ # in: IPv6(A, S1)SRH(S3, S2, S1; SL=2)
+ # out: IPv6(A, S2)SRH(S3, S2, S1; SL=1)
+
+ # get first (outer) IPv6 header of rx'ed packet
+ rx_ip = rx_pkt.getlayer(IPv6)
+ rx_srh = None
+ rx_ip2 = None
+ rx_udp = rx_pkt[UDP]
+
+ tx_ip = tx_pkt.getlayer(IPv6)
+ # we know the packet has been tx'ed
+ # with an inner IPv6 header and an SRH
+ tx_ip2 = tx_pkt.getlayer(IPv6, 2)
+ tx_srh = tx_pkt.getlayer(IPv6ExtHdrSegmentRouting)
+ tx_udp = tx_pkt[UDP]
+
+ # common checks, regardless of tx segleft value
+ # rx'ed packet should have 2nd IPv6 header
+ self.assertTrue(rx_ip.payload.haslayer(IPv6))
+ # get second (inner) IPv6 header
+ rx_ip2 = rx_pkt.getlayer(IPv6, 2)
+
+ if tx_ip.segleft > 0:
+ # SRH should NOT have been popped:
+ # End SID without PSP does not pop SRH if segleft>0
+ self.assertTrue(rx_pkt.haslayer(IPv6ExtHdrSegmentRouting))
+ rx_srh = rx_pkt.getlayer(IPv6ExtHdrSegmentRouting)
+
+ # received ip.src should be equal to expected ip.src
+ self.assertEqual(rx_ip.src, tx_ip.src)
+ # sidlist should be unchanged
+ self.assertEqual(rx_srh.addresses, tx_srh.addresses)
+ # segleft should have been decremented
+ self.assertEqual(rx_srh.segleft, tx_srh.segleft-1)
+ # received ip.dst should be equal to sidlist[segleft]
+ self.assertEqual(rx_ip.dst, rx_srh.addresses[rx_srh.segleft])
+ # lastentry should be unchanged
+ self.assertEqual(rx_srh.lastentry, tx_srh.lastentry)
+ # inner IPv6 packet (ip2) should be unchanged
+ self.assertEqual(rx_ip2.src, tx_ip2.src)
+ self.assertEqual(rx_ip2.dst, tx_ip2.dst)
+ # else: # tx_ip.segleft == 0
+ # TODO: Does this work with 2 SRHs in ingress packet?
+
+ # UDP layer should be unchanged
+ self.assertEqual(rx_udp, tx_udp)
+
+ self.logger.debug("packet verification: SUCCESS")
+
+ def compare_rx_tx_packet_End_PSP(self, tx_pkt, rx_pkt):
+ """ Compare input and output packet after passing End with PSP
+
+ :param tx_pkt: transmitted packet
+ :param rx_pkt: received packet
+ """
+ # End (PSP) updates the headers as follows:
+ # IPv6 + SRH (SL>1):
+ # in: IPv6(A, S1)SRH(S3, S2, S1; SL=2)
+ # out: IPv6(A, S2)SRH(S3, S2, S1; SL=1)
+ # IPv6 + SRH (SL=1):
+ # in: IPv6(A, S2)SRH(S3, S2, S1; SL=1)
+ # out: IPv6(A, S3)
+
+ # get first (outer) IPv6 header of rx'ed packet
+ rx_ip = rx_pkt.getlayer(IPv6)
+ rx_srh = None
+ rx_ip2 = None
+ rx_udp = rx_pkt[UDP]
+
+ tx_ip = tx_pkt.getlayer(IPv6)
+ # we know the packet has been tx'ed
+ # with an inner IPv6 header and an SRH
+ tx_ip2 = tx_pkt.getlayer(IPv6, 2)
+ tx_srh = tx_pkt.getlayer(IPv6ExtHdrSegmentRouting)
+ tx_udp = tx_pkt[UDP]
+
+ # common checks, regardless of tx segleft value
+ self.assertTrue(rx_ip.payload.haslayer(IPv6))
+ rx_ip2 = rx_pkt.getlayer(IPv6, 2)
+ # inner IPv6 packet (ip2) should be unchanged
+ self.assertEqual(rx_ip2.src, tx_ip2.src)
+ self.assertEqual(rx_ip2.dst, tx_ip2.dst)
+
+ if tx_ip.segleft > 1:
+ # SRH should NOT have been popped:
+ # End SID with PSP does not pop SRH if segleft>1
+ # rx'ed packet should have SRH
+ self.assertTrue(rx_pkt.haslayer(IPv6ExtHdrSegmentRouting))
+ rx_srh = rx_pkt.getlayer(IPv6ExtHdrSegmentRouting)
+
+ # received ip.src should be equal to expected ip.src
+ self.assertEqual(rx_ip.src, tx_ip.src)
+ # sidlist should be unchanged
+ self.assertEqual(rx_srh.addresses, tx_srh.addresses)
+ # segleft should have been decremented
+ self.assertEqual(rx_srh.segleft, tx_srh.segleft-1)
+ # received ip.dst should be equal to sidlist[segleft]
+ self.assertEqual(rx_ip.dst, rx_srh.addresses[rx_srh.segleft])
+ # lastentry should be unchanged
+ self.assertEqual(rx_srh.lastentry, tx_srh.lastentry)
+
+ else: # tx_ip.segleft <= 1
+ # SRH should have been popped:
+ # End SID with PSP and segleft=1 pops SRH
+ # the two IPv6 headers are still present
+ # outer IPv6 header has DA == last segment of popped SRH
+ # SRH should not be present
+ self.assertFalse(rx_pkt.haslayer(IPv6ExtHdrSegmentRouting))
+ # outer IPv6 header ip.src should be equal to tx'ed ip.src
+ self.assertEqual(rx_ip.src, tx_ip.src)
+ # outer IPv6 header ip.dst should be = to tx'ed sidlist[segleft-1]
+ self.assertEqual(rx_ip.dst, tx_srh.addresses[tx_srh.segleft-1])
+
+ # UDP layer should be unchanged
+ self.assertEqual(rx_udp, tx_udp)
+
+ self.logger.debug("packet verification: SUCCESS")
+
+ def compare_rx_tx_packet_End_DX6(self, tx_pkt, rx_pkt):
+ """ Compare input and output packet after passing End.DX6
+
+ :param tx_pkt: transmitted packet
+ :param rx_pkt: received packet
+ """
+ # End.DX6 updates the headers as follows:
+ # IPv6 + SRH (SL=0):
+ # in: IPv6(A, S3)SRH(S3, S2, S1; SL=0)IPv6(B, D)
+ # out: IPv6(B, D)
+ # IPv6:
+ # in: IPv6(A, S3)IPv6(B, D)
+ # out: IPv6(B, D)
+
+ # get first (outer) IPv6 header of rx'ed packet
+ rx_ip = rx_pkt.getlayer(IPv6)
+
+ tx_ip = tx_pkt.getlayer(IPv6)
+ tx_ip2 = tx_pkt.getlayer(IPv6, 2)
+
+ # verify if rx'ed packet has no SRH
+ self.assertFalse(rx_pkt.haslayer(IPv6ExtHdrSegmentRouting))
+
+ # the whole rx_ip pkt should be equal to tx_ip2
+ # except for the hlim field
+ # -> adjust tx'ed hlim to expected hlim
+ tx_ip2.hlim = tx_ip2.hlim - 1
+
+ self.assertEqual(rx_ip, tx_ip2)
+
+ self.logger.debug("packet verification: SUCCESS")
+
+ def compare_rx_tx_packet_End_DX4(self, tx_pkt, rx_pkt):
+ """ Compare input and output packet after passing End.DX4
+
+ :param tx_pkt: transmitted packet
+ :param rx_pkt: received packet
+ """
+ # End.DX4 updates the headers as follows:
+ # IPv6 + SRH (SL=0):
+ # in: IPv6(A, S3)SRH(S3, S2, S1; SL=0)IPv4(B, D)
+ # out: IPv4(B, D)
+ # IPv6:
+ # in: IPv6(A, S3)IPv4(B, D)
+ # out: IPv4(B, D)
+
+ # get IPv4 header of rx'ed packet
+ rx_ip = rx_pkt.getlayer(IP)
+
+ tx_ip = tx_pkt.getlayer(IPv6)
+ tx_ip2 = tx_pkt.getlayer(IP)
+
+ # verify if rx'ed packet has no SRH
+ self.assertFalse(rx_pkt.haslayer(IPv6ExtHdrSegmentRouting))
+
+ # the whole rx_ip pkt should be equal to tx_ip2
+ # except for the ttl field and ip checksum
+ # -> adjust tx'ed ttl to expected ttl
+ tx_ip2.ttl = tx_ip2.ttl - 1
+ # -> set tx'ed ip checksum to None and let scapy recompute
+ tx_ip2.chksum = None
+ # read back the pkt (with str()) to force computing these fields
+ # probably other ways to accomplish this are possible
+ tx_ip2 = IP(str(tx_ip2))
+
+ self.assertEqual(rx_ip, tx_ip2)
+
+ self.logger.debug("packet verification: SUCCESS")
+
+ def compare_rx_tx_packet_End_DX2(self, tx_pkt, rx_pkt):
+ """ Compare input and output packet after passing End.DX2
+
+ :param tx_pkt: transmitted packet
+ :param rx_pkt: received packet
+ """
+ # End.DX2 updates the headers as follows:
+ # IPv6 + SRH (SL=0):
+ # in: IPv6(A, S3)SRH(S3, S2, S1; SL=0)L2
+ # out: L2
+ # IPv6:
+ # in: IPv6(A, S3)L2
+ # out: L2
+
+ # get IPv4 header of rx'ed packet
+ rx_eth = rx_pkt.getlayer(Ether)
+
+ tx_ip = tx_pkt.getlayer(IPv6)
+ # we can't just get the 2nd Ether layer
+ # get the Raw content and dissect it as Ether
+ tx_eth1 = Ether(str(tx_pkt[Raw]))
+
+ # verify if rx'ed packet has no SRH
+ self.assertFalse(rx_pkt.haslayer(IPv6ExtHdrSegmentRouting))
+
+ # the whole rx_eth pkt should be equal to tx_eth1
+ self.assertEqual(rx_eth, tx_eth1)
+
+ self.logger.debug("packet verification: SUCCESS")
+
+ def create_stream(self, src_if, dst_if, packet_header, packet_sizes,
+ count):
+ """Create SRv6 input packet stream for defined interface.
+
+ :param VppInterface src_if: Interface to create packet stream for
+ :param VppInterface dst_if: destination interface of packet stream
+ :param packet_header: Layer3 scapy packet headers,
+ L2 is added when not provided,
+ Raw(payload) with packet_info is added
+ :param list packet_sizes: packet stream pckt sizes,sequentially applied
+ to packets in stream have
+ :param int count: number of packets in packet stream
+ :return: list of packets
+ """
+ self.logger.info("Creating packets")
+ pkts = []
+ for i in range(0, count-1):
+ payload_info = self.create_packet_info(src_if, dst_if)
+ self.logger.debug(
+ "Creating packet with index %d" % (payload_info.index))
+ payload = self.info_to_payload(payload_info)
+ # add L2 header if not yet provided in packet_header
+ if packet_header.getlayer(0).name == 'Ethernet':
+ p = (packet_header /
+ Raw(payload))
+ else:
+ p = (Ether(dst=src_if.local_mac, src=src_if.remote_mac) /
+ packet_header /
+ Raw(payload))
+ size = packet_sizes[i % len(packet_sizes)]
+ self.logger.debug("Packet size %d" % (size))
+ self.extend_packet(p, size)
+ # we need to store the packet with the automatic fields computed
+ # read back the dumped packet (with str())
+ # to force computing these fields
+ # probably other ways are possible
+ p = Ether(str(p))
+ payload_info.data = p.copy()
+ self.logger.debug(ppp("Created packet:", p))
+ pkts.append(p)
+ self.logger.info("Done creating packets")
+ return pkts
+
+ def send_and_verify_pkts(self, input, pkts, output, compare_func):
+ """Send packets and verify received packets using compare_func
+
+ :param input: ingress interface of DUT
+ :param pkts: list of packets to transmit
+ :param output: egress interface of DUT
+ :param compare_func: function to compare in and out packets
+ """
+ # add traffic stream to input interface
+ input.add_stream(pkts)
+
+ # enable capture on all interfaces
+ self.pg_enable_capture(self.pg_interfaces)
+
+ # start traffic
+ self.logger.info("Starting traffic")
+ self.pg_start()
+
+ # get output capture
+ self.logger.info("Getting packet capture")
+ capture = output.get_capture()
+
+ # assert nothing was captured on input interface
+ input.assert_nothing_captured()
+
+ # verify captured packets
+ self.verify_captured_pkts(output, capture, compare_func)
+
+ def create_packet_header_IPv6(self, dst):
+ """Create packet header: IPv6 header, UDP header
+
+ :param dst: IPv6 destination address
+
+ IPv6 source address is 1234::1
+ UDP source port and destination port are 1234
+ """
+
+ p = (IPv6(src='1234::1', dst=dst) /
+ UDP(sport=1234, dport=1234))
+ return p
+
+ def create_packet_header_IPv6_SRH(self, sidlist, segleft):
+ """Create packet header: IPv6 header with SRH, UDP header
+
+ :param list sidlist: segment list
+ :param int segleft: segments-left field value
+
+ IPv6 destination address is set to sidlist[segleft]
+ IPv6 source addresses are 1234::1 and 4321::1
+ UDP source port and destination port are 1234
+ """
+
+ p = (IPv6(src='1234::1', dst=sidlist[segleft]) /
+ IPv6ExtHdrSegmentRouting(addresses=sidlist) /
+ UDP(sport=1234, dport=1234))
+ return p
+
+ def create_packet_header_IPv6_SRH_IPv6(self, dst, sidlist, segleft):
+ """Create packet header: IPv6 encapsulated in SRv6:
+ IPv6 header with SRH, IPv6 header, UDP header
+
+ :param ipv6address dst: inner IPv6 destination address
+ :param list sidlist: segment list of outer IPv6 SRH
+ :param int segleft: segments-left field of outer IPv6 SRH
+
+ Outer IPv6 destination address is set to sidlist[segleft]
+ IPv6 source addresses are 1234::1 and 4321::1
+ UDP source port and destination port are 1234
+ """
+
+ p = (IPv6(src='1234::1', dst=sidlist[segleft]) /
+ IPv6ExtHdrSegmentRouting(addresses=sidlist,
+ segleft=segleft, nh=41) /
+ IPv6(src='4321::1', dst=dst) /
+ UDP(sport=1234, dport=1234))
+ return p
+
+ def create_packet_header_IPv6_IPv6(self, dst_inner, dst_outer):
+ """Create packet header: IPv6 encapsulated in IPv6:
+ IPv6 header, IPv6 header, UDP header
+
+ :param ipv6address dst_inner: inner IPv6 destination address
+ :param ipv6address dst_outer: outer IPv6 destination address
+
+ IPv6 source addresses are 1234::1 and 4321::1
+ UDP source port and destination port are 1234
+ """
+
+ p = (IPv6(src='1234::1', dst=dst_outer) /
+ IPv6(src='4321::1', dst=dst_inner) /
+ UDP(sport=1234, dport=1234))
+ return p
+
+ def create_packet_header_IPv6_SRH_SRH_IPv6(self, dst, sidlist1, segleft1,
+ sidlist2, segleft2):
+ """Create packet header: IPv6 encapsulated in SRv6 with 2 SRH:
+ IPv6 header with SRH, 2nd SRH, IPv6 header, UDP header
+
+ :param ipv6address dst: inner IPv6 destination address
+ :param list sidlist1: segment list of outer IPv6 SRH
+ :param int segleft1: segments-left field of outer IPv6 SRH
+ :param list sidlist2: segment list of inner IPv6 SRH
+ :param int segleft2: segments-left field of inner IPv6 SRH
+
+ Outer IPv6 destination address is set to sidlist[segleft]
+ IPv6 source addresses are 1234::1 and 4321::1
+ UDP source port and destination port are 1234
+ """
+
+ p = (IPv6(src='1234::1', dst=sidlist1[segleft1]) /
+ IPv6ExtHdrSegmentRouting(addresses=sidlist1,
+ segleft=segleft1, nh=43) /
+ IPv6ExtHdrSegmentRouting(addresses=sidlist2,
+ segleft=segleft2, nh=41) /
+ IPv6(src='4321::1', dst=dst) /
+ UDP(sport=1234, dport=1234))
+ return p
+
+ def create_packet_header_IPv4(self, dst):
+ """Create packet header: IPv4 header, UDP header
+
+ :param dst: IPv4 destination address
+
+ IPv4 source address is 123.1.1.1
+ UDP source port and destination port are 1234
+ """
+
+ p = (IP(src='123.1.1.1', dst=dst) /
+ UDP(sport=1234, dport=1234))
+ return p
+
+ def create_packet_header_IPv6_IPv4(self, dst_inner, dst_outer):
+ """Create packet header: IPv4 encapsulated in IPv6:
+ IPv6 header, IPv4 header, UDP header
+
+ :param ipv4address dst_inner: inner IPv4 destination address
+ :param ipv6address dst_outer: outer IPv6 destination address
+
+ IPv6 source address is 1234::1
+ IPv4 source address is 123.1.1.1
+ UDP source port and destination port are 1234
+ """
+
+ p = (IPv6(src='1234::1', dst=dst_outer) /
+ IP(src='123.1.1.1', dst=dst_inner) /
+ UDP(sport=1234, dport=1234))
+ return p
+
+ def create_packet_header_IPv6_SRH_IPv4(self, dst, sidlist, segleft):
+ """Create packet header: IPv4 encapsulated in SRv6:
+ IPv6 header with SRH, IPv4 header, UDP header
+
+ :param ipv4address dst: inner IPv4 destination address
+ :param list sidlist: segment list of outer IPv6 SRH
+ :param int segleft: segments-left field of outer IPv6 SRH
+
+ Outer IPv6 destination address is set to sidlist[segleft]
+ IPv6 source address is 1234::1
+ IPv4 source address is 123.1.1.1
+ UDP source port and destination port are 1234
+ """
+
+ p = (IPv6(src='1234::1', dst=sidlist[segleft]) /
+ IPv6ExtHdrSegmentRouting(addresses=sidlist,
+ segleft=segleft, nh=4) /
+ IP(src='123.1.1.1', dst=dst) /
+ UDP(sport=1234, dport=1234))
+ return p
+
+ def create_packet_header_L2(self, vlan=0):
+ """Create packet header: L2 header
+
+ :param vlan: if vlan!=0 then add 802.1q header
+ """
+ # Note: the dst addr ('00:55:44:33:22:11') is used in
+ # the compare function compare_rx_tx_packet_T_Encaps_L2
+ # to detect presence of L2 in SRH payload
+ p = Ether(src='00:11:22:33:44:55', dst='00:55:44:33:22:11')
+ etype = 0x8137 # IPX
+ if vlan:
+ # add 802.1q layer
+ p /= Dot1Q(vlan=vlan, type=etype)
+ else:
+ p.type = etype
+ return p
+
+ def create_packet_header_IPv6_SRH_L2(self, sidlist, segleft, vlan=0):
+ """Create packet header: L2 encapsulated in SRv6:
+ IPv6 header with SRH, L2
+
+ :param list sidlist: segment list of outer IPv6 SRH
+ :param int segleft: segments-left field of outer IPv6 SRH
+ :param vlan: L2 vlan; if vlan!=0 then add 802.1q header
+
+ Outer IPv6 destination address is set to sidlist[segleft]
+ IPv6 source address is 1234::1
+ """
+ eth = Ether(src='00:11:22:33:44:55', dst='00:55:44:33:22:11')
+ etype = 0x8137 # IPX
+ if vlan:
+ # add 802.1q layer
+ eth /= Dot1Q(vlan=vlan, type=etype)
+ else:
+ eth.type = etype
+
+ p = (IPv6(src='1234::1', dst=sidlist[segleft]) /
+ IPv6ExtHdrSegmentRouting(addresses=sidlist,
+ segleft=segleft, nh=59) /
+ eth)
+ return p
+
+ def create_packet_header_IPv6_L2(self, dst_outer, vlan=0):
+ """Create packet header: L2 encapsulated in IPv6:
+ IPv6 header, L2
+
+ :param ipv6address dst_outer: outer IPv6 destination address
+ :param vlan: L2 vlan; if vlan!=0 then add 802.1q header
+ """
+ eth = Ether(src='00:11:22:33:44:55', dst='00:55:44:33:22:11')
+ etype = 0x8137 # IPX
+ if vlan:
+ # add 802.1q layer
+ eth /= Dot1Q(vlan=vlan, type=etype)
+ else:
+ eth.type = etype
+
+ p = (IPv6(src='1234::1', dst=dst_outer, nh=59) / eth)
+ return p
+
+ def get_payload_info(self, packet):
+ """ Extract the payload_info from the packet
+ """
+ # in most cases, payload_info is in packet[Raw]
+ # but packet[Raw] gives the complete payload
+ # (incl L2 header) for the T.Encaps L2 case
+ try:
+ payload_info = self.payload_to_info(str(packet[Raw]))
+
+ except:
+ # remote L2 header from packet[Raw]:
+ # take packet[Raw], convert it to an Ether layer
+ # and then extract Raw from it
+ payload_info = self.payload_to_info(
+ str(Ether(str(packet[Raw]))[Raw]))
+
+ return payload_info
+
+ def verify_captured_pkts(self, dst_if, capture, compare_func):
+ """
+ Verify captured packet stream for specified interface.
+ Compare ingress with egress packets using the specified compare fn
+
+ :param dst_if: egress interface of DUT
+ :param capture: captured packets
+ :param compare_func: function to compare in and out packet
+ """
+ self.logger.info("Verifying capture on interface %s using function %s"
+ % (dst_if.name, compare_func.func_name))
+
+ last_info = dict()
+ for i in self.pg_interfaces:
+ last_info[i.sw_if_index] = None
+ dst_sw_if_index = dst_if.sw_if_index
+
+ for packet in capture:
+ try:
+ # extract payload_info from packet's payload
+ payload_info = self.get_payload_info(packet)
+ packet_index = payload_info.index
+
+ self.logger.debug("Verifying packet with index %d"
+ % (packet_index))
+ # packet should have arrived on the expected interface
+ self.assertEqual(payload_info.dst, dst_sw_if_index)
+ self.logger.debug(
+ "Got packet on interface %s: src=%u (idx=%u)" %
+ (dst_if.name, payload_info.src, packet_index))
+
+ # search for payload_info with same src and dst if_index
+ # this will give us the transmitted packet
+ next_info = self.get_next_packet_info_for_interface2(
+ payload_info.src, dst_sw_if_index,
+ last_info[payload_info.src])
+ last_info[payload_info.src] = next_info
+ # next_info should not be None
+ self.assertTrue(next_info is not None)
+ # index of tx and rx packets should be equal
+ self.assertEqual(packet_index, next_info.index)
+ # data field of next_info contains the tx packet
+ txed_packet = next_info.data
+
+ self.logger.debug(ppp("Transmitted packet:",
+ txed_packet)) # ppp=Pretty Print Packet
+
+ self.logger.debug(ppp("Received packet:", packet))
+
+ # compare rcvd packet with expected packet using compare_func
+ compare_func(txed_packet, packet)
+
+ except:
+ print packet.command()
+ self.logger.error(ppp("Unexpected or invalid packet:", packet))
+ raise
+
+ # have all expected packets arrived?
+ for i in self.pg_interfaces:
+ remaining_packet = self.get_next_packet_info_for_interface2(
+ i.sw_if_index, dst_sw_if_index, last_info[i.sw_if_index])
+ self.assertTrue(remaining_packet is None,
+ "Interface %s: Packet expected from interface %s "
+ "didn't arrive" % (dst_if.name, i.name))
+
+
+if __name__ == '__main__':
+ unittest.main(testRunner=VppTestRunner)
diff --git a/test/test_vapi.py b/test/test_vapi.py
new file mode 100644
index 00000000..d8e1ebe0
--- /dev/null
+++ b/test/test_vapi.py
@@ -0,0 +1,106 @@
+#!/usr/bin/env python
+""" VAPI test """
+
+from __future__ import division
+import unittest
+import os
+import signal
+import subprocess
+from threading import Thread
+from log import single_line_delim
+from framework import VppTestCase, running_extended_tests, VppTestRunner
+
+
+class Worker(Thread):
+ def __init__(self, args, logger):
+ self.logger = logger
+ self.args = args
+ self.result = None
+ super(Worker, self).__init__()
+
+ def run(self):
+ executable = self.args[0]
+ self.logger.debug("Running executable w/args `%s'" % self.args)
+ env = os.environ.copy()
+ env["CK_LOG_FILE_NAME"] = "-"
+ self.process = subprocess.Popen(
+ self.args, shell=False, env=env, preexec_fn=os.setpgrp,
+ stdout=subprocess.PIPE, stderr=subprocess.PIPE)
+ out, err = self.process.communicate()
+ self.logger.debug("Finished running `%s'" % executable)
+ self.logger.info("Return code is `%s'" % self.process.returncode)
+ self.logger.info(single_line_delim)
+ self.logger.info("Executable `%s' wrote to stdout:" % executable)
+ self.logger.info(single_line_delim)
+ self.logger.info(out)
+ self.logger.info(single_line_delim)
+ self.logger.info("Executable `%s' wrote to stderr:" % executable)
+ self.logger.info(single_line_delim)
+ self.logger.error(err)
+ self.logger.info(single_line_delim)
+ self.result = self.process.returncode
+
+
+@unittest.skipUnless(running_extended_tests(), "part of extended tests")
+class VAPITestCase(VppTestCase):
+ """ VAPI test """
+
+ def test_vapi_c(self):
+ """ run C VAPI tests """
+ var = "BR"
+ built_root = os.getenv(var, None)
+ self.assertIsNotNone(built_root,
+ "Environment variable `%s' not set" % var)
+ executable = "%s/vapi_test/vapi_c_test" % built_root
+ worker = Worker(
+ [executable, "vapi client", self.shm_prefix], self.logger)
+ worker.start()
+ timeout = 60
+ worker.join(timeout)
+ self.logger.info("Worker result is `%s'" % worker.result)
+ error = False
+ if worker.result is None:
+ try:
+ error = True
+ self.logger.error(
+ "Timeout! Worker did not finish in %ss" % timeout)
+ os.killpg(os.getpgid(worker.process.pid), signal.SIGTERM)
+ worker.join()
+ except:
+ raise Exception("Couldn't kill worker-spawned process")
+ if error:
+ raise Exception(
+ "Timeout! Worker did not finish in %ss" % timeout)
+ self.assert_equal(worker.result, 0, "Binary test return code")
+
+ def test_vapi_cpp(self):
+ """ run C++ VAPI tests """
+ var = "BR"
+ built_root = os.getenv(var, None)
+ self.assertIsNotNone(built_root,
+ "Environment variable `%s' not set" % var)
+ executable = "%s/vapi_test/vapi_cpp_test" % built_root
+ worker = Worker(
+ [executable, "vapi client", self.shm_prefix], self.logger)
+ worker.start()
+ timeout = 120
+ worker.join(timeout)
+ self.logger.info("Worker result is `%s'" % worker.result)
+ error = False
+ if worker.result is None:
+ try:
+ error = True
+ self.logger.error(
+ "Timeout! Worker did not finish in %ss" % timeout)
+ os.killpg(os.getpgid(worker.process.pid), signal.SIGTERM)
+ worker.join()
+ except:
+ raise Exception("Couldn't kill worker-spawned process")
+ if error:
+ raise Exception(
+ "Timeout! Worker did not finish in %ss" % timeout)
+ self.assert_equal(worker.result, 0, "Binary test return code")
+
+
+if __name__ == '__main__':
+ unittest.main(testRunner=VppTestRunner)
diff --git a/test/test_vtr.py b/test/test_vtr.py
new file mode 100644
index 00000000..02df2ce2
--- /dev/null
+++ b/test/test_vtr.py
@@ -0,0 +1,332 @@
+#!/usr/bin/env python
+
+import unittest
+import random
+
+from scapy.packet import Raw
+from scapy.layers.l2 import Ether, Dot1Q
+from scapy.layers.inet import IP, UDP
+
+from util import Host
+from framework import VppTestCase, VppTestRunner
+from vpp_sub_interface import VppDot1QSubint, VppDot1ADSubint
+from vpp_papi_provider import L2_VTR_OP
+from collections import namedtuple
+
+Tag = namedtuple('Tag', ['dot1', 'vlan'])
+DOT1AD = 0x88A8
+DOT1Q = 0x8100
+
+
+class TestVtr(VppTestCase):
+ """ VTR Test Case """
+
+ @classmethod
+ def setUpClass(cls):
+ super(TestVtr, cls).setUpClass()
+
+ # Test variables
+ cls.bd_id = 1
+ cls.mac_entries_count = 5
+ cls.Atag = 100
+ cls.Btag = 200
+ cls.dot1ad_sub_id = 20
+
+ try:
+ ifs = range(3)
+ cls.create_pg_interfaces(ifs)
+
+ cls.sub_interfaces = [
+ VppDot1ADSubint(cls, cls.pg1, cls.dot1ad_sub_id,
+ cls.Btag, cls.Atag),
+ VppDot1QSubint(cls, cls.pg2, cls.Btag)]
+
+ interfaces = list(cls.pg_interfaces)
+ interfaces.extend(cls.sub_interfaces)
+
+ # Create BD with MAC learning enabled and put interfaces and
+ # sub-interfaces to this BD
+ for pg_if in cls.pg_interfaces:
+ sw_if_index = pg_if.sub_if.sw_if_index \
+ if hasattr(pg_if, 'sub_if') else pg_if.sw_if_index
+ cls.vapi.sw_interface_set_l2_bridge(sw_if_index,
+ bd_id=cls.bd_id)
+
+ # setup all interfaces
+ for i in interfaces:
+ i.admin_up()
+
+ # mapping between packet-generator index and lists of test hosts
+ cls.hosts_by_pg_idx = dict()
+
+ # create test host entries and inject packets to learn MAC entries
+ # in the bridge-domain
+ cls.create_hosts_and_learn(cls.mac_entries_count)
+ cls.logger.info(cls.vapi.ppcli("show l2fib"))
+
+ except Exception:
+ super(TestVtr, cls).tearDownClass()
+ raise
+
+ def setUp(self):
+ """
+ Clear trace and packet infos before running each test.
+ """
+ super(TestVtr, self).setUp()
+ self.reset_packet_infos()
+
+ def tearDown(self):
+ """
+ Show various debug prints after each test.
+ """
+ super(TestVtr, self).tearDown()
+ if not self.vpp_dead:
+ self.logger.info(self.vapi.ppcli("show l2fib verbose"))
+ self.logger.info(self.vapi.ppcli("show bridge-domain %s detail" %
+ self.bd_id))
+
+ @classmethod
+ def create_hosts_and_learn(cls, count):
+ for pg_if in cls.pg_interfaces:
+ cls.hosts_by_pg_idx[pg_if.sw_if_index] = []
+ hosts = cls.hosts_by_pg_idx[pg_if.sw_if_index]
+ packets = []
+ for j in range(1, count + 1):
+ host = Host(
+ "00:00:00:ff:%02x:%02x" % (pg_if.sw_if_index, j),
+ "172.17.1%02x.%u" % (pg_if.sw_if_index, j))
+ packet = (Ether(dst="ff:ff:ff:ff:ff:ff", src=host.mac))
+ hosts.append(host)
+ if hasattr(pg_if, 'sub_if'):
+ packet = pg_if.sub_if.add_dot1_layer(packet)
+ packets.append(packet)
+ pg_if.add_stream(packets)
+ cls.logger.info("Sending broadcast eth frames for MAC learning")
+ cls.pg_enable_capture(cls.pg_interfaces)
+ cls.pg_start()
+
+ def create_packet(self, src_if, dst_if, do_dot1=True):
+ packet_sizes = [64, 512, 1518, 9018]
+ dst_host = random.choice(self.hosts_by_pg_idx[dst_if.sw_if_index])
+ src_host = random.choice(self.hosts_by_pg_idx[src_if.sw_if_index])
+ pkt_info = self.create_packet_info(src_if, dst_if)
+ payload = self.info_to_payload(pkt_info)
+ p = (Ether(dst=dst_host.mac, src=src_host.mac) /
+ IP(src=src_host.ip4, dst=dst_host.ip4) /
+ UDP(sport=1234, dport=1234) /
+ Raw(payload))
+ pkt_info.data = p.copy()
+ if do_dot1 and hasattr(src_if, 'sub_if'):
+ p = src_if.sub_if.add_dot1_layer(p)
+ size = random.choice(packet_sizes)
+ self.extend_packet(p, size)
+ return p
+
+ def _add_tag(self, packet, vlan, tag_type):
+ payload = packet.payload
+ inner_type = packet.type
+ packet.remove_payload()
+ packet.add_payload(Dot1Q(vlan=vlan) / payload)
+ packet.payload.type = inner_type
+ packet.payload.vlan = vlan
+ packet.type = tag_type
+ return packet
+
+ def _remove_tag(self, packet, vlan=None, tag_type=None):
+ if tag_type:
+ self.assertEqual(packet.type, tag_type)
+
+ payload = packet.payload
+ if vlan:
+ self.assertEqual(payload.vlan, vlan)
+ inner_type = payload.type
+ payload = payload.payload
+ packet.remove_payload()
+ packet.add_payload(payload)
+ packet.type = inner_type
+
+ def add_tags(self, packet, tags):
+ for t in reversed(tags):
+ self._add_tag(packet, t.vlan, t.dot1)
+
+ def remove_tags(self, packet, tags):
+ for t in tags:
+ self._remove_tag(packet, t.vlan, t.dot1)
+
+ def vtr_test(self, swif, tags):
+ p = self.create_packet(swif, self.pg0)
+ swif.add_stream(p)
+ self.pg_enable_capture(self.pg_interfaces)
+ self.pg_start()
+ rx = self.pg0.get_capture(1)
+
+ if tags:
+ self.remove_tags(rx[0], tags)
+ self.assertTrue(Dot1Q not in rx[0])
+
+ if not tags:
+ return
+
+ i = VppDot1QSubint(self, self.pg0, tags[0].vlan)
+ self.vapi.sw_interface_set_l2_bridge(
+ i.sw_if_index, bd_id=self.bd_id, enable=1)
+ i.admin_up()
+
+ p = self.create_packet(self.pg0, swif, do_dot1=False)
+ self.add_tags(p, tags)
+ self.pg0.add_stream(p)
+ self.pg_enable_capture(self.pg_interfaces)
+ self.pg_start()
+ rx = swif.get_capture(1)
+ swif.sub_if.remove_dot1_layer(rx[0])
+ self.assertTrue(Dot1Q not in rx[0])
+
+ self.vapi.sw_interface_set_l2_bridge(
+ i.sw_if_index, bd_id=self.bd_id, enable=0)
+ i.remove_vpp_config()
+
+ def test_1ad_vtr_pop_1(self):
+ """ 1AD VTR pop 1 test
+ """
+ self.pg1.sub_if.set_vtr(L2_VTR_OP.L2_POP_1)
+ self.vtr_test(self.pg1, [Tag(dot1=DOT1Q, vlan=100)])
+
+ def test_1ad_vtr_pop_2(self):
+ """ 1AD VTR pop 2 test
+ """
+ self.pg1.sub_if.set_vtr(L2_VTR_OP.L2_POP_2)
+ self.vtr_test(self.pg1, [])
+
+ def test_1ad_vtr_push_1ad(self):
+ """ 1AD VTR push 1 1AD test
+ """
+ self.pg1.sub_if.set_vtr(L2_VTR_OP.L2_PUSH_1, tag=300)
+ self.vtr_test(self.pg1, [Tag(dot1=DOT1AD, vlan=300),
+ Tag(dot1=DOT1AD, vlan=200),
+ Tag(dot1=DOT1Q, vlan=100)])
+
+ def test_1ad_vtr_push_2ad(self):
+ """ 1AD VTR push 2 1AD test
+ """
+ self.pg1.sub_if.set_vtr(L2_VTR_OP.L2_PUSH_2, outer=400, inner=300)
+ self.vtr_test(self.pg1, [Tag(dot1=DOT1AD, vlan=400),
+ Tag(dot1=DOT1Q, vlan=300),
+ Tag(dot1=DOT1AD, vlan=200),
+ Tag(dot1=DOT1Q, vlan=100)])
+
+ def test_1ad_vtr_push_1q(self):
+ """ 1AD VTR push 1 1Q test
+ """
+ self.pg1.sub_if.set_vtr(L2_VTR_OP.L2_PUSH_1, tag=300, push1q=1)
+ self.vtr_test(self.pg1, [Tag(dot1=DOT1Q, vlan=300),
+ Tag(dot1=DOT1AD, vlan=200),
+ Tag(dot1=DOT1Q, vlan=100)])
+
+ def test_1ad_vtr_push_2q(self):
+ """ 1AD VTR push 2 1Q test
+ """
+ self.pg1.sub_if.set_vtr(L2_VTR_OP.L2_PUSH_2,
+ outer=400, inner=300, push1q=1)
+ self.vtr_test(self.pg1, [Tag(dot1=DOT1Q, vlan=400),
+ Tag(dot1=DOT1Q, vlan=300),
+ Tag(dot1=DOT1AD, vlan=200),
+ Tag(dot1=DOT1Q, vlan=100)])
+
+ def test_1ad_vtr_translate_1_1ad(self):
+ """ 1AD VTR translate 1 -> 1 1AD test
+ """
+ self.pg1.sub_if.set_vtr(L2_VTR_OP.L2_TRANSLATE_1_1, tag=300)
+ self.vtr_test(self.pg1, [Tag(dot1=DOT1AD, vlan=300),
+ Tag(dot1=DOT1Q, vlan=100)])
+
+ def test_1ad_vtr_translate_1_2ad(self):
+ """ 1AD VTR translate 1 -> 2 1AD test
+ """
+ self.pg1.sub_if.set_vtr(
+ L2_VTR_OP.L2_TRANSLATE_1_2, inner=300, outer=400)
+ self.vtr_test(self.pg1, [Tag(dot1=DOT1AD, vlan=400),
+ Tag(dot1=DOT1Q, vlan=300),
+ Tag(dot1=DOT1Q, vlan=100)])
+
+ def test_1ad_vtr_translate_2_1ad(self):
+ """ 1AD VTR translate 2 -> 1 1AD test
+ """
+ self.pg1.sub_if.set_vtr(L2_VTR_OP.L2_TRANSLATE_2_1, tag=300)
+ self.vtr_test(self.pg1, [Tag(dot1=DOT1AD, vlan=300)])
+
+ def test_1ad_vtr_translate_2_2ad(self):
+ """ 1AD VTR translate 2 -> 2 1AD test
+ """
+ self.pg1.sub_if.set_vtr(
+ L2_VTR_OP.L2_TRANSLATE_2_2, inner=300, outer=400)
+ self.vtr_test(self.pg1, [Tag(dot1=DOT1AD, vlan=400),
+ Tag(dot1=DOT1Q, vlan=300)])
+
+ def test_1ad_vtr_translate_1_1q(self):
+ """ 1AD VTR translate 1 -> 1 1Q test
+ """
+ self.pg1.sub_if.set_vtr(L2_VTR_OP.L2_TRANSLATE_1_1, tag=300, push1q=1)
+ self.vtr_test(self.pg1, [Tag(dot1=DOT1Q, vlan=300),
+ Tag(dot1=DOT1Q, vlan=100)])
+
+ def test_1ad_vtr_translate_1_2q(self):
+ """ 1AD VTR translate 1 -> 2 1Q test
+ """
+ self.pg1.sub_if.set_vtr(
+ L2_VTR_OP.L2_TRANSLATE_1_2, inner=300, outer=400, push1q=1)
+ self.vtr_test(self.pg1, [Tag(dot1=DOT1Q, vlan=400),
+ Tag(dot1=DOT1Q, vlan=300),
+ Tag(dot1=DOT1Q, vlan=100)])
+
+ def test_1ad_vtr_translate_2_1q(self):
+ """ 1AD VTR translate 2 -> 1 1Q test
+ """
+ self.pg1.sub_if.set_vtr(L2_VTR_OP.L2_TRANSLATE_2_1, tag=300, push1q=1)
+ self.vtr_test(self.pg1, [Tag(dot1=DOT1Q, vlan=300)])
+
+ def test_1ad_vtr_translate_2_2q(self):
+ """ 1AD VTR translate 2 -> 2 1Q test
+ """
+ self.pg1.sub_if.set_vtr(
+ L2_VTR_OP.L2_TRANSLATE_2_2, inner=300, outer=400, push1q=1)
+ self.vtr_test(self.pg1, [Tag(dot1=DOT1Q, vlan=400),
+ Tag(dot1=DOT1Q, vlan=300)])
+
+ def test_1q_vtr_pop_1(self):
+ """ 1Q VTR pop 1 test
+ """
+ self.pg2.sub_if.set_vtr(L2_VTR_OP.L2_POP_1)
+ self.vtr_test(self.pg2, [])
+
+ def test_1q_vtr_push_1(self):
+ """ 1Q VTR push 1 test
+ """
+ self.pg2.sub_if.set_vtr(L2_VTR_OP.L2_PUSH_1, tag=300)
+ self.vtr_test(self.pg2, [Tag(dot1=DOT1AD, vlan=300),
+ Tag(dot1=DOT1Q, vlan=200)])
+
+ def test_1q_vtr_push_2(self):
+ """ 1Q VTR push 2 test
+ """
+ self.pg2.sub_if.set_vtr(L2_VTR_OP.L2_PUSH_2, outer=400, inner=300)
+ self.vtr_test(self.pg2, [Tag(dot1=DOT1AD, vlan=400),
+ Tag(dot1=DOT1Q, vlan=300),
+ Tag(dot1=DOT1Q, vlan=200)])
+
+ def test_1q_vtr_translate_1_1(self):
+ """ 1Q VTR translate 1 -> 1 test
+ """
+ self.pg2.sub_if.set_vtr(L2_VTR_OP.L2_TRANSLATE_1_1, tag=300)
+ self.vtr_test(self.pg2, [Tag(dot1=DOT1AD, vlan=300)])
+
+ def test_1q_vtr_translate_1_2(self):
+ """ 1Q VTR translate 1 -> 2 test
+ """
+ self.pg2.sub_if.set_vtr(
+ L2_VTR_OP.L2_TRANSLATE_1_2, inner=300, outer=400)
+ self.vtr_test(self.pg2, [Tag(dot1=DOT1AD, vlan=400),
+ Tag(dot1=DOT1Q, vlan=300)])
+
+
+if __name__ == '__main__':
+ unittest.main(testRunner=VppTestRunner)
diff --git a/test/test_vxlan.py b/test/test_vxlan.py
new file mode 100644
index 00000000..6bdcb258
--- /dev/null
+++ b/test/test_vxlan.py
@@ -0,0 +1,234 @@
+#!/usr/bin/env python
+
+import socket
+from util import ip4n_range
+import unittest
+from framework import VppTestCase, VppTestRunner
+from template_bd import BridgeDomain
+
+from scapy.layers.l2 import Ether
+from scapy.layers.inet import IP, UDP
+from scapy.layers.vxlan import VXLAN
+from scapy.utils import atol
+
+
+class TestVxlan(BridgeDomain, VppTestCase):
+ """ VXLAN Test Case """
+
+ def __init__(self, *args):
+ BridgeDomain.__init__(self)
+ VppTestCase.__init__(self, *args)
+
+ def encapsulate(self, pkt, vni):
+ """
+ Encapsulate the original payload frame by adding VXLAN header with its
+ UDP, IP and Ethernet fields
+ """
+ return (Ether(src=self.pg0.remote_mac, dst=self.pg0.local_mac) /
+ IP(src=self.pg0.remote_ip4, dst=self.pg0.local_ip4) /
+ UDP(sport=self.dport, dport=self.dport, chksum=0) /
+ VXLAN(vni=vni, flags=self.flags) /
+ pkt)
+
+ def encap_mcast(self, pkt, src_ip, src_mac, vni):
+ """
+ Encapsulate the original payload frame by adding VXLAN header with its
+ UDP, IP and Ethernet fields
+ """
+ return (Ether(src=src_mac, dst=self.mcast_mac) /
+ IP(src=src_ip, dst=self.mcast_ip4) /
+ UDP(sport=self.dport, dport=self.dport, chksum=0) /
+ VXLAN(vni=vni, flags=self.flags) /
+ pkt)
+
+ def decapsulate(self, pkt):
+ """
+ Decapsulate the original payload frame by removing VXLAN header
+ """
+ # check if is set I flag
+ self.assertEqual(pkt[VXLAN].flags, int('0x8', 16))
+ return pkt[VXLAN].payload
+
+ # Method for checking VXLAN encapsulation.
+ #
+ def check_encapsulation(self, pkt, vni, local_only=False, mcast_pkt=False):
+ # TODO: add error messages
+ # Verify source MAC is VPP_MAC and destination MAC is MY_MAC resolved
+ # by VPP using ARP.
+ self.assertEqual(pkt[Ether].src, self.pg0.local_mac)
+ if not local_only:
+ if not mcast_pkt:
+ self.assertEqual(pkt[Ether].dst, self.pg0.remote_mac)
+ else:
+ self.assertEqual(pkt[Ether].dst, type(self).mcast_mac)
+ # Verify VXLAN tunnel source IP is VPP_IP and destination IP is MY_IP.
+ self.assertEqual(pkt[IP].src, self.pg0.local_ip4)
+ if not local_only:
+ if not mcast_pkt:
+ self.assertEqual(pkt[IP].dst, self.pg0.remote_ip4)
+ else:
+ self.assertEqual(pkt[IP].dst, type(self).mcast_ip4)
+ # Verify UDP destination port is VXLAN 4789, source UDP port could be
+ # arbitrary.
+ self.assertEqual(pkt[UDP].dport, type(self).dport)
+ # TODO: checksum check
+ # Verify VNI
+ self.assertEqual(pkt[VXLAN].vni, vni)
+
+ @classmethod
+ def create_vxlan_flood_test_bd(cls, vni, n_ucast_tunnels):
+ # Create 10 ucast vxlan tunnels under bd
+ ip_range_start = 10
+ ip_range_end = ip_range_start + n_ucast_tunnels
+ next_hop_address = cls.pg0.remote_ip4n
+ for dest_ip4n in ip4n_range(next_hop_address, ip_range_start,
+ ip_range_end):
+ # add host route so dest_ip4n will not be resolved
+ cls.vapi.ip_add_del_route(dest_ip4n, 32, next_hop_address)
+ r = cls.vapi.vxlan_add_del_tunnel(
+ src_addr=cls.pg0.local_ip4n,
+ dst_addr=dest_ip4n,
+ vni=vni)
+ cls.vapi.sw_interface_set_l2_bridge(r.sw_if_index, bd_id=vni)
+
+ @classmethod
+ def add_del_shared_mcast_dst_load(cls, is_add):
+ """
+ add or del tunnels sharing the same mcast dst
+ to test vxlan ref_count mechanism
+ """
+ n_shared_dst_tunnels = 2000
+ vni_start = 10000
+ vni_end = vni_start + n_shared_dst_tunnels
+ for vni in range(vni_start, vni_end):
+ r = cls.vapi.vxlan_add_del_tunnel(
+ src_addr=cls.pg0.local_ip4n,
+ dst_addr=cls.mcast_ip4n,
+ mcast_sw_if_index=1,
+ vni=vni,
+ is_add=is_add)
+ if r.sw_if_index == 0xffffffff:
+ raise "bad sw_if_index"
+
+ @classmethod
+ def add_shared_mcast_dst_load(cls):
+ cls.add_del_shared_mcast_dst_load(is_add=1)
+
+ @classmethod
+ def del_shared_mcast_dst_load(cls):
+ cls.add_del_shared_mcast_dst_load(is_add=0)
+
+ @classmethod
+ def add_del_mcast_tunnels_load(cls, is_add):
+ """
+ add or del tunnels to test vxlan stability
+ """
+ n_distinct_dst_tunnels = 200
+ ip_range_start = 10
+ ip_range_end = ip_range_start + n_distinct_dst_tunnels
+ for dest_ip4n in ip4n_range(cls.mcast_ip4n, ip_range_start,
+ ip_range_end):
+ vni = bytearray(dest_ip4n)[3]
+ cls.vapi.vxlan_add_del_tunnel(
+ src_addr=cls.pg0.local_ip4n,
+ dst_addr=dest_ip4n,
+ mcast_sw_if_index=1,
+ vni=vni,
+ is_add=is_add)
+
+ @classmethod
+ def add_mcast_tunnels_load(cls):
+ cls.add_del_mcast_tunnels_load(is_add=1)
+
+ @classmethod
+ def del_mcast_tunnels_load(cls):
+ cls.add_del_mcast_tunnels_load(is_add=0)
+
+ # Class method to start the VXLAN test case.
+ # Overrides setUpClass method in VppTestCase class.
+ # Python try..except statement is used to ensure that the tear down of
+ # the class will be executed even if exception is raised.
+ # @param cls The class pointer.
+ @classmethod
+ def setUpClass(cls):
+ super(TestVxlan, cls).setUpClass()
+
+ try:
+ cls.dport = 4789
+ cls.flags = 0x8
+
+ # Create 2 pg interfaces.
+ cls.create_pg_interfaces(range(4))
+ for pg in cls.pg_interfaces:
+ pg.admin_up()
+
+ # Configure IPv4 addresses on VPP pg0.
+ cls.pg0.config_ip4()
+
+ # Resolve MAC address for VPP's IP address on pg0.
+ cls.pg0.resolve_arp()
+
+ # Our Multicast address
+ cls.mcast_ip4 = '239.1.1.1'
+ cls.mcast_ip4n = socket.inet_pton(socket.AF_INET, cls.mcast_ip4)
+ iplong = atol(cls.mcast_ip4)
+ cls.mcast_mac = "01:00:5e:%02x:%02x:%02x" % (
+ (iplong >> 16) & 0x7F, (iplong >> 8) & 0xFF, iplong & 0xFF)
+
+ # Create VXLAN VTEP on VPP pg0, and put vxlan_tunnel0 and pg1
+ # into BD.
+ cls.single_tunnel_bd = 1
+ r = cls.vapi.vxlan_add_del_tunnel(
+ src_addr=cls.pg0.local_ip4n,
+ dst_addr=cls.pg0.remote_ip4n,
+ vni=cls.single_tunnel_bd)
+ cls.vapi.sw_interface_set_l2_bridge(r.sw_if_index,
+ bd_id=cls.single_tunnel_bd)
+ cls.vapi.sw_interface_set_l2_bridge(cls.pg1.sw_if_index,
+ bd_id=cls.single_tunnel_bd)
+
+ # Setup vni 2 to test multicast flooding
+ cls.n_ucast_tunnels = 10
+ cls.mcast_flood_bd = 2
+ cls.create_vxlan_flood_test_bd(cls.mcast_flood_bd,
+ cls.n_ucast_tunnels)
+ r = cls.vapi.vxlan_add_del_tunnel(
+ src_addr=cls.pg0.local_ip4n,
+ dst_addr=cls.mcast_ip4n,
+ mcast_sw_if_index=1,
+ vni=cls.mcast_flood_bd)
+ cls.vapi.sw_interface_set_l2_bridge(r.sw_if_index,
+ bd_id=cls.mcast_flood_bd)
+ cls.vapi.sw_interface_set_l2_bridge(cls.pg2.sw_if_index,
+ bd_id=cls.mcast_flood_bd)
+
+ # Add and delete mcast tunnels to check stability
+ cls.add_shared_mcast_dst_load()
+ cls.add_mcast_tunnels_load()
+ cls.del_shared_mcast_dst_load()
+ cls.del_mcast_tunnels_load()
+
+ # Setup vni 3 to test unicast flooding
+ cls.ucast_flood_bd = 3
+ cls.create_vxlan_flood_test_bd(cls.ucast_flood_bd,
+ cls.n_ucast_tunnels)
+ cls.vapi.sw_interface_set_l2_bridge(cls.pg3.sw_if_index,
+ bd_id=cls.ucast_flood_bd)
+ except Exception:
+ super(TestVxlan, cls).tearDownClass()
+ raise
+
+ # Method to define VPP actions before tear down of the test case.
+ # Overrides tearDown method in VppTestCase class.
+ # @param self The object pointer.
+ def tearDown(self):
+ super(TestVxlan, self).tearDown()
+ if not self.vpp_dead:
+ self.logger.info(self.vapi.cli("show bridge-domain 1 detail"))
+ self.logger.info(self.vapi.cli("show bridge-domain 2 detail"))
+ self.logger.info(self.vapi.cli("show bridge-domain 3 detail"))
+ self.logger.info(self.vapi.cli("show vxlan tunnel"))
+
+
+if __name__ == '__main__':
+ unittest.main(testRunner=VppTestRunner)
diff --git a/test/test_vxlan_gpe.py b/test/test_vxlan_gpe.py
new file mode 100644
index 00000000..40975bf1
--- /dev/null
+++ b/test/test_vxlan_gpe.py
@@ -0,0 +1,313 @@
+#!/usr/bin/env python
+
+import socket
+from util import ip4n_range
+import unittest
+from framework import VppTestCase, VppTestRunner, running_extended_tests
+from template_bd import BridgeDomain
+
+from scapy.layers.l2 import Ether, Raw
+from scapy.layers.inet import IP, UDP
+from scapy.layers.vxlan import VXLAN
+from scapy.utils import atol
+
+
+@unittest.skipUnless(running_extended_tests(), "part of extended tests")
+class TestVxlanGpe(BridgeDomain, VppTestCase):
+ """ VXLAN-GPE Test Case """
+
+ def __init__(self, *args):
+ BridgeDomain.__init__(self)
+ VppTestCase.__init__(self, *args)
+
+ def encapsulate(self, pkt, vni):
+ """
+ Encapsulate the original payload frame by adding VXLAN-GPE header
+ with its UDP, IP and Ethernet fields
+ """
+ return (Ether(src=self.pg0.remote_mac, dst=self.pg0.local_mac) /
+ IP(src=self.pg0.remote_ip4, dst=self.pg0.local_ip4) /
+ UDP(sport=self.dport, dport=self.dport, chksum=0) /
+ VXLAN(vni=vni, flags=self.flags) /
+ pkt)
+
+ def encap_mcast(self, pkt, src_ip, src_mac, vni):
+ """
+ Encapsulate the original payload frame by adding VXLAN-GPE header
+ with its UDP, IP and Ethernet fields
+ """
+ return (Ether(src=src_mac, dst=self.mcast_mac) /
+ IP(src=src_ip, dst=self.mcast_ip4) /
+ UDP(sport=self.dport, dport=self.dport, chksum=0) /
+ VXLAN(vni=vni, flags=self.flags) /
+ pkt)
+
+ def decapsulate(self, pkt):
+ """
+ Decapsulate the original payload frame by removing VXLAN-GPE header
+ """
+ # check if is set I and P flag
+ self.assertEqual(pkt[VXLAN].flags, int('0xc', 16))
+ return pkt[VXLAN].payload
+
+ # Method for checking VXLAN-GPE encapsulation.
+ #
+ def check_encapsulation(self, pkt, vni, local_only=False, mcast_pkt=False):
+ # Verify source MAC is VPP_MAC and destination MAC is MY_MAC resolved
+ # by VPP using ARP.
+ self.assertEqual(pkt[Ether].src, self.pg0.local_mac)
+ if not local_only:
+ if not mcast_pkt:
+ self.assertEqual(pkt[Ether].dst, self.pg0.remote_mac)
+ else:
+ self.assertEqual(pkt[Ether].dst, type(self).mcast_mac)
+ # Verify VXLAN-GPE tunnel src IP is VPP_IP and dst IP is MY_IP.
+ self.assertEqual(pkt[IP].src, self.pg0.local_ip4)
+ if not local_only:
+ if not mcast_pkt:
+ self.assertEqual(pkt[IP].dst, self.pg0.remote_ip4)
+ else:
+ self.assertEqual(pkt[IP].dst, type(self).mcast_ip4)
+ # Verify UDP destination port is VXLAN-GPE 4790, source UDP port
+ # could be arbitrary.
+ self.assertEqual(pkt[UDP].dport, type(self).dport)
+ # Verify VNI
+ self.assertEqual(pkt[VXLAN].VNI, vni)
+
+ def test_decap(self):
+ """ Decapsulation test
+ Send encapsulated frames from pg0
+ Verify receipt of decapsulated frames on pg1
+ """
+
+ encapsulated_pkt = self.encapsulate(self.frame_request,
+ self.single_tunnel_bd)
+
+ self.pg0.add_stream([encapsulated_pkt, ])
+
+ self.pg1.enable_capture()
+
+ self.pg_start()
+
+ # Pick first received frame and check if it's the non-encapsulated
+ # frame
+ out = self.pg1.get_capture(1)
+ pkt = out[0]
+ self.assert_eq_pkts(pkt, self.frame_request)
+
+ def test_encap(self):
+ """ Encapsulation test
+ Send frames from pg1
+ Verify receipt of encapsulated frames on pg0
+ """
+ self.pg1.add_stream([self.frame_reply])
+
+ self.pg0.enable_capture()
+
+ self.pg_start()
+
+ # Pick first received frame and check if it's corectly encapsulated.
+ out = self.pg0.get_capture(1)
+ pkt = out[0]
+ self.check_encapsulation(pkt, self.single_tunnel_bd)
+
+ # payload = self.decapsulate(pkt)
+ # self.assert_eq_pkts(payload, self.frame_reply)
+
+ def test_ucast_flood(self):
+ """ Unicast flood test
+ Send frames from pg3
+ Verify receipt of encapsulated frames on pg0
+ """
+ self.pg3.add_stream([self.frame_reply])
+
+ self.pg0.enable_capture()
+
+ self.pg_start()
+
+ # Get packet from each tunnel and assert it's corectly encapsulated.
+ out = self.pg0.get_capture(self.n_ucast_tunnels)
+ for pkt in out:
+ self.check_encapsulation(pkt, self.ucast_flood_bd, True)
+ # payload = self.decapsulate(pkt)
+ # self.assert_eq_pkts(payload, self.frame_reply)
+
+ def test_mcast_flood(self):
+ """ Multicast flood test
+ Send frames from pg2
+ Verify receipt of encapsulated frames on pg0
+ """
+ self.pg2.add_stream([self.frame_reply])
+
+ self.pg0.enable_capture()
+
+ self.pg_start()
+
+ # Pick first received frame and check if it's corectly encapsulated.
+ out = self.pg0.get_capture(1)
+ pkt = out[0]
+ self.check_encapsulation(pkt, self.mcast_flood_bd,
+ local_only=False, mcast_pkt=True)
+
+ # payload = self.decapsulate(pkt)
+ # self.assert_eq_pkts(payload, self.frame_reply)
+
+ @classmethod
+ def create_vxlan_gpe_flood_test_bd(cls, vni, n_ucast_tunnels):
+ # Create 10 ucast vxlan_gpe tunnels under bd
+ ip_range_start = 10
+ ip_range_end = ip_range_start + n_ucast_tunnels
+ next_hop_address = cls.pg0.remote_ip4n
+ for dest_ip4n in ip4n_range(next_hop_address, ip_range_start,
+ ip_range_end):
+ # add host route so dest_ip4n will not be resolved
+ cls.vapi.ip_add_del_route(dest_ip4n, 32, next_hop_address)
+ r = cls.vapi.vxlan_gpe_add_del_tunnel(
+ src_addr=cls.pg0.local_ip4n,
+ dst_addr=dest_ip4n,
+ vni=vni)
+ cls.vapi.sw_interface_set_l2_bridge(r.sw_if_index, bd_id=vni)
+
+ @classmethod
+ def add_del_shared_mcast_dst_load(cls, is_add):
+ """
+ add or del tunnels sharing the same mcast dst
+ to test vxlan_gpe ref_count mechanism
+ """
+ n_shared_dst_tunnels = 20
+ vni_start = 1000
+ vni_end = vni_start + n_shared_dst_tunnels
+ for vni in range(vni_start, vni_end):
+ r = cls.vapi.vxlan_gpe_add_del_tunnel(
+ src_addr=cls.pg0.local_ip4n,
+ dst_addr=cls.mcast_ip4n,
+ mcast_sw_if_index=1,
+ vni=vni,
+ is_add=is_add)
+ if r.sw_if_index == 0xffffffff:
+ raise "bad sw_if_index"
+
+ @classmethod
+ def add_shared_mcast_dst_load(cls):
+ cls.add_del_shared_mcast_dst_load(is_add=1)
+
+ @classmethod
+ def del_shared_mcast_dst_load(cls):
+ cls.add_del_shared_mcast_dst_load(is_add=0)
+
+ @classmethod
+ def add_del_mcast_tunnels_load(cls, is_add):
+ """
+ add or del tunnels to test vxlan_gpe stability
+ """
+ n_distinct_dst_tunnels = 20
+ ip_range_start = 10
+ ip_range_end = ip_range_start + n_distinct_dst_tunnels
+ for dest_ip4n in ip4n_range(cls.mcast_ip4n, ip_range_start,
+ ip_range_end):
+ vni = bytearray(dest_ip4n)[3]
+ cls.vapi.vxlan_gpe_add_del_tunnel(
+ src_addr=cls.pg0.local_ip4n,
+ dst_addr=dest_ip4n,
+ mcast_sw_if_index=1,
+ vni=vni,
+ is_add=is_add)
+
+ @classmethod
+ def add_mcast_tunnels_load(cls):
+ cls.add_del_mcast_tunnels_load(is_add=1)
+
+ @classmethod
+ def del_mcast_tunnels_load(cls):
+ cls.add_del_mcast_tunnels_load(is_add=0)
+
+ # Class method to start the VXLAN-GPE test case.
+ # Overrides setUpClass method in VppTestCase class.
+ # Python try..except statement is used to ensure that the tear down of
+ # the class will be executed even if exception is raised.
+ # @param cls The class pointer.
+ @classmethod
+ def setUpClass(cls):
+ super(TestVxlanGpe, cls).setUpClass()
+
+ try:
+ cls.dport = 4790
+ cls.flags = 0xc
+
+ # Create 2 pg interfaces.
+ cls.create_pg_interfaces(range(4))
+ for pg in cls.pg_interfaces:
+ pg.admin_up()
+
+ # Configure IPv4 addresses on VPP pg0.
+ cls.pg0.config_ip4()
+
+ # Resolve MAC address for VPP's IP address on pg0.
+ cls.pg0.resolve_arp()
+
+ # Our Multicast address
+ cls.mcast_ip4 = '239.1.1.1'
+ cls.mcast_ip4n = socket.inet_pton(socket.AF_INET, cls.mcast_ip4)
+ iplong = atol(cls.mcast_ip4)
+ cls.mcast_mac = "01:00:5e:%02x:%02x:%02x" % (
+ (iplong >> 16) & 0x7F, (iplong >> 8) & 0xFF, iplong & 0xFF)
+
+ # Create VXLAN-GPE VTEP on VPP pg0, and put vxlan_gpe_tunnel0
+ # and pg1 into BD.
+ cls.single_tunnel_bd = 11
+ r = cls.vapi.vxlan_gpe_add_del_tunnel(
+ src_addr=cls.pg0.local_ip4n,
+ dst_addr=cls.pg0.remote_ip4n,
+ vni=cls.single_tunnel_bd)
+ cls.vapi.sw_interface_set_l2_bridge(r.sw_if_index,
+ bd_id=cls.single_tunnel_bd)
+ cls.vapi.sw_interface_set_l2_bridge(cls.pg1.sw_if_index,
+ bd_id=cls.single_tunnel_bd)
+
+ # Setup vni 2 to test multicast flooding
+ cls.n_ucast_tunnels = 10
+ cls.mcast_flood_bd = 12
+ cls.create_vxlan_gpe_flood_test_bd(cls.mcast_flood_bd,
+ cls.n_ucast_tunnels)
+ r = cls.vapi.vxlan_gpe_add_del_tunnel(
+ src_addr=cls.pg0.local_ip4n,
+ dst_addr=cls.mcast_ip4n,
+ mcast_sw_if_index=1,
+ vni=cls.mcast_flood_bd)
+ cls.vapi.sw_interface_set_l2_bridge(r.sw_if_index,
+ bd_id=cls.mcast_flood_bd)
+ cls.vapi.sw_interface_set_l2_bridge(cls.pg2.sw_if_index,
+ bd_id=cls.mcast_flood_bd)
+
+ # Add and delete mcast tunnels to check stability
+ cls.add_shared_mcast_dst_load()
+ cls.add_mcast_tunnels_load()
+ cls.del_shared_mcast_dst_load()
+ cls.del_mcast_tunnels_load()
+
+ # Setup vni 3 to test unicast flooding
+ cls.ucast_flood_bd = 13
+ cls.create_vxlan_gpe_flood_test_bd(cls.ucast_flood_bd,
+ cls.n_ucast_tunnels)
+ cls.vapi.sw_interface_set_l2_bridge(cls.pg3.sw_if_index,
+ bd_id=cls.ucast_flood_bd)
+ except Exception:
+ super(TestVxlanGpe, cls).tearDownClass()
+ raise
+
+ # Method to define VPP actions before tear down of the test case.
+ # Overrides tearDown method in VppTestCase class.
+ # @param self The object pointer.
+ def tearDown(self):
+ super(TestVxlanGpe, self).tearDown()
+ if not self.vpp_dead:
+ self.logger.info(self.vapi.cli("show bridge-domain 11 detail"))
+ self.logger.info(self.vapi.cli("show bridge-domain 12 detail"))
+ self.logger.info(self.vapi.cli("show bridge-domain 13 detail"))
+ self.logger.info(self.vapi.cli("show int"))
+ self.logger.info(self.vapi.cli("show vxlan-gpe tunnel"))
+ self.logger.info(self.vapi.cli("show trace"))
+
+
+if __name__ == '__main__':
+ unittest.main(testRunner=VppTestRunner)
diff --git a/test/util.py b/test/util.py
new file mode 100644
index 00000000..3e0267a3
--- /dev/null
+++ b/test/util.py
@@ -0,0 +1,231 @@
+""" test framework utilities """
+
+import socket
+import sys
+from abc import abstractmethod, ABCMeta
+from cStringIO import StringIO
+from scapy.layers.inet6 import in6_mactoifaceid
+
+from scapy.layers.l2 import Ether
+from scapy.packet import Raw
+from scapy.layers.inet import IP, UDP, TCP
+from scapy.layers.inet6 import IPv6, ICMPv6Unknown, ICMPv6EchoRequest
+from scapy.packet import Packet
+from socket import inet_pton, AF_INET, AF_INET6
+
+
+def ppp(headline, packet):
+ """ Return string containing the output of scapy packet.show() call. """
+ o = StringIO()
+ old_stdout = sys.stdout
+ sys.stdout = o
+ print(headline)
+ packet.show()
+ sys.stdout = old_stdout
+ return o.getvalue()
+
+
+def ppc(headline, capture, limit=10):
+ """ Return string containing ppp() printout for a capture.
+
+ :param headline: printed as first line of output
+ :param capture: packets to print
+ :param limit: limit the print to # of packets
+ """
+ if not capture:
+ return headline
+ tail = ""
+ if limit < len(capture):
+ tail = "\nPrint limit reached, %s out of %s packets printed" % (
+ len(capture), limit)
+ limit = len(capture)
+ body = "".join([ppp("Packet #%s:" % count, p)
+ for count, p in zip(range(0, limit), capture)])
+ return "%s\n%s%s" % (headline, body, tail)
+
+
+def ip4_range(ip4, s, e):
+ tmp = ip4.rsplit('.', 1)[0]
+ return ("%s.%d" % (tmp, i) for i in range(s, e))
+
+
+def ip4n_range(ip4n, s, e):
+ ip4 = socket.inet_ntop(socket.AF_INET, ip4n)
+ return (socket.inet_pton(socket.AF_INET, ip)
+ for ip in ip4_range(ip4, s, e))
+
+
+def mactobinary(mac):
+ """ Convert the : separated format into binary packet data for the API """
+ return mac.replace(':', '').decode('hex')
+
+
+def mk_ll_addr(mac):
+ euid = in6_mactoifaceid(mac)
+ addr = "fe80::" + euid
+ return addr
+
+
+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. """
+
+ @property
+ def mac(self):
+ """ MAC address """
+ return self._mac
+
+ @property
+ def bin_mac(self):
+ """ MAC address """
+ return mactobinary(self._mac)
+
+ @property
+ def ip4(self):
+ """ IPv4 address - string """
+ return self._ip4
+
+ @property
+ def ip4n(self):
+ """ IPv4 address of remote host - raw, suitable as API parameter."""
+ return socket.inet_pton(socket.AF_INET, self._ip4)
+
+ @property
+ def ip6(self):
+ """ IPv6 address - string """
+ return self._ip6
+
+ @property
+ def ip6n(self):
+ """ IPv6 address of remote host - raw, suitable as API parameter."""
+ return socket.inet_pton(socket.AF_INET6, self._ip6)
+
+ @property
+ def ip6_ll(self):
+ """ IPv6 link-local address - string """
+ return self._ip6_ll
+
+ @property
+ def ip6n_ll(self):
+ """ IPv6 link-local address of remote host -
+ raw, suitable as API parameter."""
+ return socket.inet_pton(socket.AF_INET6, self._ip6_ll)
+
+ def __eq__(self, other):
+ if isinstance(other, Host):
+ return (self.mac == other.mac and
+ self.ip4 == other.ip4 and
+ self.ip6 == other.ip6 and
+ self.ip6_ll == other.ip6_ll)
+ else:
+ return False
+
+ def __ne__(self, other):
+ return not self.__eq__(other)
+
+ def __repr__(self):
+ return "Host { mac:%s ip4:%s ip6:%s ip6_ll:%s }" % (self.mac,
+ self.ip4,
+ self.ip6,
+ self.ip6_ll)
+
+ def __hash__(self):
+ return hash(self.__repr__())
+
+ def __init__(self, mac=None, ip4=None, ip6=None, ip6_ll=None):
+ self._mac = mac
+ self._ip4 = ip4
+ self._ip6 = ip6
+ self._ip6_ll = ip6_ll
+
+
+class ForeignAddressFactory(object):
+ count = 0
+ prefix_len = 24
+ net_template = '10.10.10.{}'
+ net = net_template.format(0) + '/' + str(prefix_len)
+
+ def get_ip4(self):
+ if self.count > 255:
+ raise Exception("Network host address exhaustion")
+ self.count += 1
+ return self.net_template.format(self.count)
+
+
+class L4_Conn():
+ """ L4 'connection' tied to two VPP interfaces """
+ def __init__(self, testcase, if1, if2, af, l4proto, port1, port2):
+ self.testcase = testcase
+ self.ifs = [None, None]
+ self.ifs[0] = if1
+ self.ifs[1] = if2
+ self.address_family = af
+ self.l4proto = l4proto
+ self.ports = [None, None]
+ self.ports[0] = port1
+ self.ports[1] = port2
+ self
+
+ def pkt(self, side, l4args={}, payload="x"):
+ is_ip6 = 1 if self.address_family == AF_INET6 else 0
+ s0 = side
+ s1 = 1-side
+ src_if = self.ifs[s0]
+ dst_if = self.ifs[s1]
+ layer_3 = [IP(src=src_if.remote_ip4, dst=dst_if.remote_ip4),
+ IPv6(src=src_if.remote_ip6, dst=dst_if.remote_ip6)]
+ merged_l4args = {'sport': self.ports[s0], 'dport': self.ports[s1]}
+ merged_l4args.update(l4args)
+ p = (Ether(dst=src_if.local_mac, src=src_if.remote_mac) /
+ layer_3[is_ip6] /
+ self.l4proto(**merged_l4args) /
+ Raw(payload))
+ return p
+
+ def send(self, side, flags=None, payload=""):
+ l4args = {}
+ if flags is not None:
+ l4args['flags'] = flags
+ self.ifs[side].add_stream(self.pkt(side,
+ l4args=l4args, payload=payload))
+ self.ifs[1-side].enable_capture()
+ self.testcase.pg_start()
+
+ def recv(self, side):
+ p = self.ifs[side].wait_for_packet(1)
+ return p
+
+ def send_through(self, side, flags=None, payload=""):
+ self.send(side, flags, payload)
+ p = self.recv(1-side)
+ return p
+
+ def send_pingpong(self, side, flags1=None, flags2=None):
+ p1 = self.send_through(side, flags1)
+ p2 = self.send_through(1-side, flags2)
+ return [p1, p2]
+
+
+class L4_CONN_SIDE:
+ L4_CONN_SIDE_ZERO = 0
+ L4_CONN_SIDE_ONE = 1
diff --git a/test/vpp_gre_interface.py b/test/vpp_gre_interface.py
new file mode 100644
index 00000000..1c71875f
--- /dev/null
+++ b/test/vpp_gre_interface.py
@@ -0,0 +1,71 @@
+
+from vpp_interface import VppInterface
+import socket
+
+
+class VppGreInterface(VppInterface):
+ """
+ VPP GRE interface
+ """
+
+ def __init__(self, test, src_ip, dst_ip, outer_fib_id=0, is_teb=0):
+ """ Create VPP loopback interface """
+ self._sw_if_index = 0
+ super(VppGreInterface, self).__init__(test)
+ self._test = test
+ self.t_src = src_ip
+ self.t_dst = dst_ip
+ self.t_outer_fib = outer_fib_id
+ self.t_is_teb = is_teb
+
+ def add_vpp_config(self):
+ s = socket.inet_pton(socket.AF_INET, self.t_src)
+ d = socket.inet_pton(socket.AF_INET, self.t_dst)
+ r = self.test.vapi.gre_tunnel_add_del(s, d,
+ outer_fib_id=self.t_outer_fib,
+ is_teb=self.t_is_teb)
+ self._sw_if_index = r.sw_if_index
+ self.generate_remote_hosts()
+
+ def remove_vpp_config(self):
+ s = socket.inet_pton(socket.AF_INET, self.t_src)
+ d = socket.inet_pton(socket.AF_INET, self.t_dst)
+ self.unconfig()
+ r = self.test.vapi.gre_tunnel_add_del(s, d,
+ outer_fib_id=self.t_outer_fib,
+ is_add=0)
+
+
+class VppGre6Interface(VppInterface):
+ """
+ VPP GRE IPv6 interface
+ """
+
+ def __init__(self, test, src_ip, dst_ip, outer_fib_id=0, is_teb=0):
+ """ Create VPP loopback interface """
+ self._sw_if_index = 0
+ super(VppGre6Interface, self).__init__(test)
+ self._test = test
+ self.t_src = src_ip
+ self.t_dst = dst_ip
+ self.t_outer_fib = outer_fib_id
+ self.t_is_teb = is_teb
+
+ def add_vpp_config(self):
+ s = socket.inet_pton(socket.AF_INET6, self.t_src)
+ d = socket.inet_pton(socket.AF_INET6, self.t_dst)
+ r = self.test.vapi.gre_tunnel_add_del(s, d,
+ outer_fib_id=self.t_outer_fib,
+ is_teb=self.t_is_teb,
+ is_ip6=1)
+ self._sw_if_index = r.sw_if_index
+ self.generate_remote_hosts()
+
+ def remove_vpp_config(self):
+ s = socket.inet_pton(socket.AF_INET6, self.t_src)
+ d = socket.inet_pton(socket.AF_INET6, self.t_dst)
+ self.unconfig()
+ r = self.test.vapi.gre_tunnel_add_del(s, d,
+ outer_fib_id=self.t_outer_fib,
+ is_add=0,
+ is_ip6=1)
diff --git a/test/vpp_interface.py b/test/vpp_interface.py
new file mode 100644
index 00000000..b8505ce3
--- /dev/null
+++ b/test/vpp_interface.py
@@ -0,0 +1,374 @@
+from abc import abstractmethod, ABCMeta
+import socket
+
+from util import Host, mk_ll_addr
+from vpp_neighbor import VppNeighbor
+
+
+class VppInterface(object):
+ """Generic VPP interface."""
+ __metaclass__ = ABCMeta
+
+ @property
+ def sw_if_index(self):
+ """Interface index assigned by VPP."""
+ return self._sw_if_index
+
+ @property
+ def remote_mac(self):
+ """MAC-address of the remote interface "connected" to this interface"""
+ return self._remote_hosts[0].mac
+
+ @property
+ def local_mac(self):
+ """MAC-address of the VPP interface."""
+ return self._local_mac
+
+ @property
+ def local_ip4(self):
+ """Local IPv4 address on VPP interface (string)."""
+ return self._local_ip4
+
+ @property
+ def local_ip4n(self):
+ """Local IPv4 address - raw, suitable as API parameter."""
+ return socket.inet_pton(socket.AF_INET, self._local_ip4)
+
+ @property
+ def remote_ip4(self):
+ """IPv4 address of remote peer "connected" to this interface."""
+ return self._remote_hosts[0].ip4
+
+ @property
+ def remote_ip4n(self):
+ """IPv4 address of remote peer - raw, suitable as API parameter."""
+ return socket.inet_pton(socket.AF_INET, self.remote_ip4)
+
+ @property
+ def local_ip6(self):
+ """Local IPv6 address on VPP interface (string)."""
+ return self._local_ip6
+
+ @property
+ def local_ip6n(self):
+ """Local IPv6 address - raw, suitable as API parameter."""
+ return socket.inet_pton(socket.AF_INET6, self.local_ip6)
+
+ @property
+ def local_ip6_ll(self):
+ """Local IPv6 linnk-local address on VPP interface (string)."""
+ return self._local_ip6_ll
+
+ @property
+ def local_ip6n_ll(self):
+ """Local IPv6 link-local address - raw, suitable as API parameter."""
+ return self.local_ip6n_ll
+
+ @property
+ def remote_ip6(self):
+ """IPv6 address of remote peer "connected" to this interface."""
+ return self._remote_hosts[0].ip6
+
+ @property
+ def remote_ip6n(self):
+ """IPv6 address of remote peer - raw, suitable as API parameter"""
+ return socket.inet_pton(socket.AF_INET6, self.remote_ip6)
+
+ @property
+ def name(self):
+ """Name of the interface."""
+ return self._name
+
+ @property
+ def dump(self):
+ """RAW result of sw_interface_dump for this interface."""
+ return self._dump
+
+ @property
+ def test(self):
+ """Test case creating this interface."""
+ return self._test
+
+ @property
+ def remote_hosts(self):
+ """Remote hosts list"""
+ return self._remote_hosts
+
+ @remote_hosts.setter
+ def remote_hosts(self, value):
+ """
+ :param list value: List of remote hosts.
+ """
+ self._remote_hosts = value
+ self._hosts_by_mac = {}
+ self._hosts_by_ip4 = {}
+ self._hosts_by_ip6 = {}
+ for host in self._remote_hosts:
+ self._hosts_by_mac[host.mac] = host
+ self._hosts_by_ip4[host.ip4] = host
+ self._hosts_by_ip6[host.ip6] = host
+
+ def host_by_mac(self, mac):
+ """
+ :param mac: MAC address to find host by.
+ :return: Host object assigned to interface.
+ """
+ return self._hosts_by_mac[mac]
+
+ def host_by_ip4(self, ip):
+ """
+ :param ip: IPv4 address to find host by.
+ :return: Host object assigned to interface.
+ """
+ return self._hosts_by_ip4[ip]
+
+ def host_by_ip6(self, ip):
+ """
+ :param ip: IPv6 address to find host by.
+ :return: Host object assigned to interface.
+ """
+ return self._hosts_by_ip6[ip]
+
+ def generate_remote_hosts(self, count=1):
+ """Generate and add remote hosts for the interface.
+
+ :param int count: Number of generated remote hosts.
+ """
+ self._remote_hosts = []
+ self._hosts_by_mac = {}
+ self._hosts_by_ip4 = {}
+ self._hosts_by_ip6 = {}
+ for i in range(
+ 2, count + 2): # 0: network address, 1: local vpp address
+ mac = "02:%02x:00:00:ff:%02x" % (self.sw_if_index, i)
+ ip4 = "172.16.%u.%u" % (self.sw_if_index, i)
+ ip6 = "fd01:%x::%x" % (self.sw_if_index, i)
+ ip6_ll = mk_ll_addr(mac)
+ host = Host(mac, ip4, ip6, ip6_ll)
+ self._remote_hosts.append(host)
+ self._hosts_by_mac[mac] = host
+ self._hosts_by_ip4[ip4] = host
+ self._hosts_by_ip6[ip6] = host
+
+ @abstractmethod
+ def __init__(self, test):
+ self._test = test
+
+ self._remote_hosts = []
+ self._hosts_by_mac = {}
+ self._hosts_by_ip4 = {}
+ self._hosts_by_ip6 = {}
+
+ self.generate_remote_hosts()
+
+ self._local_ip4 = "172.16.%u.1" % self.sw_if_index
+ self._local_ip4n = socket.inet_pton(socket.AF_INET, self.local_ip4)
+ self._local_ip4_subnet = "172.16.%u.0" % self.sw_if_index
+ self._local_ip4n_subnet = socket.inet_pton(socket.AF_INET,
+ self._local_ip4_subnet)
+ self._local_ip4_bcast = "172.16.%u.255" % self.sw_if_index
+ self._local_ip4n_bcast = socket.inet_pton(socket.AF_INET,
+ self._local_ip4_bcast)
+ self.local_ip4_prefix_len = 24
+ self.has_ip4_config = False
+ self.ip4_table_id = 0
+
+ self._local_ip6 = "fd01:%x::1" % self.sw_if_index
+ self._local_ip6n = socket.inet_pton(socket.AF_INET6, self.local_ip6)
+ self.local_ip6_prefix_len = 64
+ self.has_ip6_config = False
+ self.ip6_table_id = 0
+
+ r = self.test.vapi.sw_interface_dump()
+ for intf in r:
+ if intf.sw_if_index == self.sw_if_index:
+ self._name = intf.interface_name.split(b'\0', 1)[0]
+ self._local_mac = \
+ ':'.join(intf.l2_address.encode('hex')[i:i + 2]
+ for i in range(0, 12, 2))
+ self._dump = intf
+ break
+ else:
+ raise Exception(
+ "Could not find interface with sw_if_index %d "
+ "in interface dump %s" %
+ (self.sw_if_index, repr(r)))
+ self._local_ip6_ll = mk_ll_addr(self.local_mac)
+ self._local_ip6n_ll = socket.inet_pton(socket.AF_INET6,
+ self.local_ip6_ll)
+
+ def config_ip4(self):
+ """Configure IPv4 address on the VPP interface."""
+ self.test.vapi.sw_interface_add_del_address(
+ self.sw_if_index, self.local_ip4n, self.local_ip4_prefix_len)
+ self.has_ip4_config = True
+
+ def unconfig_ip4(self):
+ """Remove IPv4 address on the VPP interface."""
+ try:
+ if self.has_ip4_config:
+ self.test.vapi.sw_interface_add_del_address(
+ self.sw_if_index,
+ self.local_ip4n,
+ self.local_ip4_prefix_len,
+ is_add=0)
+ except AttributeError:
+ self.has_ip4_config = False
+ self.has_ip4_config = False
+
+ def configure_ipv4_neighbors(self):
+ """For every remote host assign neighbor's MAC to IPv4 addresses.
+
+ :param vrf_id: The FIB table / VRF ID. (Default value = 0)
+ """
+ for host in self._remote_hosts:
+ macn = host.mac.replace(":", "").decode('hex')
+ ipn = host.ip4n
+ self.test.vapi.ip_neighbor_add_del(
+ self.sw_if_index, macn, ipn)
+
+ def config_ip6(self):
+ """Configure IPv6 address on the VPP interface."""
+ self.test.vapi.sw_interface_add_del_address(
+ self.sw_if_index, self._local_ip6n, self.local_ip6_prefix_len,
+ is_ipv6=1)
+ self.has_ip6_config = True
+
+ def unconfig_ip6(self):
+ """Remove IPv6 address on the VPP interface."""
+ try:
+ if self.has_ip6_config:
+ self.test.vapi.sw_interface_add_del_address(
+ self.sw_if_index,
+ self.local_ip6n,
+ self.local_ip6_prefix_len,
+ is_ipv6=1, is_add=0)
+ except AttributeError:
+ self.has_ip6_config = False
+ self.has_ip6_config = False
+
+ def configure_ipv6_neighbors(self):
+ """For every remote host assign neighbor's MAC to IPv6 addresses.
+
+ :param vrf_id: The FIB table / VRF ID. (Default value = 0)
+ """
+ for host in self._remote_hosts:
+ macn = host.mac.replace(":", "").decode('hex')
+ ipn = host.ip6n
+ self.test.vapi.ip_neighbor_add_del(
+ self.sw_if_index, macn, ipn, is_ipv6=1)
+
+ def unconfig(self):
+ """Unconfigure IPv6 and IPv4 address on the VPP interface."""
+ self.unconfig_ip4()
+ self.unconfig_ip6()
+
+ def set_table_ip4(self, table_id):
+ """Set the interface in a IPv4 Table.
+
+ .. note:: Must be called before configuring IP4 addresses.
+ """
+ self.ip4_table_id = table_id
+ self.test.vapi.sw_interface_set_table(
+ self.sw_if_index, 0, self.ip4_table_id)
+
+ def set_table_ip6(self, table_id):
+ """Set the interface in a IPv6 Table.
+
+ .. note:: Must be called before configuring IP6 addresses.
+ """
+ self.ip6_table_id = table_id
+ self.test.vapi.sw_interface_set_table(
+ self.sw_if_index, 1, self.ip6_table_id)
+
+ def disable_ipv6_ra(self):
+ """Configure IPv6 RA suppress on the VPP interface."""
+ self.test.vapi.sw_interface_ra_suppress(self.sw_if_index)
+
+ def ip6_ra_config(self, no=0, suppress=0, send_unicast=0):
+ """Configure IPv6 RA suppress on the VPP interface."""
+ self.test.vapi.ip6_sw_interface_ra_config(self.sw_if_index,
+ no,
+ suppress,
+ send_unicast)
+
+ def ip6_ra_prefix(self, address, address_length, is_no=0,
+ off_link=0, no_autoconfig=0, use_default=0):
+ """Configure IPv6 RA suppress on the VPP interface."""
+ self.test.vapi.ip6_sw_interface_ra_prefix(self.sw_if_index,
+ address,
+ address_length,
+ is_no=is_no,
+ off_link=off_link,
+ no_autoconfig=no_autoconfig,
+ use_default=use_default)
+
+ def admin_up(self):
+ """Put interface ADMIN-UP."""
+ self.test.vapi.sw_interface_set_flags(self.sw_if_index,
+ admin_up_down=1)
+
+ def admin_down(self):
+ """Put interface ADMIN-down."""
+ self.test.vapi.sw_interface_set_flags(self.sw_if_index,
+ admin_up_down=0)
+
+ def ip6_enable(self):
+ """IPv6 Enable interface"""
+ self.test.vapi.ip6_sw_interface_enable_disable(self.sw_if_index,
+ enable=1)
+
+ def ip6_disable(self):
+ """Put interface ADMIN-DOWN."""
+ self.test.vapi.ip6_sw_interface_enable_disable(self.sw_if_index,
+ enable=0)
+
+ def add_sub_if(self, sub_if):
+ """Register a sub-interface with this interface.
+
+ :param sub_if: sub-interface
+ """
+ if not hasattr(self, 'sub_if'):
+ self.sub_if = sub_if
+ else:
+ if isinstance(self.sub_if, list):
+ self.sub_if.append(sub_if)
+ else:
+ self.sub_if = sub_if
+
+ def enable_mpls(self):
+ """Enable MPLS on the VPP interface."""
+ self.test.vapi.sw_interface_enable_disable_mpls(
+ self.sw_if_index)
+
+ def disable_mpls(self):
+ """Enable MPLS on the VPP interface."""
+ self.test.vapi.sw_interface_enable_disable_mpls(
+ self.sw_if_index, 0)
+
+ def is_ip4_entry_in_fib_dump(self, dump):
+ for i in dump:
+ if i.address == self.local_ip4n and \
+ i.address_length == self.local_ip4_prefix_len and \
+ i.table_id == self.ip4_table_id:
+ return True
+ return False
+
+ def set_unnumbered(self, ip_sw_if_index):
+ """ Set the interface to unnumbered via ip_sw_if_index """
+ self.test.vapi.sw_interface_set_unnumbered(
+ self.sw_if_index,
+ ip_sw_if_index)
+
+ def unset_unnumbered(self, ip_sw_if_index):
+ """ Unset the interface to unnumbered via ip_sw_if_index """
+ self.test.vapi.sw_interface_set_unnumbered(
+ self.sw_if_index,
+ ip_sw_if_index,
+ is_add=0)
+
+ def set_proxy_arp(self, enable=1):
+ """ Set the interface to enable/disable Proxy ARP """
+ self.test.vapi.proxy_arp_intfc_enable_disable(
+ self.sw_if_index,
+ enable)
diff --git a/test/vpp_ip_route.py b/test/vpp_ip_route.py
new file mode 100644
index 00000000..b7993793
--- /dev/null
+++ b/test/vpp_ip_route.py
@@ -0,0 +1,529 @@
+"""
+ IP Routes
+
+ object abstractions for representing IP routes in VPP
+"""
+
+from vpp_object import *
+from socket import inet_pton, inet_ntop, AF_INET, AF_INET6
+
+# from vnet/vnet/mpls/mpls_types.h
+MPLS_IETF_MAX_LABEL = 0xfffff
+MPLS_LABEL_INVALID = MPLS_IETF_MAX_LABEL + 1
+
+
+class MRouteItfFlags:
+ MFIB_ITF_FLAG_NONE = 0
+ MFIB_ITF_FLAG_NEGATE_SIGNAL = 1
+ MFIB_ITF_FLAG_ACCEPT = 2
+ MFIB_ITF_FLAG_FORWARD = 4
+ MFIB_ITF_FLAG_SIGNAL_PRESENT = 8
+ MFIB_ITF_FLAG_INTERNAL_COPY = 16
+
+
+class MRouteEntryFlags:
+ MFIB_ENTRY_FLAG_NONE = 0
+ MFIB_ENTRY_FLAG_SIGNAL = 1
+ MFIB_ENTRY_FLAG_DROP = 2
+ MFIB_ENTRY_FLAG_CONNECTED = 4
+ MFIB_ENTRY_FLAG_INHERIT_ACCEPT = 8
+
+
+class DpoProto:
+ DPO_PROTO_IP4 = 0
+ DPO_PROTO_IP6 = 1
+ DPO_PROTO_MPLS = 2
+ DPO_PROTO_ETHERNET = 3
+ DPO_PROTO_NSH = 4
+
+
+def find_route(test, ip_addr, len, table_id=0, inet=AF_INET):
+ if inet == AF_INET:
+ s = 4
+ routes = test.vapi.ip_fib_dump()
+ else:
+ s = 16
+ routes = test.vapi.ip6_fib_dump()
+
+ route_addr = inet_pton(inet, ip_addr)
+ for e in routes:
+ if route_addr == e.address[:s] \
+ and len == e.address_length \
+ and table_id == e.table_id:
+ return True
+ return False
+
+
+class VppIpTable(VppObject):
+
+ def __init__(self,
+ test,
+ table_id,
+ is_ip6=0):
+ self._test = test
+ self.table_id = table_id
+ self.is_ip6 = is_ip6
+
+ def add_vpp_config(self):
+ self._test.vapi.ip_table_add_del(
+ self.table_id,
+ is_ipv6=self.is_ip6,
+ is_add=1)
+ self._test.registry.register(self, self._test.logger)
+
+ def remove_vpp_config(self):
+ self._test.vapi.ip_table_add_del(
+ self.table_id,
+ is_ipv6=self.is_ip6,
+ is_add=0)
+
+ def query_vpp_config(self):
+ # find the default route
+ return find_route(self._test,
+ "::" if self.is_ip6 else "0.0.0.0",
+ 0,
+ self.table_id,
+ inet=AF_INET6 if self.is_ip6 == 1 else AF_INET)
+
+ def __str__(self):
+ return self.object_id()
+
+ def object_id(self):
+ return ("table-%s-%d" %
+ ("v6" if self.is_ip6 == 1 else "v4",
+ self.table_id))
+
+
+class VppRoutePath(object):
+
+ def __init__(
+ self,
+ nh_addr,
+ nh_sw_if_index,
+ nh_table_id=0,
+ labels=[],
+ nh_via_label=MPLS_LABEL_INVALID,
+ rpf_id=0,
+ is_interface_rx=0,
+ is_resolve_host=0,
+ is_resolve_attached=0,
+ proto=DpoProto.DPO_PROTO_IP4):
+ self.nh_itf = nh_sw_if_index
+ self.nh_table_id = nh_table_id
+ self.nh_via_label = nh_via_label
+ self.nh_labels = labels
+ self.weight = 1
+ self.rpf_id = rpf_id
+ self.proto = proto
+ if self.proto is DpoProto.DPO_PROTO_IP6:
+ self.nh_addr = inet_pton(AF_INET6, nh_addr)
+ elif self.proto is DpoProto.DPO_PROTO_IP4:
+ self.nh_addr = inet_pton(AF_INET, nh_addr)
+ else:
+ self.nh_addr = inet_pton(AF_INET6, "::")
+ self.is_resolve_host = is_resolve_host
+ self.is_resolve_attached = is_resolve_attached
+ self.is_interface_rx = is_interface_rx
+ self.is_rpf_id = 0
+ if rpf_id != 0:
+ self.is_rpf_id = 1
+ self.nh_itf = rpf_id
+
+
+class VppMRoutePath(VppRoutePath):
+
+ def __init__(self, nh_sw_if_index, flags):
+ super(VppMRoutePath, self).__init__("0.0.0.0",
+ nh_sw_if_index)
+ self.nh_i_flags = flags
+
+
+class VppIpRoute(VppObject):
+ """
+ IP Route
+ """
+
+ def __init__(self, test, dest_addr,
+ dest_addr_len, paths, table_id=0, is_ip6=0, is_local=0,
+ is_unreach=0, is_prohibit=0):
+ self._test = test
+ self.paths = paths
+ self.dest_addr_len = dest_addr_len
+ self.table_id = table_id
+ self.is_ip6 = is_ip6
+ self.is_local = is_local
+ self.is_unreach = is_unreach
+ self.is_prohibit = is_prohibit
+ self.dest_addr_p = dest_addr
+ if is_ip6:
+ self.dest_addr = inet_pton(AF_INET6, dest_addr)
+ else:
+ self.dest_addr = inet_pton(AF_INET, dest_addr)
+
+ def modify(self, paths, is_local=0,
+ is_unreach=0, is_prohibit=0):
+ self.paths = paths
+ self.is_local = is_local
+ self.is_unreach = is_unreach
+ self.is_prohibit = is_prohibit
+
+ def add_vpp_config(self):
+ if self.is_local or self.is_unreach or self.is_prohibit:
+ self._test.vapi.ip_add_del_route(
+ self.dest_addr,
+ self.dest_addr_len,
+ inet_pton(AF_INET6, "::"),
+ 0xffffffff,
+ is_local=self.is_local,
+ is_unreach=self.is_unreach,
+ is_prohibit=self.is_prohibit,
+ table_id=self.table_id,
+ is_ipv6=self.is_ip6)
+ else:
+ for path in self.paths:
+ self._test.vapi.ip_add_del_route(
+ self.dest_addr,
+ self.dest_addr_len,
+ path.nh_addr,
+ path.nh_itf,
+ table_id=self.table_id,
+ next_hop_out_label_stack=path.nh_labels,
+ next_hop_n_out_labels=len(
+ path.nh_labels),
+ next_hop_via_label=path.nh_via_label,
+ next_hop_table_id=path.nh_table_id,
+ is_ipv6=self.is_ip6,
+ is_resolve_host=path.is_resolve_host,
+ is_resolve_attached=path.is_resolve_attached,
+ is_multipath=1 if len(self.paths) > 1 else 0)
+ self._test.registry.register(self, self._test.logger)
+
+ def remove_vpp_config(self):
+ if self.is_local or self.is_unreach or self.is_prohibit:
+ self._test.vapi.ip_add_del_route(
+ self.dest_addr,
+ self.dest_addr_len,
+ inet_pton(AF_INET6, "::"),
+ 0xffffffff,
+ is_local=self.is_local,
+ is_unreach=self.is_unreach,
+ is_prohibit=self.is_prohibit,
+ is_add=0,
+ table_id=self.table_id,
+ is_ipv6=self.is_ip6)
+ else:
+ for path in self.paths:
+ self._test.vapi.ip_add_del_route(
+ self.dest_addr,
+ self.dest_addr_len,
+ path.nh_addr,
+ path.nh_itf,
+ table_id=self.table_id,
+ next_hop_table_id=path.nh_table_id,
+ next_hop_via_label=path.nh_via_label,
+ is_add=0,
+ is_ipv6=self.is_ip6)
+
+ def query_vpp_config(self):
+ return find_route(self._test,
+ self.dest_addr_p,
+ self.dest_addr_len,
+ self.table_id,
+ inet=AF_INET6 if self.is_ip6 == 1 else AF_INET)
+
+ def __str__(self):
+ return self.object_id()
+
+ def object_id(self):
+ return ("%d:%s/%d"
+ % (self.table_id,
+ self.dest_addr_p,
+ self.dest_addr_len))
+
+
+class VppIpMRoute(VppObject):
+ """
+ IP Multicast Route
+ """
+
+ def __init__(self, test, src_addr, grp_addr,
+ grp_addr_len, e_flags, paths, table_id=0,
+ rpf_id=0, is_ip6=0):
+ self._test = test
+ self.paths = paths
+ self.grp_addr_len = grp_addr_len
+ self.table_id = table_id
+ self.e_flags = e_flags
+ self.is_ip6 = is_ip6
+ self.rpf_id = rpf_id
+
+ if is_ip6:
+ self.grp_addr = inet_pton(AF_INET6, grp_addr)
+ self.src_addr = inet_pton(AF_INET6, src_addr)
+ else:
+ self.grp_addr = inet_pton(AF_INET, grp_addr)
+ self.src_addr = inet_pton(AF_INET, src_addr)
+
+ def add_vpp_config(self):
+ for path in self.paths:
+ self._test.vapi.ip_mroute_add_del(self.src_addr,
+ self.grp_addr,
+ self.grp_addr_len,
+ self.e_flags,
+ path.nh_itf,
+ path.nh_i_flags,
+ rpf_id=self.rpf_id,
+ table_id=self.table_id,
+ is_ipv6=self.is_ip6)
+ self._test.registry.register(self, self._test.logger)
+
+ def remove_vpp_config(self):
+ for path in self.paths:
+ self._test.vapi.ip_mroute_add_del(self.src_addr,
+ self.grp_addr,
+ self.grp_addr_len,
+ self.e_flags,
+ path.nh_itf,
+ path.nh_i_flags,
+ table_id=self.table_id,
+ is_add=0,
+ is_ipv6=self.is_ip6)
+
+ def update_entry_flags(self, flags):
+ self.e_flags = flags
+ self._test.vapi.ip_mroute_add_del(self.src_addr,
+ self.grp_addr,
+ self.grp_addr_len,
+ self.e_flags,
+ 0xffffffff,
+ 0,
+ table_id=self.table_id,
+ is_ipv6=self.is_ip6)
+
+ def update_rpf_id(self, rpf_id):
+ self.rpf_id = rpf_id
+ self._test.vapi.ip_mroute_add_del(self.src_addr,
+ self.grp_addr,
+ self.grp_addr_len,
+ self.e_flags,
+ 0xffffffff,
+ 0,
+ rpf_id=self.rpf_id,
+ table_id=self.table_id,
+ is_ipv6=self.is_ip6)
+
+ def update_path_flags(self, itf, flags):
+ for path in self.paths:
+ if path.nh_itf == itf:
+ path.nh_i_flags = flags
+ break
+ self._test.vapi.ip_mroute_add_del(self.src_addr,
+ self.grp_addr,
+ self.grp_addr_len,
+ self.e_flags,
+ path.nh_itf,
+ path.nh_i_flags,
+ table_id=self.table_id,
+ is_ipv6=self.is_ip6)
+
+ def query_vpp_config(self):
+ dump = self._test.vapi.ip_fib_dump()
+ for e in dump:
+ if self.grp_addr == e.address \
+ and self.grp_addr_len == e.address_length \
+ and self.table_id == e.table_id:
+ return True
+ return False
+
+ def __str__(self):
+ return self.object_id()
+
+ def object_id(self):
+ if self.is_ip6:
+ return ("%d:(%s,%s/%d)"
+ % (self.table_id,
+ inet_ntop(AF_INET6, self.src_addr),
+ inet_ntop(AF_INET6, self.grp_addr),
+ self.grp_addr_len))
+ else:
+ return ("%d:(%s,%s/%d)"
+ % (self.table_id,
+ inet_ntop(AF_INET, self.src_addr),
+ inet_ntop(AF_INET, self.grp_addr),
+ self.grp_addr_len))
+
+
+class VppMFibSignal(object):
+ def __init__(self, test, route, interface, packet):
+ self.route = route
+ self.interface = interface
+ self.packet = packet
+ self.test = test
+
+ def compare(self, signal):
+ self.test.assertEqual(self.interface, signal.sw_if_index)
+ self.test.assertEqual(self.route.table_id, signal.table_id)
+ self.test.assertEqual(self.route.grp_addr_len,
+ signal.grp_address_len)
+ for i in range(self.route.grp_addr_len / 8):
+ self.test.assertEqual(self.route.grp_addr[i],
+ signal.grp_address[i])
+ if (self.route.grp_addr_len > 32):
+ for i in range(4):
+ self.test.assertEqual(self.route.src_addr[i],
+ signal.src_address[i])
+
+
+class VppMplsIpBind(VppObject):
+ """
+ MPLS to IP Binding
+ """
+
+ def __init__(self, test, local_label, dest_addr, dest_addr_len,
+ table_id=0, ip_table_id=0, is_ip6=0):
+ self._test = test
+ self.dest_addr_len = dest_addr_len
+ self.dest_addr = dest_addr
+ self.local_label = local_label
+ self.table_id = table_id
+ self.ip_table_id = ip_table_id
+ self.is_ip6 = is_ip6
+ if is_ip6:
+ self.dest_addrn = inet_pton(AF_INET6, dest_addr)
+ else:
+ self.dest_addrn = inet_pton(AF_INET, dest_addr)
+
+ def add_vpp_config(self):
+ self._test.vapi.mpls_ip_bind_unbind(self.local_label,
+ self.dest_addrn,
+ self.dest_addr_len,
+ table_id=self.table_id,
+ ip_table_id=self.ip_table_id,
+ is_ip4=(self.is_ip6 == 0))
+ self._test.registry.register(self, self._test.logger)
+
+ def remove_vpp_config(self):
+ self._test.vapi.mpls_ip_bind_unbind(self.local_label,
+ self.dest_addrn,
+ self.dest_addr_len,
+ table_id=self.table_id,
+ ip_table_id=self.ip_table_id,
+ is_bind=0,
+ is_ip4=(self.is_ip6 == 0))
+
+ def query_vpp_config(self):
+ dump = self._test.vapi.mpls_fib_dump()
+ for e in dump:
+ if self.local_label == e.label \
+ and self.table_id == e.table_id:
+ return True
+ return False
+
+ def __str__(self):
+ return self.object_id()
+
+ def object_id(self):
+ return ("%d:%s binds %d:%s/%d"
+ % (self.table_id,
+ self.local_label,
+ self.ip_table_id,
+ self.dest_addr,
+ self.dest_addr_len))
+
+
+class VppMplsTable(VppObject):
+
+ def __init__(self,
+ test,
+ table_id):
+ self._test = test
+ self.table_id = table_id
+
+ def add_vpp_config(self):
+ self._test.vapi.mpls_table_add_del(
+ self.table_id,
+ is_add=1)
+ self._test.registry.register(self, self._test.logger)
+
+ def remove_vpp_config(self):
+ self._test.vapi.mpls_table_add_del(
+ self.table_id,
+ is_add=0)
+
+ def query_vpp_config(self):
+ # find the default route
+ dump = self._test.vapi.mpls_fib_dump()
+ if len(dump):
+ return True
+ return False
+
+ def __str__(self):
+ return self.object_id()
+
+ def object_id(self):
+ return ("table-mpls-%d" % (self.table_id))
+
+
+class VppMplsRoute(VppObject):
+ """
+ MPLS Route/LSP
+ """
+
+ def __init__(self, test, local_label, eos_bit, paths, table_id=0,
+ is_multicast=0):
+ self._test = test
+ self.paths = paths
+ self.local_label = local_label
+ self.eos_bit = eos_bit
+ self.table_id = table_id
+ self.is_multicast = is_multicast
+
+ def add_vpp_config(self):
+ is_multipath = len(self.paths) > 1
+ for path in self.paths:
+ self._test.vapi.mpls_route_add_del(
+ self.local_label,
+ self.eos_bit,
+ path.proto,
+ path.nh_addr,
+ path.nh_itf,
+ is_multicast=self.is_multicast,
+ is_multipath=is_multipath,
+ table_id=self.table_id,
+ is_interface_rx=path.is_interface_rx,
+ is_rpf_id=path.is_rpf_id,
+ next_hop_out_label_stack=path.nh_labels,
+ next_hop_n_out_labels=len(
+ path.nh_labels),
+ next_hop_via_label=path.nh_via_label,
+ next_hop_table_id=path.nh_table_id)
+ self._test.registry.register(self, self._test.logger)
+
+ def remove_vpp_config(self):
+ for path in self.paths:
+ self._test.vapi.mpls_route_add_del(self.local_label,
+ self.eos_bit,
+ path.proto,
+ path.nh_addr,
+ path.nh_itf,
+ is_rpf_id=path.is_rpf_id,
+ table_id=self.table_id,
+ is_add=0)
+
+ def query_vpp_config(self):
+ dump = self._test.vapi.mpls_fib_dump()
+ for e in dump:
+ if self.local_label == e.label \
+ and self.eos_bit == e.eos_bit \
+ and self.table_id == e.table_id:
+ return True
+ return False
+
+ def __str__(self):
+ return self.object_id()
+
+ def object_id(self):
+ return ("%d:%s/%d"
+ % (self.table_id,
+ self.local_label,
+ 20+self.eos_bit))
diff --git a/test/vpp_lo_interface.py b/test/vpp_lo_interface.py
new file mode 100644
index 00000000..963123f6
--- /dev/null
+++ b/test/vpp_lo_interface.py
@@ -0,0 +1,35 @@
+from vpp_object import VppObject
+from vpp_interface import VppInterface
+
+
+class VppLoInterface(VppInterface, VppObject):
+ """VPP loopback interface."""
+
+ def __init__(self, test, lo_index):
+ """ Create VPP loopback interface """
+ self._test = test
+ self.add_vpp_config()
+ super(VppLoInterface, self).__init__(test)
+ self._lo_index = lo_index
+
+ def add_vpp_config(self):
+ r = self.test.vapi.create_loopback()
+ self._sw_if_index = r.sw_if_index
+
+ def remove_vpp_config(self):
+ self.test.vapi.delete_loopback(self.sw_if_index)
+
+ def query_vpp_config(self):
+ dump = self.test.vapi.sw_interface_dump()
+ return self.is_interface_config_in_dump(dump)
+
+ def is_interface_config_in_dump(self, dump):
+ for i in dump:
+ if i.interface_name.rstrip(' \t\r\n\0') == self.name and \
+ i.sw_if_index == self.sw_if_index:
+ return True
+ else:
+ return False
+
+ def object_id(self):
+ return "loopback-%d" % self._sw_if_index
diff --git a/test/vpp_mpls_tunnel_interface.py b/test/vpp_mpls_tunnel_interface.py
new file mode 100644
index 00000000..0542b05c
--- /dev/null
+++ b/test/vpp_mpls_tunnel_interface.py
@@ -0,0 +1,48 @@
+
+from vpp_interface import VppInterface
+from vpp_ip_route import VppRoutePath
+import socket
+
+
+class VppMPLSTunnelInterface(VppInterface):
+ """
+ VPP MPLS Tunnel interface
+ """
+
+ def __init__(self, test, paths, is_multicast=0, is_l2=0):
+ """ Create MPLS Tunnel interface """
+ self._sw_if_index = 0
+ super(VppMPLSTunnelInterface, self).__init__(test)
+ self._test = test
+ self.t_paths = paths
+ self.is_multicast = is_multicast
+ self.is_l2 = is_l2
+
+ def add_vpp_config(self):
+ self._sw_if_index = 0xffffffff
+ for path in self.t_paths:
+ reply = self.test.vapi.mpls_tunnel_add_del(
+ self._sw_if_index,
+ 1, # IPv4 next-hop
+ path.nh_addr,
+ path.nh_itf,
+ path.nh_table_id,
+ path.weight,
+ next_hop_out_label_stack=path.nh_labels,
+ next_hop_n_out_labels=len(path.nh_labels),
+ is_multicast=self.is_multicast,
+ l2_only=self.is_l2)
+ self._sw_if_index = reply.sw_if_index
+
+ def remove_vpp_config(self):
+ for path in self.t_paths:
+ reply = self.test.vapi.mpls_tunnel_add_del(
+ self.sw_if_index,
+ 1, # IPv4 next-hop
+ path.nh_addr,
+ path.nh_itf,
+ path.nh_table_id,
+ path.weight,
+ next_hop_out_label_stack=path.nh_labels,
+ next_hop_n_out_labels=len(path.nh_labels),
+ is_add=0)
diff --git a/test/vpp_neighbor.py b/test/vpp_neighbor.py
new file mode 100644
index 00000000..5919cf8e
--- /dev/null
+++ b/test/vpp_neighbor.py
@@ -0,0 +1,79 @@
+"""
+ Neighbour Entries
+
+ object abstractions for ARP and ND
+"""
+
+from socket import inet_pton, inet_ntop, AF_INET, AF_INET6
+from vpp_object import *
+from util import mactobinary
+
+
+def find_nbr(test, sw_if_index, ip_addr, is_static=0, inet=AF_INET):
+ nbrs = test.vapi.ip_neighbor_dump(sw_if_index,
+ is_ipv6=1 if AF_INET6 == inet else 0)
+ if inet == AF_INET:
+ s = 4
+ else:
+ s = 16
+ nbr_addr = inet_pton(inet, ip_addr)
+
+ for n in nbrs:
+ if nbr_addr == n.ip_address[:s] \
+ and is_static == n.is_static:
+ return True
+ return False
+
+
+class VppNeighbor(VppObject):
+ """
+ ARP Entry
+ """
+
+ def __init__(self, test, sw_if_index, mac_addr, nbr_addr,
+ af=AF_INET, is_static=False, is_no_fib_entry=0):
+ self._test = test
+ self.sw_if_index = sw_if_index
+ self.mac_addr = mactobinary(mac_addr)
+ self.af = af
+ self.is_static = is_static
+ self.is_no_fib_entry = is_no_fib_entry
+ self.nbr_addr = inet_pton(af, nbr_addr)
+
+ def add_vpp_config(self):
+ self._test.vapi.ip_neighbor_add_del(
+ self.sw_if_index,
+ self.mac_addr,
+ self.nbr_addr,
+ is_add=1,
+ is_ipv6=1 if AF_INET6 == self.af else 0,
+ is_static=self.is_static,
+ is_no_adj_fib=self.is_no_fib_entry)
+ self._test.registry.register(self, self._test.logger)
+
+ def remove_vpp_config(self):
+ self._test.vapi.ip_neighbor_add_del(
+ self.sw_if_index,
+ self.mac_addr,
+ self.nbr_addr,
+ is_ipv6=1 if AF_INET6 == self.af else 0,
+ is_add=0,
+ is_static=self.is_static)
+
+ def query_vpp_config(self):
+ dump = self._test.vapi.ip_neighbor_dump(
+ self.sw_if_index,
+ is_ipv6=1 if AF_INET6 == self.af else 0)
+ for n in dump:
+ if self.nbr_addr == n.ip_address \
+ and self.is_static == n.is_static:
+ return True
+ return False
+
+ def __str__(self):
+ return self.object_id()
+
+ def object_id(self):
+ return ("%d:%s"
+ % (self.sw_if_index,
+ inet_ntop(self.af, self.nbr_addr)))
diff --git a/test/vpp_object.py b/test/vpp_object.py
new file mode 100644
index 00000000..a1cf42fc
--- /dev/null
+++ b/test/vpp_object.py
@@ -0,0 +1,87 @@
+""" abstract vpp object and object registry """
+
+from abc import ABCMeta, abstractmethod
+
+
+class VppObject(object):
+ """ Abstract vpp object """
+ __metaclass__ = ABCMeta
+
+ @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, obj, logger):
+ """ Register an object in the registry. """
+ if obj.object_id() not in self._object_dict:
+ self._object_registry.append(obj)
+ self._object_dict[obj.object_id()] = obj
+ logger.debug("REG: registering %s" % obj)
+ else:
+ logger.debug("REG: duplicate add, ignoring (%s)" % obj)
+
+ def unregister_all(self, logger):
+ """ Remove all object registrations from registry. """
+ logger.debug("REG: removing all object registrations")
+ self._object_registry = []
+ self._object_dict = dict()
+
+ 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("REG: No objects registered for auto-cleanup.")
+ return
+ logger.info("REG: Removing VPP configuration for registered objects")
+ # remove the config in reverse order as there might be dependencies
+ for obj in reversed(self._object_registry):
+ if obj.query_vpp_config():
+ logger.info("REG: Removing configuration for %s" % obj)
+ obj.remove_vpp_config()
+ else:
+ logger.info(
+ "REG: Skipping removal for %s, configuration not present" %
+ obj)
+ failed = []
+ for obj in self._object_registry:
+ if obj.query_vpp_config():
+ failed.append(obj)
+ self.unregister_all(logger)
+ if failed:
+ logger.error("REG: Couldn't remove configuration for object(s):")
+ for obj in failed:
+ logger.error(repr(obj))
+ 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
new file mode 100644
index 00000000..2eab6c6e
--- /dev/null
+++ b/test/vpp_papi_provider.py
@@ -0,0 +1,2390 @@
+import os
+import fnmatch
+import time
+from hook import Hook
+from collections import deque
+
+# 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")
+ if no_vpp_papi == "1":
+ do_import = False
+except:
+ pass
+
+if do_import:
+ from vpp_papi import VPP
+
+# from vnet/vnet/mpls/mpls_types.h
+MPLS_IETF_MAX_LABEL = 0xfffff
+MPLS_LABEL_INVALID = MPLS_IETF_MAX_LABEL + 1
+
+
+class L2_VTR_OP:
+ L2_DISABLED = 0
+ L2_PUSH_1 = 1
+ L2_PUSH_2 = 2
+ L2_POP_1 = 3
+ L2_POP_2 = 4
+ L2_TRANSLATE_1_1 = 5
+ L2_TRANSLATE_1_2 = 6
+ L2_TRANSLATE_2_1 = 7
+ L2_TRANSLATE_2_2 = 8
+
+
+class UnexpectedApiReturnValueError(Exception):
+ """ exception raised when the API return value is unexpected """
+ pass
+
+
+class VppPapiProvider(object):
+ """VPP-api provider using vpp-papi
+
+ @property hook: hook object providing before and after api/cli hooks
+ """
+
+ _zero, _negative = range(2)
+
+ def __init__(self, name, shm_prefix, test_class):
+ self.hook = Hook("vpp-papi-provider")
+ self.name = name
+ self.shm_prefix = shm_prefix
+ self.test_class = test_class
+ self._expect_api_retval = self._zero
+ self._expect_stack = []
+ jsonfiles = []
+
+ 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.vpp = VPP(jsonfiles, logger=test_class.logger)
+ self._events = deque()
+
+ def __enter__(self):
+ return self
+
+ def expect_negative_api_retval(self):
+ """ Expect API failure """
+ self._expect_stack.append(self._expect_api_retval)
+ self._expect_api_retval = self._negative
+ return self
+
+ def expect_zero_api_retval(self):
+ """ Expect API success """
+ self._expect_stack.append(self._expect_api_retval)
+ self._expect_api_retval = self._zero
+ return self
+
+ def __exit__(self, exc_type, exc_value, traceback):
+ self._expect_api_retval = self._expect_stack.pop()
+
+ def register_hook(self, hook):
+ """Replace hook registration with new hook
+
+ :param hook:
+
+ """
+ self.hook = hook
+
+ def collect_events(self):
+ """ Collect all events from the internal queue and clear the queue. """
+ e = self._events
+ self._events = deque()
+ return e
+
+ def wait_for_event(self, timeout, name=None):
+ """ Wait for and return next event. """
+ if name:
+ self.test_class.logger.debug("Expecting event '%s' within %ss",
+ name, timeout)
+ else:
+ self.test_class.logger.debug("Expecting event within %ss",
+ timeout)
+ if self._events:
+ self.test_class.logger.debug("Not waiting, event already queued")
+ limit = time.time() + timeout
+ while time.time() < limit:
+ if self._events:
+ e = self._events.popleft()
+ if name and type(e).__name__ != name:
+ raise Exception(
+ "Unexpected event received: %s, expected: %s" %
+ (type(e).__name__, name))
+ self.test_class.logger.debug("Returning event %s:%s" %
+ (name, e))
+ return e
+ time.sleep(0) # yield
+ raise Exception("Event did not occur within timeout")
+
+ def __call__(self, name, event):
+ """ Enqueue event in the internal event queue. """
+ # FIXME use the name instead of relying on type(e).__name__ ?
+ # FIXME #2 if this throws, it is eaten silently, Ole?
+ self.test_class.logger.debug("New event: %s: %s" % (name, event))
+ self._events.append(event)
+
+ def connect(self):
+ """Connect the API to VPP"""
+ self.vpp.connect(self.name, self.shm_prefix)
+ self.papi = self.vpp.api
+ self.vpp.register_event_callback(self)
+
+ def disconnect(self):
+ """Disconnect the API from VPP"""
+ self.vpp.disconnect()
+
+ def api(self, api_fn, api_args, expected_retval=0):
+ """ Call API function and check it's return value.
+ Call the appropriate hooks before and after the API call
+
+ :param api_fn: API function to call
+ :param api_args: tuple of API function arguments
+ :param expected_retval: Expected return value (Default value = 0)
+ :returns: reply from the API
+
+ """
+ self.hook.before_api(api_fn.__name__, api_args)
+ reply = api_fn(**api_args)
+ if self._expect_api_retval == self._negative:
+ if hasattr(reply, 'retval') and reply.retval >= 0:
+ msg = "API call passed unexpectedly: expected negative "\
+ "return value instead of %d in %s" % \
+ (reply.retval, repr(reply))
+ self.test_class.logger.info(msg)
+ raise UnexpectedApiReturnValueError(msg)
+ elif self._expect_api_retval == self._zero:
+ if hasattr(reply, 'retval') and reply.retval != expected_retval:
+ msg = "API call failed, expected %d return value instead "\
+ "of %d in %s" % (expected_retval, reply.retval,
+ repr(reply))
+ self.test_class.logger.info(msg)
+ raise UnexpectedApiReturnValueError(msg)
+ else:
+ raise Exception("Internal error, unexpected value for "
+ "self._expect_api_retval %s" %
+ self._expect_api_retval)
+ self.hook.after_api(api_fn.__name__, api_args)
+ return reply
+
+ def cli(self, cli):
+ """ Execute a CLI, calling the before/after hooks appropriately.
+
+ :param cli: CLI to execute
+ :returns: CLI output
+
+ """
+ self.hook.before_cli(cli)
+ cli += '\n'
+ r = self.papi.cli_inband(length=len(cli), cmd=cli)
+ self.hook.after_cli(cli)
+ if hasattr(r, 'reply'):
+ return r.reply.decode().rstrip('\x00')
+
+ def ppcli(self, cli):
+ """ Helper method to print CLI command in case of info logging level.
+
+ :param cli: CLI to execute
+ :returns: CLI output
+ """
+ return cli + "\n" + str(self.cli(cli))
+
+ def _convert_mac(self, mac):
+ return int(mac.replace(":", ""), 16) << 16
+
+ def show_version(self):
+ """ """
+ return self.api(self.papi.show_version, {})
+
+ def pg_create_interface(self, pg_index):
+ """
+
+ :param pg_index:
+
+ """
+ return self.api(self.papi.pg_create_interface,
+ {"interface_id": pg_index})
+
+ def sw_interface_dump(self, filter=None):
+ """
+
+ :param filter: (Default value = None)
+
+ """
+ if filter is not None:
+ args = {"name_filter_valid": 1, "name_filter": filter}
+ else:
+ args = {}
+ return self.api(self.papi.sw_interface_dump, args)
+
+ def sw_interface_set_table(self, sw_if_index, is_ipv6, table_id):
+ """ Set the IPvX Table-id for the Interface
+
+ :param sw_if_index:
+ :param is_ipv6:
+ :param table_id:
+
+ """
+ return self.api(self.papi.sw_interface_set_table,
+ {'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):
+ """
+
+ :param addr: param is_ipv6: (Default value = 0)
+ :param sw_if_index:
+ :param addr_len:
+ :param is_ipv6: (Default value = 0)
+ :param is_add: (Default value = 1)
+ :param del_all: (Default value = 0)
+
+ """
+ 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})
+
+ def sw_interface_set_unnumbered(self, sw_if_index, ip_sw_if_index,
+ is_add=1):
+ """ Set the Interface to be unnumbered
+
+ :param is_add: (Default value = 1)
+ :param sw_if_index - interface That will be unnumbered
+ :param ip_sw_if_index - interface with an IP addres
+
+ """
+ return self.api(self.papi.sw_interface_set_unnumbered,
+ {'sw_if_index': ip_sw_if_index,
+ 'unnumbered_sw_if_index': sw_if_index,
+ 'is_add': is_add})
+
+ def sw_interface_enable_disable_mpls(self, sw_if_index,
+ is_enable=1):
+ """
+ Enable/Disable MPLS on the interface
+ :param sw_if_index:
+ :param is_enable: (Default value = 1)
+
+ """
+ return self.api(self.papi.sw_interface_set_mpls_enable,
+ {'sw_if_index': sw_if_index,
+ 'enable': is_enable})
+
+ def sw_interface_ra_suppress(self, sw_if_index, suppress=1):
+ return self.api(self.papi.sw_interface_ip6nd_ra_config,
+ {'sw_if_index': sw_if_index,
+ 'suppress': suppress})
+
+ def set_ip_flow_hash(self,
+ table_id,
+ src=1,
+ dst=1,
+ sport=1,
+ dport=1,
+ proto=1,
+ reverse=0,
+ is_ip6=0):
+ return self.api(self.papi.set_ip_flow_hash,
+ {'vrf_id': table_id,
+ 'src': src,
+ 'dst': dst,
+ 'dport': dport,
+ 'sport': sport,
+ 'proto': proto,
+ 'reverse': reverse,
+ 'is_ipv6': is_ip6})
+
+ def ip6_nd_proxy(self, address, sw_if_index, is_del=0):
+ return self.api(self.papi.ip6nd_proxy_add_del,
+ {'address': address,
+ 'sw_if_index': sw_if_index,
+ 'is_del': is_del})
+
+ def ip6_sw_interface_ra_config(self, sw_if_index,
+ no,
+ suppress,
+ send_unicast):
+ return self.api(self.papi.sw_interface_ip6nd_ra_config,
+ {'sw_if_index': sw_if_index,
+ 'is_no': no,
+ 'suppress': suppress,
+ 'send_unicast': send_unicast})
+
+ def ip6_sw_interface_ra_prefix(self,
+ sw_if_index,
+ address,
+ address_length,
+ use_default=0,
+ no_advertise=0,
+ off_link=0,
+ no_autoconfig=0,
+ no_onlink=0,
+ is_no=0,
+ val_lifetime=0xffffffff,
+ pref_lifetime=0xffffffff):
+ return self.api(self.papi.sw_interface_ip6nd_ra_prefix,
+ {'sw_if_index': sw_if_index,
+ 'address': address,
+ 'address_length': address_length,
+ 'use_default': use_default,
+ 'no_advertise': no_advertise,
+ 'off_link': off_link,
+ 'no_autoconfig': no_autoconfig,
+ 'no_onlink': no_onlink,
+ 'is_no': is_no,
+ 'val_lifetime': val_lifetime,
+ 'pref_lifetime': pref_lifetime})
+
+ def ip6_sw_interface_enable_disable(self, sw_if_index, enable):
+ """
+ Enable/Disable An interface for IPv6
+ """
+ return self.api(self.papi.sw_interface_ip6_enable_disable,
+ {'sw_if_index': sw_if_index,
+ 'enable': enable})
+
+ def vxlan_add_del_tunnel(
+ self,
+ src_addr,
+ dst_addr,
+ mcast_sw_if_index=0xFFFFFFFF,
+ is_add=1,
+ is_ipv6=0,
+ encap_vrf_id=0,
+ decap_next_index=0xFFFFFFFF,
+ vni=0):
+ """
+
+ :param dst_addr:
+ :param src_addr:
+ :param is_add: (Default value = 1)
+ :param is_ipv6: (Default value = 0)
+ :param encap_vrf_id: (Default value = 0)
+ :param decap_next_index: (Default value = 0xFFFFFFFF)
+ :param mcast_sw_if_index: (Default value = 0xFFFFFFFF)
+ :param vni: (Default value = 0)
+
+ """
+ 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})
+
+ def bridge_domain_add_del(self, bd_id, flood=1, uu_flood=1, forward=1,
+ learn=1, arp_term=0, is_add=1):
+ """Create/delete bridge domain.
+
+ :param int bd_id: Bridge domain index.
+ :param int flood: Enable/disable bcast/mcast flooding in the BD.
+ (Default value = 1)
+ :param int uu_flood: Enable/disable unknown unicast flood in the BD.
+ (Default value = 1)
+ :param int forward: Enable/disable forwarding on all interfaces in
+ the BD. (Default value = 1)
+ :param int learn: Enable/disable learning on all interfaces in the BD.
+ (Default value = 1)
+ :param int arp_term: Enable/disable arp termination in the BD.
+ (Default value = 1)
+ :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})
+
+ def bd_ip_mac_add_del(self, bd_id, mac, ip, is_ipv6=0, is_add=1):
+ return self.api(self.papi.bd_ip_mac_add_del,
+ {'bd_id': bd_id,
+ 'is_add': is_add,
+ 'is_ipv6': is_ipv6,
+ 'ip_address': ip,
+ 'mac_address': mac})
+
+ def want_ip4_arp_events(self, enable_disable=1, address=0):
+ return self.api(self.papi.want_ip4_arp_events,
+ {'enable_disable': enable_disable,
+ 'address': address,
+ 'pid': os.getpid(), })
+
+ def want_ip6_nd_events(self, enable_disable=1, address=0):
+ return self.api(self.papi.want_ip6_nd_events,
+ {'enable_disable': enable_disable,
+ 'address': address,
+ 'pid': os.getpid(), })
+
+ def want_macs_learn_events(self, enable_disable=1, scan_delay=0,
+ max_macs_in_event=0, learn_limit=0):
+ return self.api(self.papi.want_l2_macs_events,
+ {'enable_disable': enable_disable,
+ 'scan_delay': scan_delay,
+ 'max_macs_in_event': max_macs_in_event,
+ 'learn_limit': learn_limit,
+ 'pid': os.getpid(), })
+
+ def l2fib_add_del(self, mac, bd_id, sw_if_index, is_add=1, static_mac=0,
+ filter_mac=0, bvi_mac=0):
+ """Create/delete L2 FIB entry.
+
+ :param str mac: MAC address to create FIB entry for.
+ :param int bd_id: Bridge domain index.
+ :param int sw_if_index: Software interface index of the interface.
+ :param int is_add: Add or delete flag. (Default value = 1)
+ :param int static_mac: Set to 1 to create static MAC entry.
+ (Default value = 0)
+ :param int filter_mac: Set to 1 to drop packet that's source or
+ destination MAC address contains defined MAC address.
+ (Default value = 0)
+ :param int bvi_mac: Set to 1 to create entry that points to BVI
+ 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})
+
+ def l2fib_flush_int(self, sw_if_index):
+ """Flush L2 FIB entries for sw_if_index.
+
+ :param int sw_if_index: Software interface index of the interface.
+ """
+ return self.api(self.papi.l2fib_flush_int,
+ {'sw_if_index': sw_if_index})
+
+ def l2fib_flush_bd(self, bd_id):
+ """Flush L2 FIB entries for bd_id.
+
+ :param int sw_if_index: Bridge Domain id.
+ """
+ return self.api(self.papi.l2fib_flush_bd,
+ {'bd_id': bd_id})
+
+ def l2fib_flush_all(self):
+ """Flush all L2 FIB.
+ """
+ return self.api(self.papi.l2fib_flush_all, {})
+
+ def sw_interface_set_l2_bridge(self, sw_if_index, bd_id,
+ shg=0, bvi=0, enable=1):
+ """Add/remove interface to/from bridge domain.
+
+ :param int sw_if_index: Software interface index of the interface.
+ :param int bd_id: Bridge domain index.
+ :param int shg: Split-horizon group index. (Default value = 0)
+ :param int bvi: Set interface as a bridge group virtual interface.
+ (Default value = 0)
+ :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})
+
+ def bridge_flags(self, bd_id, is_set, feature_bitmap):
+ """Enable/disable required feature of the bridge domain with defined ID.
+
+ :param int bd_id: Bridge domain ID.
+ :param int is_set: Set to 1 to enable, set to 0 to disable the feature.
+ :param int feature_bitmap: Bitmap value of the feature to be set:
+ - learn (1 << 0),
+ - forward (1 << 1),
+ - flood (1 << 2),
+ - uu-flood (1 << 3) or
+ - arp-term (1 << 4).
+ """
+ return self.api(self.papi.bridge_flags,
+ {'bd_id': bd_id,
+ 'is_set': is_set,
+ 'feature_bitmap': feature_bitmap})
+
+ def bridge_domain_dump(self, bd_id=0):
+ """
+
+ :param int bd_id: Bridge domain ID. (Default value = 0 => dump of all
+ existing bridge domains returned)
+ :return: Dictionary of bridge domain(s) data.
+ """
+ return self.api(self.papi.bridge_domain_dump,
+ {'bd_id': bd_id})
+
+ def sw_interface_set_l2_xconnect(self, rx_sw_if_index, tx_sw_if_index,
+ enable):
+ """Create or delete unidirectional cross-connect from Tx interface to
+ Rx interface.
+
+ :param int rx_sw_if_index: Software interface index of Rx interface.
+ :param int tx_sw_if_index: Software interface index of Tx interface.
+ :param int enable: Create cross-connect if equal to 1, delete
+ cross-connect if equal to 0.
+
+ """
+ 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})
+
+ 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
+ :param sw_if_index - interface the operation is applied to
+ :param vtr_op - Choose from l2_vtr_op_t enum values
+ :param push_dot1q - first pushed flag dot1q id set, else dot1ad
+ :param tag1 - Needed for any push or translate vtr op
+ :param tag2 - Needed for any push 2 or translate x-2 vtr ops
+
+ """
+ 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})
+
+ def sw_interface_set_flags(self, sw_if_index, admin_up_down):
+ """
+
+ :param admin_up_down:
+ :param sw_if_index:
+
+ """
+ return self.api(self.papi.sw_interface_set_flags,
+ {'sw_if_index': sw_if_index,
+ 'admin_up_down': admin_up_down})
+
+ 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,
+ default_sub=0, outer_vlan_id_any=0, inner_vlan_id_any=0):
+ """Create subinterface
+ from vpe.api: set dot1ad = 0 for dot1q, set dot1ad = 1 for dot1ad
+
+ :param sub_id: param inner_vlan:
+ :param sw_if_index:
+ :param outer_vlan:
+ :param inner_vlan:
+ :param no_tags: (Default value = 0)
+ :param one_tag: (Default value = 0)
+ :param two_tags: (Default value = 0)
+ :param dot1ad: (Default value = 0)
+ :param exact_match: (Default value = 0)
+ :param default_sub: (Default value = 0)
+ :param outer_vlan_id_any: (Default value = 0)
+ :param inner_vlan_id_any: (Default value = 0)
+
+ """
+ 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})
+
+ def create_p2pethernet_subif(self, sw_if_index, remote_mac, subif_id):
+ """Create p2p ethernet subinterface
+
+ :param sw_if_index: main (parent) interface
+ :param remote_mac: client (remote) mac address
+
+ """
+ return self.api(
+ self.papi.p2p_ethernet_add,
+ {'parent_if_index': sw_if_index,
+ 'remote_mac': remote_mac,
+ 'subif_id': subif_id})
+
+ def delete_subif(self, sw_if_index):
+ """Delete subinterface
+
+ :param sw_if_index:
+ """
+ return self.api(self.papi.delete_subif,
+ {'sw_if_index': sw_if_index})
+
+ def delete_p2pethernet_subif(self, sw_if_index, remote_mac):
+ """Delete p2p ethernet subinterface
+
+ :param sw_if_index: main (parent) interface
+ :param remote_mac: client (remote) mac address
+
+ """
+ return self.api(
+ self.papi.p2p_ethernet_del,
+ {'parent_if_index': sw_if_index,
+ 'remote_mac': remote_mac})
+
+ def create_vlan_subif(self, sw_if_index, vlan):
+ """
+
+ :param vlan:
+ :param sw_if_index:
+
+ """
+ return self.api(self.papi.create_vlan_subif,
+ {'sw_if_index': sw_if_index,
+ 'vlan_id': vlan})
+
+ def create_loopback(self, mac=''):
+ """
+
+ :param mac: (Optional)
+ """
+ return self.api(self.papi.create_loopback,
+ {'mac_address': mac})
+
+ def delete_loopback(self, sw_if_index):
+ return self.api(self.papi.delete_loopback,
+ {'sw_if_index': sw_if_index, })
+
+ def ip_table_add_del(self,
+ table_id,
+ is_add=1,
+ is_ipv6=0):
+ """
+
+ :param table_id
+ :param is_add: (Default value = 1)
+ :param is_ipv6: (Default value = 0)
+
+ """
+
+ return self.api(
+ self.papi.ip_table_add_del,
+ {'table_id': table_id,
+ 'is_add': is_add,
+ 'is_ipv6': is_ipv6})
+
+ def ip_add_del_route(
+ self,
+ dst_address,
+ dst_address_length,
+ next_hop_address,
+ next_hop_sw_if_index=0xFFFFFFFF,
+ 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,
+ is_resolve_host=0,
+ is_resolve_attached=0,
+ classify_table_index=0xFFFFFFFF,
+ is_add=1,
+ is_drop=0,
+ is_unreach=0,
+ is_prohibit=0,
+ is_ipv6=0,
+ is_local=0,
+ is_classify=0,
+ is_multipath=0,
+ not_last=0):
+ """
+
+ :param dst_address_length:
+ :param next_hop_sw_if_index: (Default value = 0xFFFFFFFF)
+ :param dst_address:
+ :param next_hop_address:
+ :param next_hop_sw_if_index: (Default value = 0xFFFFFFFF)
+ :param vrf_id: (Default value = 0)
+ :param lookup_in_vrf: (Default value = 0)
+ :param classify_table_index: (Default value = 0xFFFFFFFF)
+ :param is_add: (Default value = 1)
+ :param is_drop: (Default value = 0)
+ :param is_ipv6: (Default value = 0)
+ :param is_local: (Default value = 0)
+ :param is_classify: (Default value = 0)
+ :param is_multipath: (Default value = 0)
+ :param is_resolve_host: (Default value = 0)
+ :param is_resolve_attached: (Default value = 0)
+ :param not_last: (Default value = 0)
+ :param next_hop_weight: (Default value = 1)
+
+ """
+
+ 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,
+ '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_fib_dump(self):
+ return self.api(self.papi.ip_fib_dump, {})
+
+ def ip6_fib_dump(self):
+ return self.api(self.papi.ip6_fib_dump, {})
+
+ def ip_neighbor_add_del(self,
+ sw_if_index,
+ mac_address,
+ dst_address,
+ is_add=1,
+ is_ipv6=0,
+ is_static=0,
+ is_no_adj_fib=0,
+ ):
+ """ Add neighbor MAC to IPv4 or IPv6 address.
+
+ :param sw_if_index:
+ :param mac_address:
+ :param dst_address:
+ :param is_add: (Default value = 1)
+ :param is_ipv6: (Default value = 0)
+ :param is_static: (Default value = 0)
+ :param is_no_adj_fib: (Default value = 0)
+ """
+
+ return self.api(
+ self.papi.ip_neighbor_add_del,
+ {'sw_if_index': sw_if_index,
+ 'is_add': is_add,
+ 'is_ipv6': is_ipv6,
+ 'is_static': is_static,
+ 'is_no_adj_fib': is_no_adj_fib,
+ 'mac_address': mac_address,
+ 'dst_address': dst_address
+ }
+ )
+
+ def ip_neighbor_dump(self,
+ sw_if_index,
+ is_ipv6=0):
+ """ Return IP neighbor dump.
+
+ :param sw_if_index:
+ :param int is_ipv6: 1 for IPv6 neighbor, 0 for IPv4. (Default = 0)
+ """
+
+ return self.api(
+ self.papi.ip_neighbor_dump,
+ {'is_ipv6': is_ipv6,
+ 'sw_if_index': sw_if_index
+ }
+ )
+
+ def proxy_arp_add_del(self,
+ low_address,
+ hi_address,
+ vrf_id=0,
+ is_add=1):
+ """ Config Proxy Arp Range.
+
+ :param low_address: Start address in the rnage to Proxy for
+ :param hi_address: End address in the rnage to Proxy for
+ :param vrf_id: The VRF/table in which to proxy
+ """
+
+ return self.api(
+ self.papi.proxy_arp_add_del,
+ {'vrf_id': vrf_id,
+ 'is_add': is_add,
+ 'low_address': low_address,
+ 'hi_address': hi_address,
+ }
+ )
+
+ def proxy_arp_intfc_enable_disable(self,
+ sw_if_index,
+ is_enable=1):
+ """ Enable/Disable an interface for proxy ARP requests
+
+ :param sw_if_index: Interface
+ :param enable_disable: Enable/Disable
+ """
+
+ return self.api(
+ self.papi.proxy_arp_intfc_enable_disable,
+ {'sw_if_index': sw_if_index,
+ 'enable_disable': is_enable
+ }
+ )
+
+ def reset_vrf(self,
+ vrf_id,
+ is_ipv6=0,
+ ):
+ """ Reset VRF (remove all routes etc.) request.
+
+ :param int vrf_id: ID of the FIB table / VRF to reset.
+ :param int is_ipv6: 1 for IPv6 neighbor, 0 for IPv4. (Default = 0)
+ """
+
+ return self.api(
+ self.papi.reset_vrf,
+ {'vrf_id': vrf_id,
+ 'is_ipv6': is_ipv6,
+ }
+ )
+
+ def reset_fib(self,
+ vrf_id,
+ is_ipv6=0,
+ ):
+ """ Reset VRF (remove all routes etc.) request.
+
+ :param int vrf_id: ID of the FIB table / VRF to reset.
+ :param int is_ipv6: 1 for IPv6 neighbor, 0 for IPv4. (Default = 0)
+ """
+
+ return self.api(
+ self.papi.reset_fib,
+ {'vrf_id': vrf_id,
+ 'is_ipv6': is_ipv6,
+ }
+ )
+
+ def ip_dump(self,
+ is_ipv6=0,
+ ):
+ """ Return IP dump.
+
+ :param int is_ipv6: 1 for IPv6 neighbor, 0 for IPv4. (Default = 0)
+ """
+
+ return self.api(
+ self.papi.ip_dump,
+ {'is_ipv6': is_ipv6,
+ }
+ )
+
+ def sw_interface_span_enable_disable(
+ self, sw_if_index_from, sw_if_index_to, state=1, is_l2=0):
+ """
+
+ :param sw_if_index_from:
+ :param sw_if_index_to:
+ :param state:
+ :param is_l2:
+ """
+ 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,
+ 'is_l2': is_l2,
+ })
+
+ def gre_tunnel_add_del(self,
+ src_address,
+ dst_address,
+ outer_fib_id=0,
+ is_teb=0,
+ is_add=1,
+ is_ip6=0):
+ """ Add a GRE tunnel
+
+ :param src_address:
+ :param dst_address:
+ :param outer_fib_id: (Default value = 0)
+ :param is_add: (Default value = 1)
+ :param is_ipv6: (Default value = 0)
+ :param is_teb: (Default value = 0)
+ """
+
+ 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}
+ )
+
+ def mpls_fib_dump(self):
+ return self.api(self.papi.mpls_fib_dump, {})
+
+ def mpls_table_add_del(
+ self,
+ table_id,
+ is_add=1):
+ """
+
+ :param table_id
+ :param is_add: (Default value = 1)
+
+ """
+
+ return self.api(
+ self.papi.mpls_table_add_del,
+ {'mt_table_id': table_id,
+ 'mt_is_add': is_add})
+
+ def mpls_route_add_del(
+ self,
+ label,
+ eos,
+ next_hop_proto,
+ next_hop_address,
+ next_hop_sw_if_index=0xFFFFFFFF,
+ 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,
+ is_resolve_host=0,
+ is_resolve_attached=0,
+ is_interface_rx=0,
+ is_rpf_id=0,
+ is_multicast=0,
+ is_add=1,
+ is_drop=0,
+ is_multipath=0,
+ classify_table_index=0xFFFFFFFF,
+ is_classify=0,
+ not_last=0):
+ """
+
+ :param dst_address_length:
+ :param next_hop_sw_if_index: (Default value = 0xFFFFFFFF)
+ :param dst_address:
+ :param next_hop_address:
+ :param next_hop_sw_if_index: (Default value = 0xFFFFFFFF)
+ :param vrf_id: (Default value = 0)
+ :param lookup_in_vrf: (Default value = 0)
+ :param classify_table_index: (Default value = 0xFFFFFFFF)
+ :param is_add: (Default value = 1)
+ :param is_drop: (Default value = 0)
+ :param is_ipv6: (Default value = 0)
+ :param is_local: (Default value = 0)
+ :param is_classify: (Default value = 0)
+ :param is_multipath: (Default value = 0)
+ :param is_multicast: (Default value = 0)
+ :param is_resolve_host: (Default value = 0)
+ :param is_resolve_attached: (Default value = 0)
+ :param not_last: (Default value = 0)
+ :param next_hop_weight: (Default value = 1)
+
+ """
+
+ 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_is_add': is_add,
+ 'mr_is_classify': is_classify,
+ 'mr_is_multipath': is_multipath,
+ 'mr_is_multicast': is_multicast,
+ 'mr_is_resolve_host': is_resolve_host,
+ 'mr_is_resolve_attached': is_resolve_attached,
+ 'mr_is_interface_rx': is_interface_rx,
+ 'mr_is_rpf_id': is_rpf_id,
+ 'mr_next_hop_proto': next_hop_proto,
+ '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,
+ label,
+ dst_address,
+ dst_address_length,
+ table_id=0,
+ ip_table_id=0,
+ is_ip4=1,
+ is_bind=1):
+ """
+ """
+ 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_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,
+ tun_sw_if_index,
+ next_hop_proto_is_ip4,
+ next_hop_address,
+ 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,
+ is_add=1,
+ l2_only=0,
+ is_multicast=0):
+ """
+
+ :param dst_address_length:
+ :param next_hop_sw_if_index: (Default value = 0xFFFFFFFF)
+ :param dst_address:
+ :param next_hop_address:
+ :param next_hop_sw_if_index: (Default value = 0xFFFFFFFF)
+ :param vrf_id: (Default value = 0)
+ :param lookup_in_vrf: (Default value = 0)
+ :param classify_table_index: (Default value = 0xFFFFFFFF)
+ :param is_add: (Default value = 1)
+ :param is_drop: (Default value = 0)
+ :param is_ipv6: (Default value = 0)
+ :param is_local: (Default value = 0)
+ :param is_classify: (Default value = 0)
+ :param is_multipath: (Default value = 0)
+ :param is_resolve_host: (Default value = 0)
+ :param is_resolve_attached: (Default value = 0)
+ :param next_hop_weight: (Default value = 1)
+ :param is_multicast: (Default value = 0)
+
+ """
+ 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_is_multicast': is_multicast,
+ '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 nat44_interface_add_del_feature(
+ self,
+ sw_if_index,
+ is_inside=1,
+ is_add=1):
+ """Enable/disable NAT44 feature on the interface
+
+ :param sw_if_index: Software index of the interface
+ :param is_inside: 1 if inside, 0 if outside (Default value = 1)
+ :param is_add: 1 if add, 0 if delete (Default value = 1)
+ """
+ return self.api(
+ self.papi.nat44_interface_add_del_feature,
+ {'is_add': is_add,
+ 'is_inside': is_inside,
+ 'sw_if_index': sw_if_index})
+
+ def nat44_interface_add_del_output_feature(
+ self,
+ sw_if_index,
+ is_inside=1,
+ is_add=1):
+ """Enable/disable NAT44 output feature on the interface
+
+ :param sw_if_index: Software index of the interface
+ :param is_inside: 1 if inside, 0 if outside (Default value = 1)
+ :param is_add: 1 if add, 0 if delete (Default value = 1)
+ """
+ return self.api(
+ self.papi.nat44_interface_add_del_output_feature,
+ {'is_add': is_add,
+ 'is_inside': is_inside,
+ 'sw_if_index': sw_if_index})
+
+ def nat44_add_del_static_mapping(
+ self,
+ local_ip,
+ external_ip=0,
+ external_sw_if_index=0xFFFFFFFF,
+ local_port=0,
+ external_port=0,
+ addr_only=1,
+ vrf_id=0,
+ protocol=0,
+ is_add=1):
+ """Add/delete NAT44 static mapping
+
+ :param local_ip: Local IP address
+ :param external_ip: External IP address
+ :param external_sw_if_index: External interface instead of IP address
+ :param local_port: Local port number (Default value = 0)
+ :param external_port: External port number (Default value = 0)
+ :param addr_only: 1 if address only mapping, 0 if address and port
+ :param vrf_id: VRF ID
+ :param protocol: IP protocol (Default value = 0)
+ :param is_add: 1 if add, 0 if delete (Default value = 1)
+ """
+ return self.api(
+ self.papi.nat44_add_del_static_mapping,
+ {'is_add': is_add,
+ 'addr_only': addr_only,
+ 'local_ip_address': local_ip,
+ 'external_ip_address': external_ip,
+ 'local_port': local_port,
+ 'external_port': external_port,
+ 'external_sw_if_index': external_sw_if_index,
+ 'vrf_id': vrf_id,
+ 'protocol': protocol})
+
+ def nat44_add_del_address_range(
+ self,
+ first_ip_address,
+ last_ip_address,
+ is_add=1,
+ vrf_id=0xFFFFFFFF):
+ """Add/del NAT44 address range
+
+ :param first_ip_address: First IP address
+ :param last_ip_address: Last IP address
+ :param vrf_id: VRF id for the address range
+ :param is_add: 1 if add, 0 if delete (Default value = 1)
+ """
+ return self.api(
+ self.papi.nat44_add_del_address_range,
+ {'first_ip_address': first_ip_address,
+ 'last_ip_address': last_ip_address,
+ 'vrf_id': vrf_id,
+ 'is_add': is_add})
+
+ def nat44_address_dump(self):
+ """Dump NAT44 addresses
+ :return: Dictionary of NAT44 addresses
+ """
+ return self.api(self.papi.nat44_address_dump, {})
+
+ def nat44_interface_dump(self):
+ """Dump interfaces with NAT44 feature
+ :return: Dictionary of interfaces with NAT44 feature
+ """
+ return self.api(self.papi.nat44_interface_dump, {})
+
+ def nat44_interface_output_feature_dump(self):
+ """Dump interfaces with NAT44 output feature
+ :return: Dictionary of interfaces with NAT44 output feature
+ """
+ return self.api(self.papi.nat44_interface_output_feature_dump, {})
+
+ def nat44_static_mapping_dump(self):
+ """Dump NAT44 static mappings
+ :return: Dictionary of NAT44 static mappings
+ """
+ return self.api(self.papi.nat44_static_mapping_dump, {})
+
+ def nat_show_config(self):
+ """Show NAT plugin config
+ :return: NAT plugin config parameters
+ """
+ return self.api(self.papi.nat_show_config, {})
+
+ def nat44_add_interface_addr(
+ self,
+ sw_if_index,
+ is_add=1):
+ """Add/del NAT44 address from interface
+
+ :param sw_if_index: Software index of the interface
+ :param is_add: 1 if add, 0 if delete (Default value = 1)
+ """
+ return self.api(self.papi.nat44_add_del_interface_addr,
+ {'is_add': is_add, 'sw_if_index': sw_if_index})
+
+ def nat44_interface_addr_dump(self):
+ """Dump NAT44 addresses interfaces
+ :return: Dictionary of NAT44 addresses interfaces
+ """
+ return self.api(self.papi.nat44_interface_addr_dump, {})
+
+ def nat_ipfix(
+ self,
+ domain_id=1,
+ src_port=4739,
+ enable=1):
+ """Enable/disable NAT IPFIX logging
+
+ :param domain_id: Observation domain ID (Default value = 1)
+ :param src_port: Source port number (Default value = 4739)
+ :param enable: 1 if enable, 0 if disable (Default value = 1)
+ """
+ return self.api(
+ self.papi.nat_ipfix_enable_disable,
+ {'domain_id': domain_id,
+ 'src_port': src_port,
+ 'enable': enable})
+
+ def nat44_user_session_dump(
+ self,
+ ip_address,
+ vrf_id):
+ """Dump NAT44 user's sessions
+
+ :param ip_address: ip adress of the user to be dumped
+ :param cpu_index: cpu_index on which the user is
+ :param vrf_id: VRF ID
+ :return: Dictionary of S-NAT sessions
+ """
+ return self.api(
+ self.papi.nat44_user_session_dump,
+ {'ip_address': ip_address,
+ 'vrf_id': vrf_id})
+
+ def nat44_user_dump(self):
+ """Dump NAT44 users
+
+ :return: Dictionary of NAT44 users
+ """
+ return self.api(self.papi.nat44_user_dump, {})
+
+ def nat44_add_del_lb_static_mapping(
+ self,
+ external_addr,
+ external_port,
+ protocol,
+ vrf_id=0,
+ local_num=0,
+ locals=[],
+ is_add=1):
+ """Add/delete NAT44 load balancing static mapping
+
+ :param is_add - 1 if add, 0 if delete
+ """
+ return self.api(
+ self.papi.nat44_add_del_lb_static_mapping,
+ {'is_add': is_add,
+ 'external_addr': external_addr,
+ 'external_port': external_port,
+ 'protocol': protocol,
+ 'vrf_id': vrf_id,
+ 'local_num': local_num,
+ 'locals': locals})
+
+ def nat44_lb_static_mapping_dump(self):
+ """Dump NAT44 load balancing static mappings
+
+ :return: Dictionary of NAT44 load balancing static mapping
+ """
+ return self.api(self.papi.nat44_lb_static_mapping_dump, {})
+
+ def nat_det_add_del_map(
+ self,
+ in_addr,
+ in_plen,
+ out_addr,
+ out_plen,
+ is_add=1):
+ """Add/delete deterministic NAT mapping
+
+ :param is_add - 1 if add, 0 if delete
+ :param in_addr - inside IP address
+ :param in_plen - inside IP address prefix length
+ :param out_addr - outside IP address
+ :param out_plen - outside IP address prefix length
+ """
+ return self.api(
+ self.papi.nat_det_add_del_map,
+ {'is_add': is_add,
+ 'is_nat44': 1,
+ 'in_addr': in_addr,
+ 'in_plen': in_plen,
+ 'out_addr': out_addr,
+ 'out_plen': out_plen})
+
+ def nat_det_forward(
+ self,
+ in_addr):
+ """Get outside address and port range from inside address
+
+ :param in_addr - inside IP address
+ """
+ return self.api(
+ self.papi.nat_det_forward,
+ {'in_addr': in_addr,
+ 'is_nat44': 1})
+
+ def nat_det_reverse(
+ self,
+ out_addr,
+ out_port):
+ """Get inside address from outside address and port
+
+ :param out_addr - outside IP address
+ :param out_port - outside port
+ """
+ return self.api(
+ self.papi.nat_det_reverse,
+ {'out_addr': out_addr,
+ 'out_port': out_port})
+
+ def nat_det_map_dump(self):
+ """Dump deterministic NAT mappings
+
+ :return: Dictionary of deterministic NAT mappings
+ """
+ return self.api(self.papi.nat_det_map_dump, {})
+
+ def nat_det_set_timeouts(
+ self,
+ udp=300,
+ tcp_established=7440,
+ tcp_transitory=240,
+ icmp=60):
+ """Set values of timeouts for deterministic NAT (in seconds)
+
+ :param udp - UDP timeout (Default value = 300)
+ :param tcp_established - TCP established timeout (Default value = 7440)
+ :param tcp_transitory - TCP transitory timeout (Default value = 240)
+ :param icmp - ICMP timeout (Default value = 60)
+ """
+ return self.api(
+ self.papi.nat_det_set_timeouts,
+ {'udp': udp,
+ 'tcp_established': tcp_established,
+ 'tcp_transitory': tcp_transitory,
+ 'icmp': icmp})
+
+ def nat_det_get_timeouts(self):
+ """Get values of timeouts for deterministic NAT
+
+ :return: Timeouts for deterministic NAT (in seconds)
+ """
+ return self.api(self.papi.nat_det_get_timeouts, {})
+
+ def nat_det_close_session_out(
+ self,
+ out_addr,
+ out_port,
+ ext_addr,
+ ext_port):
+ """Close deterministic NAT session using outside address and port
+
+ :param out_addr - outside IP address
+ :param out_port - outside port
+ :param ext_addr - external host IP address
+ :param ext_port - external host port
+ """
+ return self.api(
+ self.papi.nat_det_close_session_out,
+ {'out_addr': out_addr,
+ 'out_port': out_port,
+ 'ext_addr': ext_addr,
+ 'ext_port': ext_port})
+
+ def nat_det_close_session_in(
+ self,
+ in_addr,
+ in_port,
+ ext_addr,
+ ext_port):
+ """Close deterministic NAT session using inside address and port
+
+ :param in_addr - inside IP address
+ :param in_port - inside port
+ :param ext_addr - external host IP address
+ :param ext_port - external host port
+ """
+ return self.api(
+ self.papi.nat_det_close_session_in,
+ {'in_addr': in_addr,
+ 'in_port': in_port,
+ 'ext_addr': ext_addr,
+ 'ext_port': ext_port,
+ 'is_nat44': 1})
+
+ def nat_det_session_dump(
+ self,
+ user_addr):
+ """Dump deterministic NAT sessions belonging to a user
+
+ :param user_addr - inside IP address of the user
+ :return: Dictionary of deterministic NAT sessions
+ """
+ return self.api(
+ self.papi.nat_det_session_dump,
+ {'is_nat44': 1,
+ 'user_addr': user_addr})
+
+ def nat64_add_del_pool_addr_range(
+ self,
+ start_addr,
+ end_addr,
+ vrf_id=0xFFFFFFFF,
+ is_add=1):
+ """Add/del address range to NAT64 pool
+
+ :param start_addr: First IP address
+ :param end_addr: Last IP address
+ :param vrf_id: VRF id for the address range
+ :param is_add: 1 if add, 0 if delete (Default value = 1)
+ """
+ return self.api(
+ self.papi.nat64_add_del_pool_addr_range,
+ {'start_addr': start_addr,
+ 'end_addr': end_addr,
+ 'vrf_id': vrf_id,
+ 'is_add': is_add})
+
+ def nat64_pool_addr_dump(self):
+ """Dump NAT64 pool addresses
+ :return: Dictionary of NAT64 pool addresses
+ """
+ return self.api(self.papi.nat64_pool_addr_dump, {})
+
+ def nat64_add_del_interface(
+ self,
+ sw_if_index,
+ is_inside=1,
+ is_add=1):
+ """Enable/disable NAT64 feature on the interface
+ :param sw_if_index: Index of the interface
+ :param is_inside: 1 if inside, 0 if outside (Default value = 1)
+ :param is_add: 1 if add, 0 if delete (Default value = 1)
+ """
+ return self.api(
+ self.papi.nat64_add_del_interface,
+ {'sw_if_index': sw_if_index,
+ 'is_inside': is_inside,
+ 'is_add': is_add})
+
+ def nat64_interface_dump(self):
+ """Dump interfaces with NAT64 feature
+ :return: Dictionary of interfaces with NAT64 feature
+ """
+ return self.api(self.papi.nat64_interface_dump, {})
+
+ def nat64_add_del_static_bib(
+ self,
+ in_ip,
+ out_ip,
+ in_port,
+ out_port,
+ protocol,
+ vrf_id=0,
+ is_add=1):
+ """Add/delete S-NAT static BIB entry
+
+ :param in_ip: Inside IPv6 address
+ :param out_ip: Outside IPv4 address
+ :param in_port: Inside port number
+ :param out_port: Outside port number
+ :param protocol: IP protocol
+ :param vrf_id: VRF ID (Default value = 0)
+ :param is_add: 1 if add, 0 if delete (Default value = 1)
+ """
+ return self.api(
+ self.papi.nat64_add_del_static_bib,
+ {'i_addr': in_ip,
+ 'o_addr': out_ip,
+ 'i_port': in_port,
+ 'o_port': out_port,
+ 'vrf_id': vrf_id,
+ 'proto': protocol,
+ 'is_add': is_add})
+
+ def nat64_bib_dump(self, protocol=255):
+ """Dump NAT64 BIB
+
+ :param protocol: IP protocol (Default value = 255, all BIBs)
+ :returns: Dictionary of NAT64 BIB entries
+ """
+ return self.api(self.papi.nat64_bib_dump, {'proto': protocol})
+
+ def nat64_set_timeouts(self, udp=300, icmp=60, tcp_trans=240, tcp_est=7440,
+ tcp_incoming_syn=6):
+ """Set values of timeouts for NAT64 (in seconds)
+
+ :param udpi: UDP timeout (Default value = 300)
+ :param icmp: ICMP timeout (Default value = 60)
+ :param tcp_trans: TCP transitory timeout (Default value = 240)
+ :param tcp_est: TCP established timeout (Default value = 7440)
+ :param tcp_incoming_syn: TCP incoming SYN timeout (Default value = 6)
+ """
+ return self.api(
+ self.papi.nat64_set_timeouts,
+ {'udp': udp,
+ 'icmp': icmp,
+ 'tcp_trans': tcp_trans,
+ 'tcp_est': tcp_est,
+ 'tcp_incoming_syn': tcp_incoming_syn})
+
+ def nat64_get_timeouts(self):
+ """Get values of timeouts for NAT64
+
+ :return: Timeouts for NAT64 (in seconds)
+ """
+ return self.api(self.papi.nat64_get_timeouts, {})
+
+ def nat64_st_dump(self, protocol=255):
+ """Dump NAT64 session table
+
+ :param protocol: IP protocol (Default value = 255, all STs)
+ :returns: Dictionary of NAT64 sesstion table entries
+ """
+ return self.api(self.papi.nat64_st_dump, {'proto': protocol})
+
+ def nat64_add_del_prefix(self, prefix, plen, vrf_id=0, is_add=1):
+ """Add/del NAT64 prefix
+
+ :param prefix: NAT64 prefix
+ :param plen: NAT64 prefix length
+ :param vrf_id: VRF id of tenant (Default 0)
+ :param is_add: 1 if add, 0 if delete (Default value = 1)
+ """
+ return self.api(
+ self.papi.nat64_add_del_prefix,
+ {'prefix': prefix,
+ 'prefix_len': plen,
+ 'vrf_id': vrf_id,
+ 'is_add': is_add})
+
+ def nat64_prefix_dump(self):
+ """Dump NAT64 prefix
+
+ :returns: Dictionary of NAT64 prefixes
+ """
+ return self.api(self.papi.nat64_prefix_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,
+ bfd_key_id=None, conf_key_id=None):
+ if bfd_key_id is None:
+ 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,
+ })
+ else:
+ 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,
+ 'is_authenticated': 1,
+ 'bfd_key_id': bfd_key_id,
+ 'conf_key_id': conf_key_id,
+ })
+
+ def bfd_udp_mod(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_mod,
+ {
+ '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_auth_activate(self, sw_if_index, local_addr, peer_addr,
+ is_ipv6=0, bfd_key_id=None, conf_key_id=None,
+ is_delayed=False):
+ return self.api(self.papi.bfd_udp_auth_activate,
+ {
+ 'sw_if_index': sw_if_index,
+ 'local_addr': local_addr,
+ 'peer_addr': peer_addr,
+ 'is_ipv6': is_ipv6,
+ 'is_delayed': 1 if is_delayed else 0,
+ 'bfd_key_id': bfd_key_id,
+ 'conf_key_id': conf_key_id,
+ })
+
+ def bfd_udp_auth_deactivate(self, sw_if_index, local_addr, peer_addr,
+ is_ipv6=0, is_delayed=False):
+ return self.api(self.papi.bfd_udp_auth_deactivate,
+ {
+ 'sw_if_index': sw_if_index,
+ 'local_addr': local_addr,
+ 'peer_addr': peer_addr,
+ 'is_ipv6': is_ipv6,
+ 'is_delayed': 1 if is_delayed else 0,
+ })
+
+ 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_udp_session_set_flags(self, admin_up_down, sw_if_index, local_addr,
+ peer_addr, is_ipv6=0):
+ return self.api(self.papi.bfd_udp_session_set_flags, {
+ 'admin_up_down': admin_up_down,
+ 'sw_if_index': sw_if_index,
+ 'local_addr': local_addr,
+ 'peer_addr': peer_addr,
+ 'is_ipv6': is_ipv6,
+ })
+
+ def want_bfd_events(self, enable_disable=1):
+ return self.api(self.papi.want_bfd_events, {
+ 'enable_disable': enable_disable,
+ 'pid': os.getpid(),
+ })
+
+ def bfd_auth_set_key(self, conf_key_id, auth_type, key):
+ return self.api(self.papi.bfd_auth_set_key, {
+ 'conf_key_id': conf_key_id,
+ 'auth_type': auth_type,
+ 'key': key,
+ 'key_len': len(key),
+ })
+
+ def bfd_auth_del_key(self, conf_key_id):
+ return self.api(self.papi.bfd_auth_del_key, {
+ 'conf_key_id': conf_key_id,
+ })
+
+ def bfd_auth_keys_dump(self):
+ return self.api(self.papi.bfd_auth_keys_dump, {})
+
+ def bfd_udp_set_echo_source(self, sw_if_index):
+ return self.api(self.papi.bfd_udp_set_echo_source,
+ {'sw_if_index': sw_if_index})
+
+ def bfd_udp_del_echo_source(self):
+ return self.api(self.papi.bfd_udp_del_echo_source, {})
+
+ def classify_add_del_table(
+ self,
+ is_add,
+ mask,
+ match_n_vectors=1,
+ table_index=0xFFFFFFFF,
+ nbuckets=2,
+ memory_size=2097152,
+ skip_n_vectors=0,
+ next_table_index=0xFFFFFFFF,
+ miss_next_index=0xFFFFFFFF,
+ current_data_flag=0,
+ current_data_offset=0):
+ """
+ :param is_add:
+ :param mask:
+ :param match_n_vectors: (Default value = 1)
+ :param table_index: (Default value = 0xFFFFFFFF)
+ :param nbuckets: (Default value = 2)
+ :param memory_size: (Default value = 2097152)
+ :param skip_n_vectors: (Default value = 0)
+ :param next_table_index: (Default value = 0xFFFFFFFF)
+ :param miss_next_index: (Default value = 0xFFFFFFFF)
+ :param current_data_flag: (Default value = 0)
+ :param current_data_offset: (Default value = 0)
+ """
+
+ return self.api(
+ self.papi.classify_add_del_table,
+ {'is_add': is_add,
+ 'table_index': table_index,
+ 'nbuckets': nbuckets,
+ 'memory_size': memory_size,
+ 'skip_n_vectors': skip_n_vectors,
+ 'match_n_vectors': match_n_vectors,
+ 'next_table_index': next_table_index,
+ 'miss_next_index': miss_next_index,
+ 'current_data_flag': current_data_flag,
+ 'current_data_offset': current_data_offset,
+ 'mask': mask})
+
+ def classify_add_del_session(
+ self,
+ is_add,
+ table_index,
+ match,
+ opaque_index=0xFFFFFFFF,
+ hit_next_index=0xFFFFFFFF,
+ advance=0,
+ action=0,
+ metadata=0):
+ """
+ :param is_add:
+ :param table_index:
+ :param match:
+ :param opaque_index: (Default value = 0xFFFFFFFF)
+ :param hit_next_index: (Default value = 0xFFFFFFFF)
+ :param advance: (Default value = 0)
+ :param action: (Default value = 0)
+ :param metadata: (Default value = 0)
+ """
+
+ return self.api(
+ self.papi.classify_add_del_session,
+ {'is_add': is_add,
+ 'table_index': table_index,
+ 'hit_next_index': hit_next_index,
+ 'opaque_index': opaque_index,
+ 'advance': advance,
+ 'action': action,
+ 'metadata': metadata,
+ 'match': match})
+
+ def input_acl_set_interface(
+ self,
+ is_add,
+ sw_if_index,
+ ip4_table_index=0xFFFFFFFF,
+ ip6_table_index=0xFFFFFFFF,
+ l2_table_index=0xFFFFFFFF):
+ """
+ :param is_add:
+ :param sw_if_index:
+ :param ip4_table_index: (Default value = 0xFFFFFFFF)
+ :param ip6_table_index: (Default value = 0xFFFFFFFF)
+ :param l2_table_index: (Default value = 0xFFFFFFFF)
+ """
+
+ return self.api(
+ self.papi.input_acl_set_interface,
+ {'sw_if_index': sw_if_index,
+ 'ip4_table_index': ip4_table_index,
+ 'ip6_table_index': ip6_table_index,
+ 'l2_table_index': l2_table_index,
+ 'is_add': is_add})
+
+ def set_ipfix_exporter(
+ self,
+ collector_address,
+ src_address,
+ path_mtu,
+ template_interval,
+ vrf_id=0,
+ collector_port=4739,
+ udp_checksum=0):
+ return self.api(
+ self.papi.set_ipfix_exporter,
+ {
+ 'collector_address': collector_address,
+ 'collector_port': collector_port,
+ 'src_address': src_address,
+ 'vrf_id': vrf_id,
+ 'path_mtu': path_mtu,
+ 'template_interval': template_interval,
+ 'udp_checksum': udp_checksum,
+ })
+
+ def dhcp_proxy_config(self,
+ dhcp_server,
+ dhcp_src_address,
+ rx_table_id=0,
+ server_table_id=0,
+ is_add=1,
+ is_ipv6=0):
+ return self.api(
+ self.papi.dhcp_proxy_config,
+ {
+ 'rx_vrf_id': rx_table_id,
+ 'server_vrf_id': server_table_id,
+ 'is_ipv6': is_ipv6,
+ 'is_add': is_add,
+ 'dhcp_server': dhcp_server,
+ 'dhcp_src_address': dhcp_src_address,
+ })
+
+ def dhcp_proxy_set_vss(self,
+ table_id,
+ fib_id,
+ oui,
+ is_add=1,
+ is_ip6=0):
+ return self.api(
+ self.papi.dhcp_proxy_set_vss,
+ {
+ 'tbl_id': table_id,
+ 'fib_id': fib_id,
+ 'is_ipv6': is_ip6,
+ 'is_add': is_add,
+ 'oui': oui,
+ })
+
+ def dhcp_client(self,
+ sw_if_index,
+ hostname,
+ client_id='',
+ is_add=1,
+ want_dhcp_events=0):
+ return self.api(
+ self.papi.dhcp_client_config,
+ {
+ 'sw_if_index': sw_if_index,
+ 'hostname': hostname,
+ 'client_id': client_id,
+ 'is_add': is_add,
+ 'want_dhcp_event': want_dhcp_events,
+ 'pid': os.getpid(),
+ })
+
+ def ip_mroute_add_del(self,
+ src_address,
+ grp_address,
+ grp_address_length,
+ e_flags,
+ next_hop_sw_if_index,
+ i_flags,
+ rpf_id=0,
+ table_id=0,
+ is_add=1,
+ is_ipv6=0,
+ is_local=0):
+ """
+ """
+ return self.api(
+ self.papi.ip_mroute_add_del,
+ {'next_hop_sw_if_index': next_hop_sw_if_index,
+ 'entry_flags': e_flags,
+ 'itf_flags': i_flags,
+ 'table_id': table_id,
+ 'rpf_id': rpf_id,
+ 'is_add': is_add,
+ 'is_ipv6': is_ipv6,
+ 'is_local': is_local,
+ 'grp_address_length': grp_address_length,
+ 'grp_address': grp_address,
+ 'src_address': src_address})
+
+ def mfib_signal_dump(self):
+ return self.api(self.papi.mfib_signal_dump, {})
+
+ def ip_mfib_dump(self):
+ return self.api(self.papi.ip_mfib_dump, {})
+
+ def lisp_enable_disable(self, is_enabled):
+ return self.api(
+ self.papi.lisp_enable_disable,
+ {
+ 'is_en': is_enabled,
+ })
+
+ def lisp_locator_set(self,
+ ls_name,
+ is_add=1):
+ return self.api(
+ self.papi.lisp_add_del_locator_set,
+ {
+ 'is_add': is_add,
+ 'locator_set_name': ls_name
+ })
+
+ def lisp_locator_set_dump(self):
+ return self.api(self.papi.lisp_locator_set_dump, {})
+
+ def lisp_locator(self,
+ ls_name,
+ sw_if_index,
+ priority=1,
+ weight=1,
+ is_add=1):
+ return self.api(
+ self.papi.lisp_add_del_locator,
+ {
+ 'is_add': is_add,
+ 'locator_set_name': ls_name,
+ 'sw_if_index': sw_if_index,
+ 'priority': priority,
+ 'weight': weight
+ })
+
+ def lisp_locator_dump(self, is_index_set, ls_name=None, ls_index=0):
+ return self.api(
+ self.papi.lisp_locator_dump,
+ {
+ 'is_index_set': is_index_set,
+ 'ls_name': ls_name,
+ 'ls_index': ls_index,
+ })
+
+ def lisp_local_mapping(self,
+ ls_name,
+ eid_type,
+ eid,
+ prefix_len,
+ vni=0,
+ key_id=0,
+ key="",
+ is_add=1):
+ return self.api(
+ self.papi.lisp_add_del_local_eid,
+ {
+ 'locator_set_name': ls_name,
+ 'is_add': is_add,
+ 'eid_type': eid_type,
+ 'eid': eid,
+ 'prefix_len': prefix_len,
+ 'vni': vni,
+ 'key_id': key_id,
+ 'key': key
+ })
+
+ def lisp_eid_table_dump(self,
+ eid_set=0,
+ prefix_length=0,
+ vni=0,
+ eid_type=0,
+ eid=None,
+ filter_opt=0):
+ return self.api(
+ self.papi.lisp_eid_table_dump,
+ {
+ 'eid_set': eid_set,
+ 'prefix_length': prefix_length,
+ 'vni': vni,
+ 'eid_type': eid_type,
+ 'eid': eid,
+ 'filter': filter_opt,
+ })
+
+ def lisp_remote_mapping(self,
+ eid_type,
+ eid,
+ eid_prefix_len=0,
+ vni=0,
+ rlocs=[],
+ rlocs_num=0,
+ is_src_dst=0,
+ is_add=1):
+ return self.api(
+ self.papi.lisp_add_del_remote_mapping,
+ {
+ 'is_add': is_add,
+ 'eid_type': eid_type,
+ 'eid': eid,
+ 'eid_len': eid_prefix_len,
+ 'rloc_num': rlocs_num,
+ 'rlocs': rlocs,
+ 'vni': vni,
+ 'is_src_dst': is_src_dst,
+ })
+
+ def lisp_adjacency(self,
+ leid,
+ reid,
+ leid_len,
+ reid_len,
+ eid_type,
+ is_add=1,
+ vni=0):
+ return self.api(
+ self.papi.lisp_add_del_adjacency,
+ {
+ 'is_add': is_add,
+ 'vni': vni,
+ 'eid_type': eid_type,
+ 'leid': leid,
+ 'reid': reid,
+ 'leid_len': leid_len,
+ 'reid_len': reid_len,
+ })
+
+ def lisp_adjacencies_get(self, vni=0):
+ return self.api(
+ self.papi.lisp_adjacencies_get,
+ {
+ 'vni': vni
+ })
+
+ def map_add_domain(self,
+ ip6_prefix,
+ ip6_prefix_len,
+ ip6_src,
+ ip6_src_prefix_len,
+ ip4_prefix,
+ ip4_prefix_len,
+ ea_bits_len=0,
+ psid_offset=0,
+ psid_length=0,
+ is_translation=0,
+ mtu=1280):
+ return self.api(
+ self.papi.map_add_domain,
+ {
+ 'ip6_prefix': ip6_prefix,
+ 'ip6_prefix_len': ip6_prefix_len,
+ 'ip4_prefix': ip4_prefix,
+ 'ip4_prefix_len': ip4_prefix_len,
+ 'ip6_src': ip6_src,
+ 'ip6_src_prefix_len': ip6_src_prefix_len,
+ 'ea_bits_len': ea_bits_len,
+ 'psid_offset': psid_offset,
+ 'psid_length': psid_length,
+ 'is_translation': is_translation,
+ 'mtu': mtu
+ })
+
+ def gtpu_add_del_tunnel(
+ self,
+ src_addr,
+ dst_addr,
+ is_add=1,
+ is_ipv6=0,
+ mcast_sw_if_index=0xFFFFFFFF,
+ encap_vrf_id=0,
+ decap_next_index=0xFFFFFFFF,
+ teid=0):
+ """
+
+ :param is_add: (Default value = 1)
+ :param is_ipv6: (Default value = 0)
+ :param src_addr:
+ :param dst_addr:
+ :param mcast_sw_if_index: (Default value = 0xFFFFFFFF)
+ :param encap_vrf_id: (Default value = 0)
+ :param decap_next_index: (Default value = 0xFFFFFFFF)
+ :param teid: (Default value = 0)
+
+ """
+ return self.api(self.papi.gtpu_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,
+ 'teid': teid})
+
+ def vxlan_gpe_add_del_tunnel(
+ self,
+ src_addr,
+ dst_addr,
+ mcast_sw_if_index=0xFFFFFFFF,
+ is_add=1,
+ is_ipv6=0,
+ encap_vrf_id=0,
+ decap_vrf_id=0,
+ protocol=3,
+ vni=0):
+ """
+
+ :param local:
+ :param remote:
+ :param is_add: (Default value = 1)
+ :param is_ipv6: (Default value = 0)
+ :param encap_vrf_id: (Default value = 0)
+ :param decap_vrf_id: (Default value = 0)
+ :param mcast_sw_if_index: (Default value = 0xFFFFFFFF)
+ :param protocol: (Default value = 3)
+ :param vni: (Default value = 0)
+
+ """
+ return self.api(self.papi.vxlan_gpe_add_del_tunnel,
+ {'is_add': is_add,
+ 'is_ipv6': is_ipv6,
+ 'local': src_addr,
+ 'remote': dst_addr,
+ 'mcast_sw_if_index': mcast_sw_if_index,
+ 'encap_vrf_id': encap_vrf_id,
+ 'decap_vrf_id': decap_vrf_id,
+ 'protocol': protocol,
+ 'vni': vni})
+
+ def pppoe_add_del_session(
+ self,
+ client_ip,
+ client_mac,
+ session_id=0,
+ is_add=1,
+ is_ipv6=0,
+ decap_vrf_id=0):
+ """
+
+ :param is_add: (Default value = 1)
+ :param is_ipv6: (Default value = 0)
+ :param client_ip:
+ :param session_id: (Default value = 0)
+ :param client_mac:
+ :param decap_vrf_id: (Default value = 0)
+
+ """
+ return self.api(self.papi.pppoe_add_del_session,
+ {'is_add': is_add,
+ 'is_ipv6': is_ipv6,
+ 'session_id': session_id,
+ 'client_ip': client_ip,
+ 'decap_vrf_id': decap_vrf_id,
+ 'client_mac': client_mac})
+
+ def sr_localsid_add_del(self,
+ localsid_addr,
+ behavior,
+ nh_addr,
+ is_del=0,
+ end_psp=0,
+ sw_if_index=0xFFFFFFFF,
+ vlan_index=0,
+ fib_table=0,
+ ):
+ """ Add/del IPv6 SR local-SID.
+
+ :param localsid_addr:
+ :param behavior: END=1; END.X=2; END.DX2=4; END.DX6=5;
+ :param behavior: END.DX4=6; END.DT6=7; END.DT4=8
+ :param nh_addr:
+ :param is_del: (Default value = 0)
+ :param end_psp: (Default value = 0)
+ :param sw_if_index: (Default value = 0xFFFFFFFF)
+ :param vlan_index: (Default value = 0)
+ :param fib_table: (Default value = 0)
+ """
+ return self.api(
+ self.papi.sr_localsid_add_del,
+ {'is_del': is_del,
+ 'localsid_addr': localsid_addr,
+ 'end_psp': end_psp,
+ 'behavior': behavior,
+ 'sw_if_index': sw_if_index,
+ 'vlan_index': vlan_index,
+ 'fib_table': fib_table,
+ 'nh_addr': nh_addr
+ }
+ )
+
+ def sr_policy_add(
+ self,
+ bsid_addr,
+ weight=1,
+ is_encap=1,
+ type=0,
+ fib_table=0,
+ n_segments=0,
+ segments=[]):
+ """
+ :param bsid_addr: bindingSID of the SR Policy
+ :param weight: weight of the sid list. optional. (default: 1)
+ :param is_encap: (bool) whether SR policy should Encap or SRH insert \
+ (default: Encap)
+ :param type: type/behavior of the SR policy. (default or spray) \
+ (default: default)
+ :param fib_table: VRF where to install the FIB entry for the BSID \
+ (default: 0)
+ :param n_segments: number of segments \
+ (default: 0)
+ :param segments: a vector of IPv6 address composing the segment list \
+ (default: [])
+ """
+ return self.api(
+ self.papi.sr_policy_add,
+ {'bsid_addr': bsid_addr,
+ 'weight': weight,
+ 'is_encap': is_encap,
+ 'type': type,
+ 'fib_table': fib_table,
+ 'n_segments': n_segments,
+ 'segments': segments
+ }
+ )
+
+ def sr_policy_del(
+ self,
+ bsid_addr,
+ sr_policy_index=0):
+ """
+ :param bsid: bindingSID of the SR Policy
+ :param sr_policy_index: index of the sr policy (default: 0)
+ """
+ return self.api(
+ self.papi.sr_policy_del,
+ {'bsid_addr': bsid_addr,
+ 'sr_policy_index': sr_policy_index
+ })
+
+ def sr_steering_add_del(
+ self,
+ is_del,
+ bsid_addr,
+ sr_policy_index,
+ table_id,
+ prefix_addr,
+ mask_width,
+ sw_if_index,
+ traffic_type):
+ """
+ Steer traffic L2 and L3 traffic through a given SR policy
+
+ :param is_del: delete or add
+ :param bsid_addr: bindingSID of the SR Policy (alt to sr_policy_index)
+ :param sr_policy: is the index of the SR Policy (alt to bsid)
+ :param table_id: is the VRF where to install the FIB entry for the BSID
+ :param prefix_addr: is the IPv4/v6 address for L3 traffic type
+ :param mask_width: is the mask for L3 traffic type
+ :param sw_if_index: is the incoming interface for L2 traffic
+ :param traffic_type: type of traffic (IPv4: 4, IPv6: 6, L2: 2)
+ """
+ return self.api(
+ self.papi.sr_steering_add_del,
+ {'is_del': is_del,
+ 'bsid_addr': bsid_addr,
+ 'sr_policy_index': sr_policy_index,
+ 'table_id': table_id,
+ 'prefix_addr': prefix_addr,
+ 'mask_width': mask_width,
+ 'sw_if_index': sw_if_index,
+ 'traffic_type': traffic_type
+ })
+
+ def acl_add_replace(self, acl_index, r, tag='',
+ expected_retval=0):
+ """Add/replace an ACL
+ :param int acl_index: ACL index to replace, 2^32-1 to create new ACL.
+ :param acl_rule r: ACL rules array.
+ :param str tag: symbolic tag (description) for this ACL.
+ :param int count: number of rules.
+ """
+ return self.api(self.papi.acl_add_replace,
+ {'acl_index': acl_index,
+ 'r': r,
+ 'count': len(r),
+ 'tag': tag},
+ expected_retval=expected_retval)
+
+ def acl_del(self, acl_index, expected_retval=0):
+ """
+
+ :param acl_index:
+ :return:
+ """
+ return self.api(self.papi.acl_del,
+ {'acl_index': acl_index},
+ expected_retval=expected_retval)
+
+ def acl_interface_set_acl_list(self, sw_if_index, n_input, acls,
+ expected_retval=0):
+ return self.api(self.papi.acl_interface_set_acl_list,
+ {'sw_if_index': sw_if_index,
+ 'count': len(acls),
+ 'n_input': n_input,
+ 'acls': acls},
+ expected_retval=expected_retval)
+
+ def acl_dump(self, acl_index, expected_retval=0):
+ return self.api(self.papi.acl_dump,
+ {'acl_index': acl_index},
+ expected_retval=expected_retval)
+
+ def macip_acl_add(self, rules, tag=""):
+ """ Add MACIP acl
+
+ :param rules: list of rules for given acl
+ :param tag: acl tag
+ """
+
+ return self.api(self.papi.macip_acl_add,
+ {'r': rules,
+ 'count': len(rules),
+ 'tag': tag})
+
+ def macip_acl_add_replace(self, rules, acl_index=0xFFFFFFFF, tag=""):
+ """ Add MACIP acl
+
+ :param rules: list of rules for given acl
+ :param tag: acl tag
+ """
+
+ return self.api(self.papi.macip_acl_add_replace,
+ {'acl_index': acl_index,
+ 'r': rules,
+ 'count': len(rules),
+ 'tag': tag})
+
+ def macip_acl_del(self, acl_index):
+ """
+
+ :param acl_index:
+ :return:
+ """
+ return self.api(self.papi.macip_acl_del,
+ {'acl_index': acl_index})
+
+ def macip_acl_interface_add_del(self,
+ sw_if_index,
+ acl_index,
+ is_add=1):
+ """ Add MACIP acl to interface
+
+ :param sw_if_index:
+ :param acl_index:
+ :param is_add: (Default value = 1)
+ """
+
+ return self.api(self.papi.macip_acl_interface_add_del,
+ {'is_add': is_add,
+ 'sw_if_index': sw_if_index,
+ 'acl_index': acl_index})
+
+ def macip_acl_interface_get(self):
+ """ Return interface acls dump
+ """
+ return self.api(
+ self.papi.macip_acl_interface_get, {})
+
+ def macip_acl_dump(self, acl_index=4294967295):
+ """ Return MACIP acl dump
+ """
+
+ return self.api(
+ self.papi.macip_acl_dump, {'acl_index': acl_index})
diff --git a/test/vpp_pg_interface.py b/test/vpp_pg_interface.py
new file mode 100644
index 00000000..81e9714a
--- /dev/null
+++ b/test/vpp_pg_interface.py
@@ -0,0 +1,481 @@
+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 """
+ r = test.vapi.pg_create_interface(pg_index)
+ self._sw_if_index = r.sw_if_index
+
+ super(VppPGInterface, self).__init__(test)
+
+ 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")
diff --git a/test/vpp_pppoe_interface.py b/test/vpp_pppoe_interface.py
new file mode 100644
index 00000000..9a8b8699
--- /dev/null
+++ b/test/vpp_pppoe_interface.py
@@ -0,0 +1,79 @@
+
+from vpp_interface import VppInterface
+import socket
+from util import ppp, ppc, mactobinary
+
+
+class VppPppoeInterface(VppInterface):
+ """
+ VPP Pppoe interface
+ """
+
+ def __init__(self, test, client_ip, client_mac,
+ session_id, decap_vrf_id=0):
+ """ Create VPP PPPoE4 interface """
+ self._sw_if_index = 0
+ super(VppPppoeInterface, self).__init__(test)
+ self._test = test
+ self.client_ip = client_ip
+ self.client_mac = client_mac
+ self.session_id = session_id
+ self.decap_vrf_id = decap_vrf_id
+
+ def add_vpp_config(self):
+ cip = socket.inet_pton(socket.AF_INET, self.client_ip)
+ cmac = mactobinary(self.client_mac)
+ r = self.test.vapi.pppoe_add_del_session(
+ cip, cmac,
+ session_id=self.session_id,
+ decap_vrf_id=self.decap_vrf_id)
+ self._sw_if_index = r.sw_if_index
+ self.generate_remote_hosts()
+
+ def remove_vpp_config(self):
+ cip = socket.inet_pton(socket.AF_INET, self.client_ip)
+ cmac = mactobinary(self.client_mac)
+ self.unconfig()
+ r = self.test.vapi.pppoe_add_del_session(
+ cip, cmac,
+ session_id=self.session_id,
+ decap_vrf_id=self.decap_vrf_id,
+ is_add=0)
+
+
+class VppPppoe6Interface(VppInterface):
+ """
+ VPP Pppoe IPv6 interface
+ """
+
+ def __init__(self, test, src_ip, dst_ip, outer_fib_id=0, is_teb=0):
+ """ Create VPP PPPoE6 interface """
+ self._sw_if_index = 0
+ super(VppPppoe6Interface, self).__init__(test)
+ self._test = test
+ self.client_ip = client_ip
+ self.client_mac = client_mac
+ self.session_id = session_id
+ self.decap_vrf_id = decap_vrf_id
+
+ def add_vpp_config(self):
+ cip = socket.inet_pton(socket.AF_INET6, self.client_ip)
+ cmac = mactobinary(self.client_mac)
+ r = self.test.vapi.pppoe_add_del_session(
+ cip, cmac,
+ session_id=self.session_id,
+ decap_vrf_id=self.decap_vrf_id,
+ is_ip6=1)
+ self._sw_if_index = r.sw_if_index
+ self.generate_remote_hosts()
+
+ def remove_vpp_config(self):
+ cip = socket.inet_pton(socket.AF_INET6, self.client_ip)
+ cmac = mactobinary(self.client_mac)
+ self.unconfig()
+ r = self.test.vapi.pppoe_add_del_session(
+ cip, cmac,
+ session_id=self.session_id,
+ decap_vrf_id=self.decap_vrf_id,
+ is_add=0,
+ is_ip6=1)
diff --git a/test/vpp_srv6.py b/test/vpp_srv6.py
new file mode 100644
index 00000000..28ff4b85
--- /dev/null
+++ b/test/vpp_srv6.py
@@ -0,0 +1,238 @@
+"""
+ SRv6 LocalSIDs
+
+ object abstractions for representing SRv6 localSIDs in VPP
+"""
+
+from vpp_object import *
+from socket import inet_pton, inet_ntop, AF_INET, AF_INET6
+
+
+class SRv6LocalSIDBehaviors():
+ # from src/vnet/srv6/sr.h
+ SR_BEHAVIOR_END = 1
+ SR_BEHAVIOR_X = 2
+ SR_BEHAVIOR_T = 3
+ SR_BEHAVIOR_D_FIRST = 4 # Unused. Separator in between regular and D
+ SR_BEHAVIOR_DX2 = 5
+ SR_BEHAVIOR_DX6 = 6
+ SR_BEHAVIOR_DX4 = 7
+ SR_BEHAVIOR_DT6 = 8
+ SR_BEHAVIOR_DT4 = 9
+ SR_BEHAVIOR_LAST = 10 # Must always be the last one
+
+
+class SRv6PolicyType():
+ # from src/vnet/srv6/sr.h
+ SR_POLICY_TYPE_DEFAULT = 0
+ SR_POLICY_TYPE_SPRAY = 1
+
+
+class SRv6PolicySteeringTypes():
+ # from src/vnet/srv6/sr.h
+ SR_STEER_L2 = 2
+ SR_STEER_IPV4 = 4
+ SR_STEER_IPV6 = 6
+
+
+class VppSRv6LocalSID(VppObject):
+ """
+ SRv6 LocalSID
+ """
+
+ def __init__(self, test, localsid_addr, behavior, nh_addr, end_psp,
+ sw_if_index, vlan_index, fib_table):
+ self._test = test
+ self.localsid_addr = localsid_addr
+ # keep binary format in _localsid_addr
+ self._localsid_addr = inet_pton(AF_INET6, self.localsid_addr)
+ self.behavior = behavior
+ self.nh_addr = nh_addr
+ # keep binary format in _nh_addr
+ if ':' in nh_addr:
+ # IPv6
+ self._nh_addr = inet_pton(AF_INET6, nh_addr)
+ else:
+ # IPv4
+ # API expects 16 octets (128 bits)
+ # last 4 octets are used for IPv4
+ # --> prepend 12 octets
+ self._nh_addr = ('\x00' * 12) + inet_pton(AF_INET, nh_addr)
+ self.end_psp = end_psp
+ self.sw_if_index = sw_if_index
+ self.vlan_index = vlan_index
+ self.fib_table = fib_table
+ self._configured = False
+
+ def add_vpp_config(self):
+ self._test.vapi.sr_localsid_add_del(
+ self._localsid_addr,
+ self.behavior,
+ self._nh_addr,
+ is_del=0,
+ end_psp=self.end_psp,
+ sw_if_index=self.sw_if_index,
+ vlan_index=self.vlan_index,
+ fib_table=self.fib_table)
+ self._configured = True
+
+ def remove_vpp_config(self):
+ self._test.vapi.sr_localsid_add_del(
+ self._localsid_addr,
+ self.behavior,
+ self._nh_addr,
+ is_del=1,
+ end_psp=self.end_psp,
+ sw_if_index=self.sw_if_index,
+ vlan_index=self.vlan_index,
+ fib_table=self.fib_table)
+ self._configured = False
+
+ def query_vpp_config(self):
+ # sr_localsids_dump API is disabled
+ # use _configured flag for now
+ return self._configured
+
+ def __str__(self):
+ return self.object_id()
+
+ def object_id(self):
+ return ("%d;%s,%d"
+ % (self.fib_table,
+ self.localsid_addr,
+ self.behavior))
+
+
+class VppSRv6Policy(VppObject):
+ """
+ SRv6 Policy
+ """
+
+ def __init__(self, test, bsid,
+ is_encap, sr_type, weight, fib_table,
+ segments, source):
+ self._test = test
+ self.bsid = bsid
+ # keep binary format in _bsid
+ self._bsid = inet_pton(AF_INET6, bsid)
+ self.is_encap = is_encap
+ self.sr_type = sr_type
+ self.weight = weight
+ self.fib_table = fib_table
+ self.segments = segments
+ # keep binary format in _segments
+ self._segments = []
+ for seg in segments:
+ self._segments.extend(inet_pton(AF_INET6, seg))
+ self.n_segments = len(segments)
+ # source not passed to API
+ # self.source = inet_pton(AF_INET6, source)
+ self.source = source
+ self._configured = False
+
+ def add_vpp_config(self):
+ self._test.vapi.sr_policy_add(
+ self._bsid,
+ self.weight,
+ self.is_encap,
+ self.sr_type,
+ self.fib_table,
+ self.n_segments,
+ self._segments)
+ self._configured = True
+
+ def remove_vpp_config(self):
+ self._test.vapi.sr_policy_del(
+ self._bsid)
+ self._configured = False
+
+ def query_vpp_config(self):
+ # no API to query SR Policies
+ # use _configured flag for now
+ return self._configured
+
+ def __str__(self):
+ return self.object_id()
+
+ def object_id(self):
+ return ("%d;%s-><%s>;%d"
+ % (self.sr_type,
+ self.bsid,
+ ','.join(self.segments),
+ self.is_encap))
+
+
+class VppSRv6Steering(VppObject):
+ """
+ SRv6 Steering
+ """
+
+ def __init__(self, test,
+ bsid,
+ prefix,
+ mask_width,
+ traffic_type,
+ sr_policy_index,
+ table_id,
+ sw_if_index):
+ self._test = test
+ self.bsid = bsid
+ # keep binary format in _bsid
+ self._bsid = inet_pton(AF_INET6, bsid)
+ self.prefix = prefix
+ # keep binary format in _prefix
+ if ':' in prefix:
+ # IPv6
+ self._prefix = inet_pton(AF_INET6, prefix)
+ else:
+ # IPv4
+ # API expects 16 octets (128 bits)
+ # last 4 octets are used for IPv4
+ # --> prepend 12 octets
+ self._prefix = ('\x00' * 12) + inet_pton(AF_INET, prefix)
+ self.mask_width = mask_width
+ self.traffic_type = traffic_type
+ self.sr_policy_index = sr_policy_index
+ self.sw_if_index = sw_if_index
+ self.table_id = table_id
+ self._configured = False
+
+ def add_vpp_config(self):
+ self._test.vapi.sr_steering_add_del(
+ 0,
+ self._bsid,
+ self.sr_policy_index,
+ self.table_id,
+ self._prefix,
+ self.mask_width,
+ self.sw_if_index,
+ self.traffic_type)
+ self._configured = True
+
+ def remove_vpp_config(self):
+ self._test.vapi.sr_steering_add_del(
+ 1,
+ self._bsid,
+ self.sr_policy_index,
+ self.table_id,
+ self._prefix,
+ self.mask_width,
+ self.sw_if_index,
+ self.traffic_type)
+ self._configured = False
+
+ def query_vpp_config(self):
+ # no API to query steering entries
+ # use _configured flag for now
+ return self._configured
+
+ def __str__(self):
+ return self.object_id()
+
+ def object_id(self):
+ return ("%d;%d;%s/%d->%s"
+ % (self.table_id,
+ self.traffic_type,
+ self.prefix,
+ self.mask_width,
+ self.bsid))
diff --git a/test/vpp_sub_interface.py b/test/vpp_sub_interface.py
new file mode 100644
index 00000000..cabee88d
--- /dev/null
+++ b/test/vpp_sub_interface.py
@@ -0,0 +1,213 @@
+from scapy.layers.l2 import Dot1Q
+from abc import abstractmethod, ABCMeta
+from vpp_interface import VppInterface
+from vpp_pg_interface import VppPGInterface
+from vpp_papi_provider import L2_VTR_OP
+
+
+class VppSubInterface(VppPGInterface):
+ __metaclass__ = ABCMeta
+
+ @property
+ def parent(self):
+ """Parent interface for this sub-interface"""
+ return self._parent
+
+ @property
+ def sub_id(self):
+ """Sub-interface ID"""
+ return self._sub_id
+
+ @property
+ def tag1(self):
+ return self._tag1
+
+ @property
+ def tag2(self):
+ return self._tag2
+
+ @property
+ def vtr(self):
+ return self._vtr
+
+ def __init__(self, test, parent, sub_id):
+ VppInterface.__init__(self, test)
+ self._parent = parent
+ self._parent.add_sub_if(self)
+ self._sub_id = sub_id
+ self.set_vtr(L2_VTR_OP.L2_DISABLED)
+ self.DOT1AD_TYPE = 0x88A8
+ self.DOT1Q_TYPE = 0x8100
+
+ @abstractmethod
+ def create_arp_req(self):
+ pass
+
+ @abstractmethod
+ def create_ndp_req(self):
+ pass
+
+ def resolve_arp(self):
+ super(VppSubInterface, self).resolve_arp(self.parent)
+
+ def resolve_ndp(self):
+ super(VppSubInterface, self).resolve_ndp(self.parent)
+
+ @abstractmethod
+ def add_dot1_layer(self, pkt):
+ pass
+
+ def remove_vpp_config(self):
+ self.test.vapi.delete_subif(self._sw_if_index)
+
+ def _add_tag(self, packet, vlan, tag_type):
+ payload = packet.payload
+ inner_type = packet.type
+ packet.remove_payload()
+ packet.add_payload(Dot1Q(vlan=vlan) / payload)
+ packet.payload.type = inner_type
+ packet.payload.vlan = vlan
+ packet.type = tag_type
+ return packet
+
+ def _remove_tag(self, packet, vlan=None, tag_type=None):
+ if tag_type:
+ self.test.instance().assertEqual(packet.type, tag_type)
+
+ payload = packet.payload
+ if vlan:
+ self.test.instance().assertEqual(payload.vlan, vlan)
+ inner_type = payload.type
+ payload = payload.payload
+ packet.remove_payload()
+ packet.add_payload(payload)
+ packet.type = inner_type
+ return packet
+
+ def add_dot1q_layer(self, packet, vlan):
+ return self._add_tag(packet, vlan, self.DOT1Q_TYPE)
+
+ def add_dot1ad_layer(self, packet, outer, inner):
+ p = self._add_tag(packet, inner, self.DOT1Q_TYPE)
+ return self._add_tag(p, outer, self.DOT1AD_TYPE)
+
+ def remove_dot1q_layer(self, packet, vlan=None):
+ return self._remove_tag(packet, vlan, self.DOT1Q_TYPE)
+
+ def remove_dot1ad_layer(self, packet, outer=None, inner=None):
+ p = self._remove_tag(packet, outer, self.DOT1AD_TYPE)
+ return self._remove_tag(p, inner, self.DOT1Q_TYPE)
+
+ def set_vtr(self, vtr, push1q=0, tag=None, inner=None, outer=None):
+ self._tag1 = 0
+ self._tag2 = 0
+ self._push1q = 0
+
+ if (vtr == L2_VTR_OP.L2_PUSH_1 or
+ vtr == L2_VTR_OP.L2_TRANSLATE_1_1 or
+ vtr == L2_VTR_OP.L2_TRANSLATE_2_1):
+ self._tag1 = tag
+ self._push1q = push1q
+ if (vtr == L2_VTR_OP.L2_PUSH_2 or
+ vtr == L2_VTR_OP.L2_TRANSLATE_1_2 or
+ vtr == L2_VTR_OP.L2_TRANSLATE_2_2):
+ self._tag1 = outer
+ self._tag2 = inner
+ self._push1q = push1q
+
+ self.test.vapi.sw_interface_set_l2_tag_rewrite(
+ self.sw_if_index, vtr, push=self._push1q,
+ tag1=self._tag1, tag2=self._tag2)
+ self._vtr = vtr
+
+
+class VppDot1QSubint(VppSubInterface):
+
+ @property
+ def vlan(self):
+ """VLAN tag"""
+ return self._vlan
+
+ def __init__(self, test, parent, sub_id, vlan=None):
+ if vlan is None:
+ vlan = sub_id
+ self._vlan = vlan
+ r = test.vapi.create_vlan_subif(parent.sw_if_index, vlan)
+ self._sw_if_index = r.sw_if_index
+ super(VppDot1QSubint, self).__init__(test, parent, sub_id)
+
+ def create_arp_req(self):
+ packet = VppPGInterface.create_arp_req(self)
+ return self.add_dot1_layer(packet)
+
+ def create_ndp_req(self):
+ packet = VppPGInterface.create_ndp_req(self)
+ return self.add_dot1_layer(packet)
+
+ # called before sending packet
+ def add_dot1_layer(self, packet):
+ return self.add_dot1q_layer(packet, self.vlan)
+
+ # called on received packet to "reverse" the add call
+ def remove_dot1_layer(self, packet):
+ return self.remove_dot1q_layer(packet, self.vlan)
+
+
+class VppDot1ADSubint(VppSubInterface):
+
+ @property
+ def outer_vlan(self):
+ """Outer VLAN tag"""
+ return self._outer_vlan
+
+ @property
+ def inner_vlan(self):
+ """Inner VLAN tag"""
+ return self._inner_vlan
+
+ def __init__(self, test, parent, sub_id, outer_vlan, inner_vlan):
+ r = test.vapi.create_subif(parent.sw_if_index, sub_id, outer_vlan,
+ inner_vlan, dot1ad=1, two_tags=1,
+ exact_match=1)
+ self._sw_if_index = r.sw_if_index
+ self._outer_vlan = outer_vlan
+ self._inner_vlan = inner_vlan
+ super(VppDot1ADSubint, self).__init__(test, parent, sub_id)
+
+ def create_arp_req(self):
+ packet = VppPGInterface.create_arp_req(self)
+ return self.add_dot1_layer(packet)
+
+ def create_ndp_req(self):
+ packet = VppPGInterface.create_ndp_req(self)
+ return self.add_dot1_layer(packet)
+
+ def add_dot1_layer(self, packet):
+ return self.add_dot1ad_layer(packet, self.outer_vlan, self.inner_vlan)
+
+ def remove_dot1_layer(self, packet):
+ return self.remove_dot1ad_layer(packet, self.outer_vlan,
+ self.inner_vlan)
+
+
+class VppP2PSubint(VppSubInterface):
+
+ def __init__(self, test, parent, sub_id, remote_mac):
+ r = test.vapi.create_p2pethernet_subif(parent.sw_if_index,
+ remote_mac, sub_id)
+ self._sw_if_index = r.sw_if_index
+ super(VppP2PSubint, self).__init__(test, parent, sub_id)
+
+ def add_dot1_layer(self, packet):
+ return packet
+
+ def remove_dot1_layer(self, packet):
+ return packet
+
+ def create_arp_req(self):
+ packet = VppPGInterface.create_arp_req(self)
+ return packet
+
+ def create_ndp_req(self):
+ packet = VppPGInterface.create_ndp_req(self)
+ return packet