Repository: sosy-lab/benchexec (language: python)

Issue:
Make tables better usable
Many features in the HTML tables created by table-generator are quite hidden.

There should be a bar at the top of the table (preferably one that does not scroll) with entries for the features "select columns", "filter rows", "quantile plot", and "scatter plot".
Without JavaScript, the bar should be hidden, and it should be printed.

In order to use quantile plots from the table, the quantile-plot window should get a drop-down box for selecting one of the available columns, like the scatter-plot window has.



Discussion / hints:

Fix patch:
diff --git a/contrib/p4-benchmark.py b/contrib/p4-benchmark.py
new file mode 100755
index 000000000..5bfef2e3a
--- /dev/null
+++ b/contrib/p4-benchmark.py
@@ -0,0 +1,65 @@
+# SPDX-FileCopyrightText: 2020-2021 CASTOR Software Research Centre
+# <https://www.castor.kth.se/>
+# SPDX-FileCopyrightText: 2020-2021 Johan Paulsson
+
+# SPDX-License-Identifier: Apache-2.0
+
+# For instructions on how to use this program, se README in /p4.
+
+import sys
+import logging
+import os
+import benchexec.util as util
+
+from benchexec.benchexec import BenchExecException
+from benchexec.benchexec import BenchExec
+
+
+class P4BenchExec(BenchExec):
+    """
+    Extension of the basic BenchExec. It overrides the executor and
+    changes it to the p4execution executor to examine p4 programs.
+    """
+
+    def __init__(self):
+        BenchExec.__init__(self)
+
+    def load_executor(self):
+        from p4.p4execution import P4Execution
+
+        return P4Execution()
+
+
+def main(benchexec=None, argv=None):
+    """
+    The main method of BenchExec for use in a command-line script.
+    In addition to calling benchexec.start(argv),
+    it also handles signals and keyboard interrupts.
+    It does not return but calls sys.exit().
+    @param benchexec: An instance of BenchExec for executing benchmarks.
+    @param argv: optionally the list of command-line options to use
+    """
+    if sys.version_info < (3,):
+        sys.exit("benchexec needs Python 3 to run.")
+
+    if os.getuid() != 0:
+        sys.exit("p4-benchmark needs root access to run")
+    try:
+        if not benchexec:
+            benchexec = P4BenchExec()
+
+        def signal_stop(signum, frame):
+            logging.debug("Received signal %d, terminating.", signum)
+            benchexec.stop()
+
+        # Handle termination-request signals that are available on the current platform
+        for signal_name in ["SIGINT", "SIGQUIT", "SIGTERM", "SIGBREAK"]:
+            util.try_set_signal_handler(signal_name, signal_stop)
+
+        sys.exit(benchexec.start(argv or sys.argv))
+    except BenchExecException as e:
+        sys.exit("Error: " + str(e))
+
+
+if __name__ == "__main__":
+    main()
diff --git a/contrib/p4/README.md b/contrib/p4/README.md
new file mode 100644
index 000000000..9e1d9be7a
--- /dev/null
+++ b/contrib/p4/README.md
@@ -0,0 +1,101 @@
+<!-- PDX-FileCopyrightText: 2020-2021 CASTOR Software Research Centre
+<https://www.castor.kth.se/>
+SPDX-FileCopyrightText: 2020-2021 Johan Paulsson
+
+SPDX-License-Identifier: Apache-2.0 -->
+
+# P4 Extension for Benchexec
+This extension allows Benchexec to be used to analyze P4 programs for programmable switches. The user can alter the way the test should
+be executed by providing different input files to define the test.
+
+## Installation
+
+### Dependencies
+#### Docker Engine
+To install docker, follow the instruction on [dockers website](https://docs.docker.com/engine/install/)
+
+On ubuntu, one can execute these commands to install the latest version of docker.
+```
+sudo apt-get update
+sudo apt-get install \
+    apt-transport-https \
+    ca-certificates \
+    curl \
+    gnupg \
+    lsb-release
+curl -fsSL https://download.docker.com/linux/ubuntu/gpg | sudo gpg --dearmor -o /usr/share/keyrings/docker-archive-keyring.gpg
+echo \
+  "deb [arch=amd64 signed-by=/usr/share/keyrings/docker-archive-keyring.gpg] https://download.docker.com/linux/ubuntu \
+  $(lsb_release -cs) stable" | sudo tee /etc/apt/sources.list.d/docker.list > /dev/null
+sudo apt-get update
+sudo apt-get install docker-ce docker-ce-cli containerd.io
+```
+
+Also install the required python modules,
+
+```
+sudo pip3 install docker
+sudo pip3 install pyroute2
+```
+
+### Benchexec installation
+
+To install benchexecute with p4 extensions follow these steps. First clone the repository and then install with pip. 
+
+```
+git clone https://github.com/thaprau/benchexec.git
+sudo pip3 install .
+```
+
+### Dockerfile setup
+To set up the required docker files, some images have to be created locally. In total, three images have to be built.
+1. Node image - basic image which acts as a node in the network
+2. Switch image - Image that runs the bmv2 software provided by the p4 institution
+3. Ptf tester image - Image that runs PTF(package test framework) also provided by p4 institution
+
+To build the images, run the following commands:
+
+```
+cd contrib/p4/docker_files
+sudo docker build -t basic_node basic_node
+sudo docker build -t switch_bmv2 switch_bmv2
+sudo docker build -t ptf_tester ptf_tester
+```
+
+## Setup
+
+To run benchexec with p4, there are a few requirements. The first one is to create a benchmark file. The structure of the benchmark file is the same as the regular benchexec, but with 3 extra option tags. The tags are:
+
+1.    Path to ptf test folder. Format: <option name="switch_folder">path/to/switch_folder</option>
+2.    Path to switch folder. Format <option name="ptf_test_folder">path/to/ptf_test_folder</option>
+3.    Path to network configuration file <option name="network_config">path/tp/network_config_file</option>
+
+### Switch folder layout
+The switch folder `contrib/p4/docker_files/switch_bmv2` contains 3 folders, log, P4 and tables.
+
+1. Log - used by the program
+2. P4 - This is where the switch expect to find the setup file
+3. tables - This is where the switch will look for the defined table entries
+
+### Writing test for the switch
+The program utilizes PTF to create and handle tests. To write your own test, one can look the examples in this repository(`contrib/p4/docker_files/ptf_tester/tests`) or the [PTF documentation](https://github.com/p4lang/ptf)
+
+Finally, the user can define expected results for the test run. If the task is run with the include tag (can be replaced with the withoutfile tag), the user can include an expected results json file. An example of such a file is in `/doc/task_files_expected_results.json`. The format of the file is Modulename.testname. If no modules are defined, the module name is the file name. The test name is the name of the test. For more info, look at [PTF documentation](https://github.com/p4lang/ptf).
+
+A complete example of the xml file is in `contrib/p4/test_unput/simpleP4Test.XML`. The given example uses a pre-compiled json file for the switch. (Replace with the p4 file to be executed)The switch is a simple ipv4 forward switch from the p4 tutorial. The file also refers to a network configuration file which sets up the configuration as the picture below. Finally, it refers to some simple ipv4 forwarding test.
+
+
+### Network configuration file
+
+The network configuration file is what defines the test setup. To create such a file, the easiest way is to use the python API. On how to use the API, see the README of that [repository](https://github.com/thaprau/p4_bench_api).
+
+## Execution
+
+To run the program, execute the p4-benchmark.py file located in `/contrib`. An example would be:
+
+```
+sudo python3 contrib/p4-benchmark.py contrib/p4/test_input/simpleP4Test.XML
+```
+
+
+
diff --git a/contrib/p4/__init__.py b/contrib/p4/__init__.py
new file mode 100644
index 000000000..42d614ae5
--- /dev/null
+++ b/contrib/p4/__init__.py
@@ -0,0 +1,9 @@
+# SPDX-FileCopyrightText: 2020-2021 CASTOR Software Research Centre
+# <https://www.castor.kth.se/>
+# SPDX-FileCopyrightText: 2020-2021 Johan Paulsson
+
+# SPDX-License-Identifier: Apache-2.0
+
+"""
+Library for all python related p4 helper classes
+"""
diff --git a/contrib/p4/counter.py b/contrib/p4/counter.py
new file mode 100644
index 000000000..d142fb78e
--- /dev/null
+++ b/contrib/p4/counter.py
@@ -0,0 +1,21 @@
+# SPDX-FileCopyrightText: 2020-2021 CASTOR Software Research Centre
+# <https://www.castor.kth.se/>
+# SPDX-FileCopyrightText: 2020-2021 Johan Paulsson
+
+# SPDX-License-Identifier: Apache-2.0
+
+import threading
+
+"""
+Simple counter for threads to increase
+"""
+
+
+class Counter(object):
+    def __init__(self, start=0):
+        self.lock = threading.Lock()
+        self.value = start
+
+    def increment(self):
+        with self.lock:
+            self.value += 1
diff --git a/contrib/p4/docker_files/basic_node/Dockerfile b/contrib/p4/docker_files/basic_node/Dockerfile
new file mode 100644
index 000000000..0fff23c75
--- /dev/null
+++ b/contrib/p4/docker_files/basic_node/Dockerfile
@@ -0,0 +1,30 @@
+# SPDX-FileCopyrightText: 2020-2021 CASTOR Software Research Centre
+# <https://www.castor.kth.se/>
+# SPDX-FileCopyrightText: 2020-2021 Johan Paulsson
+
+# SPDX-License-Identifier: Apache-2.0
+
+#This dockerfile represents a single node/client in a network.
+#It uses the example from https://github.com/p4lang/ptf/ptf_nn
+#to listen and forward packages
+
+FROM ubuntu:18.04
+
+# Packages
+RUN apt-get update
+RUN apt-get install -y sudo
+RUN apt-get install -y iproute2
+RUN apt-get install -y net-tools
+RUN apt-get install -y iputils-ping
+RUN apt-get install -y tcpdump
+RUN apt-get install -y git
+RUN apt-get install -y libnanomsg-dev
+RUN apt-get install -y python3
+RUN apt-get install -y python3-pip
+RUN pip3 install rpyc
+RUN pip3 install netifaces
+RUN pip3 install nnpy
+RUN git clone https://github.com/p4lang/ptf /usr/local/src/ptf
+RUN cd /usr/local/src/ptf && pip3 install .
+
+CMD tail -f /dev/null
diff --git a/contrib/p4/docker_files/switch_bmv2/Dockerfile b/contrib/p4/docker_files/switch_bmv2/Dockerfile
new file mode 100644
index 000000000..ce950caa8
--- /dev/null
+++ b/contrib/p4/docker_files/switch_bmv2/Dockerfile
@@ -0,0 +1,57 @@
+# SPDX-FileCopyrightText: 2020-2021 CASTOR Software Research Centre
+# <https://www.castor.kth.se/>
+# SPDX-FileCopyrightText: 2020-2021 Johan Paulsson
+
+# SPDX-License-Identifier: Apache-2.0
+
+#Container for using bmv2 open source switch. See ref https://github.com/p4lang/behavioral-model
+#It is capable of loading a compiled bmv2 json file
+
+FROM ubuntu:16.04
+
+RUN apt-get update && apt-get install -y \
+    automake \
+    cmake \
+    libjudy-dev \
+    libgmp-dev \
+    libpcap-dev \
+    libboost-dev \
+    libboost-test-dev \
+    libboost-program-options-dev \
+    libboost-system-dev \
+    libboost-filesystem-dev \
+    libboost-thread-dev \
+    libevent-dev \
+    libtool \
+    flex \
+    bison \
+    pkg-config \
+    g++ \
+    libssl-dev \
+    libffi-dev \
+    python3-dev \
+    python3-pip \
+    wget \
+    git \
+    sudo \
+    && rm -rf /var/lib/apt/list/*
+
+RUN git clone https://github.com/p4lang/behavioral-model.git /usr/local/behavioral-model
+RUN cd /usr/local/behavioral-model/travis && sudo chmod +x install-thrift.sh install-nanomsg.sh install-nnpy.sh
+
+
+#Instead of install_deps.sh
+RUN mkdir tempdir
+RUN cd tempdir && sudo /usr/local/behavioral-model/travis/install-thrift.sh
+RUN cd tempdir && /usr/local/behavioral-model/travis/install-nanomsg.sh
+RUN cd tempdir && /usr/local/behavioral-model/travis/install-nnpy.sh
+
+RUN rm -rf tempdir
+
+RUN cd /usr/local/behavioral-model/ && ./autogen.sh
+RUN /usr/local/behavioral-model/configure
+RUN make
+RUN make install
+RUN cd /usr/local/behavioral-model/ && ldconfig
+
+CMD tail -f /dev/null
diff --git a/contrib/p4/docker_files/switch_bmv2/P4/simple_switch.json b/contrib/p4/docker_files/switch_bmv2/P4/simple_switch.json
new file mode 100644
index 000000000..fe2eb1d1a
--- /dev/null
+++ b/contrib/p4/docker_files/switch_bmv2/P4/simple_switch.json
@@ -0,0 +1,565 @@
+{
+  "header_types" : [
+    {
+      "name" : "scalars_0",
+      "id" : 0,
+      "fields" : []
+    },
+    {
+      "name" : "standard_metadata",
+      "id" : 1,
+      "fields" : [
+        ["ingress_port", 9, false],
+        ["egress_spec", 9, false],
+        ["egress_port", 9, false],
+        ["clone_spec", 32, false],
+        ["instance_type", 32, false],
+        ["drop", 1, false],
+        ["recirculate_port", 16, false],
+        ["packet_length", 32, false],
+        ["enq_timestamp", 32, false],
+        ["enq_qdepth", 19, false],
+        ["deq_timedelta", 32, false],
+        ["deq_qdepth", 19, false],
+        ["ingress_global_timestamp", 48, false],
+        ["egress_global_timestamp", 48, false],
+        ["lf_field_list", 32, false],
+        ["mcast_grp", 16, false],
+        ["resubmit_flag", 32, false],
+        ["egress_rid", 16, false],
+        ["checksum_error", 1, false],
+        ["recirculate_flag", 32, false],
+        ["parser_error", 32, false],
+        ["_padding", 5, false]
+      ]
+    },
+    {
+      "name" : "ethernet_t",
+      "id" : 2,
+      "fields" : [
+        ["dstAddr", 48, false],
+        ["srcAddr", 48, false],
+        ["etherType", 16, false]
+      ]
+    },
+    {
+      "name" : "ipv4_t",
+      "id" : 3,
+      "fields" : [
+        ["version", 4, false],
+        ["ihl", 4, false],
+        ["diffserv", 8, false],
+        ["totalLen", 16, false],
+        ["identification", 16, false],
+        ["flags", 3, false],
+        ["fragOffset", 13, false],
+        ["ttl", 8, false],
+        ["protocol", 8, false],
+        ["hdrChecksum", 16, false],
+        ["srcAddr", 32, false],
+        ["dstAddr", 32, false]
+      ]
+    }
+  ],
+  "headers" : [
+    {
+      "name" : "scalars",
+      "id" : 0,
+      "header_type" : "scalars_0",
+      "metadata" : true,
+      "pi_omit" : true
+    },
+    {
+      "name" : "standard_metadata",
+      "id" : 1,
+      "header_type" : "standard_metadata",
+      "metadata" : true,
+      "pi_omit" : true
+    },
+    {
+      "name" : "ethernet",
+      "id" : 2,
+      "header_type" : "ethernet_t",
+      "metadata" : false,
+      "pi_omit" : true
+    },
+    {
+      "name" : "ipv4",
+      "id" : 3,
+      "header_type" : "ipv4_t",
+      "metadata" : false,
+      "pi_omit" : true
+    }
+  ],
+  "header_stacks" : [],
+  "header_union_types" : [],
+  "header_unions" : [],
+  "header_union_stacks" : [],
+  "field_lists" : [],
+  "errors" : [
+    ["NoError", 0],
+    ["PacketTooShort", 1],
+    ["NoMatch", 2],
+    ["StackOutOfBounds", 3],
+    ["HeaderTooShort", 4],
+    ["ParserTimeout", 5]
+  ],
+  "enums" : [],
+  "parsers" : [
+    {
+      "name" : "parser",
+      "id" : 0,
+      "init_state" : "start",
+      "parse_states" : [
+        {
+          "name" : "start",
+          "id" : 0,
+          "parser_ops" : [
+            {
+              "parameters" : [
+                {
+                  "type" : "regular",
+                  "value" : "ethernet"
+                }
+              ],
+              "op" : "extract"
+            }
+          ],
+          "transitions" : [
+            {
+              "type" : "hexstr",
+              "value" : "0x0800",
+              "mask" : null,
+              "next_state" : "parse_ipv4"
+            },
+            {
+              "value" : "default",
+              "mask" : null,
+              "next_state" : null
+            }
+          ],
+          "transition_key" : [
+            {
+              "type" : "field",
+              "value" : ["ethernet", "etherType"]
+            }
+          ]
+        },
+        {
+          "name" : "parse_ipv4",
+          "id" : 1,
+          "parser_ops" : [
+            {
+              "parameters" : [
+                {
+                  "type" : "regular",
+                  "value" : "ipv4"
+                }
+              ],
+              "op" : "extract"
+            }
+          ],
+          "transitions" : [
+            {
+              "value" : "default",
+              "mask" : null,
+              "next_state" : null
+            }
+          ],
+          "transition_key" : []
+        }
+      ]
+    }
+  ],
+  "parse_vsets" : [],
+  "deparsers" : [
+    {
+      "name" : "deparser",
+      "id" : 0,
+      "source_info" : {
+        "filename" : "simple_switch.p4",
+        "line" : 158,
+        "column" : 8,
+        "source_fragment" : "MyDeparser"
+      },
+      "order" : ["ethernet", "ipv4"]
+    }
+  ],
+  "meter_arrays" : [],
+  "counter_arrays" : [],
+  "register_arrays" : [],
+  "calculations" : [
+    {
+      "name" : "calc",
+      "id" : 0,
+      "source_info" : {
+        "filename" : "simple_switch.p4",
+        "line" : 136,
+        "column" : 1,
+        "source_fragment" : "update_checksum( ..."
+      },
+      "algo" : "csum16",
+      "input" : [
+        {
+          "type" : "field",
+          "value" : ["ipv4", "version"]
+        },
+        {
+          "type" : "field",
+          "value" : ["ipv4", "ihl"]
+        },
+        {
+          "type" : "field",
+          "value" : ["ipv4", "diffserv"]
+        },
+        {
+          "type" : "field",
+          "value" : ["ipv4", "totalLen"]
+        },
+        {
+          "type" : "field",
+          "value" : ["ipv4", "identification"]
+        },
+        {
+          "type" : "field",
+          "value" : ["ipv4", "flags"]
+        },
+        {
+          "type" : "field",
+          "value" : ["ipv4", "fragOffset"]
+        },
+        {
+          "type" : "field",
+          "value" : ["ipv4", "ttl"]
+        },
+        {
+          "type" : "field",
+          "value" : ["ipv4", "protocol"]
+        },
+        {
+          "type" : "field",
+          "value" : ["ipv4", "srcAddr"]
+        },
+        {
+          "type" : "field",
+          "value" : ["ipv4", "dstAddr"]
+        }
+      ]
+    }
+  ],
+  "learn_lists" : [],
+  "actions" : [
+    {
+      "name" : "NoAction",
+      "id" : 0,
+      "runtime_data" : [],
+      "primitives" : []
+    },
+    {
+      "name" : "MyIngress.drop",
+      "id" : 1,
+      "runtime_data" : [],
+      "primitives" : [
+        {
+          "op" : "drop",
+          "parameters" : [],
+          "source_info" : {
+            "filename" : "simple_switch.p4",
+            "line" : 90,
+            "column" : 8,
+            "source_fragment" : "mark_to_drop()"
+          }
+        }
+      ]
+    },
+    {
+      "name" : "MyIngress.ipv4_forward",
+      "id" : 2,
+      "runtime_data" : [
+        {
+          "name" : "dstAddr",
+          "bitwidth" : 48
+        },
+        {
+          "name" : "port",
+          "bitwidth" : 9
+        }
+      ],
+      "primitives" : [
+        {
+          "op" : "assign",
+          "parameters" : [
+            {
+              "type" : "field",
+              "value" : ["standard_metadata", "egress_spec"]
+            },
+            {
+              "type" : "runtime_data",
+              "value" : 1
+            }
+          ],
+          "source_info" : {
+            "filename" : "simple_switch.p4",
+            "line" : 94,
+            "column" : 8,
+            "source_fragment" : "standard_metadata.egress_spec = port"
+          }
+        },
+        {
+          "op" : "assign",
+          "parameters" : [
+            {
+              "type" : "field",
+              "value" : ["ethernet", "srcAddr"]
+            },
+            {
+              "type" : "field",
+              "value" : ["ethernet", "dstAddr"]
+            }
+          ],
+          "source_info" : {
+            "filename" : "simple_switch.p4",
+            "line" : 95,
+            "column" : 8,
+            "source_fragment" : "hdr.ethernet.srcAddr = hdr.ethernet.dstAddr"
+          }
+        },
+        {
+          "op" : "assign",
+          "parameters" : [
+            {
+              "type" : "field",
+              "value" : ["ethernet", "dstAddr"]
+            },
+            {
+              "type" : "runtime_data",
+              "value" : 0
+            }
+          ],
+          "source_info" : {
+            "filename" : "simple_switch.p4",
+            "line" : 96,
+            "column" : 8,
+            "source_fragment" : "hdr.ethernet.dstAddr = dstAddr"
+          }
+        },
+        {
+          "op" : "assign",
+          "parameters" : [
+            {
+              "type" : "field",
+              "value" : ["ipv4", "ttl"]
+            },
+            {
+              "type" : "expression",
+              "value" : {
+                "type" : "expression",
+                "value" : {
+                  "op" : "&",
+                  "left" : {
+                    "type" : "expression",
+                    "value" : {
+                      "op" : "+",
+                      "left" : {
+                        "type" : "field",
+                        "value" : ["ipv4", "ttl"]
+                      },
+                      "right" : {
+                        "type" : "hexstr",
+                        "value" : "0xff"
+                      }
+                    }
+                  },
+                  "right" : {
+                    "type" : "hexstr",
+                    "value" : "0xff"
+                  }
+                }
+              }
+            }
+          ],
+          "source_info" : {
+            "filename" : "simple_switch.p4",
+            "line" : 97,
+            "column" : 8,
+            "source_fragment" : "hdr.ipv4.ttl = hdr.ipv4.ttl - 1"
+          }
+        }
+      ]
+    }
+  ],
+  "pipelines" : [
+    {
+      "name" : "ingress",
+      "id" : 0,
+      "source_info" : {
+        "filename" : "simple_switch.p4",
+        "line" : 86,
+        "column" : 8,
+        "source_fragment" : "MyIngress"
+      },
+      "init_table" : "node_2",
+      "tables" : [
+        {
+          "name" : "MyIngress.ipv4_lpm",
+          "id" : 0,
+          "source_info" : {
+            "filename" : "simple_switch.p4",
+            "line" : 100,
+            "column" : 10,
+            "source_fragment" : "ipv4_lpm"
+          },
+          "key" : [
+            {
+              "match_type" : "lpm",
+              "name" : "hdr.ipv4.dstAddr",
+              "target" : ["ipv4", "dstAddr"],
+              "mask" : null
+            }
+          ],
+          "match_type" : "lpm",
+          "type" : "simple",
+          "max_size" : 1024,
+          "with_counters" : false,
+          "support_timeout" : false,
+          "direct_meters" : null,
+          "action_ids" : [2, 1, 0],
+          "actions" : ["MyIngress.ipv4_forward", "MyIngress.drop", "NoAction"],
+          "base_default_next" : null,
+          "next_tables" : {
+            "MyIngress.ipv4_forward" : null,
+            "MyIngress.drop" : null,
+            "NoAction" : null
+          },
+          "default_entry" : {
+            "action_id" : 1,
+            "action_const" : false,
+            "action_data" : [],
+            "action_entry_const" : false
+          }
+        }
+      ],
+      "action_profiles" : [],
+      "conditionals" : [
+        {
+          "name" : "node_2",
+          "id" : 0,
+          "source_info" : {
+            "filename" : "simple_switch.p4",
+            "line" : 114,
+            "column" : 12,
+            "source_fragment" : "hdr.ipv4.isValid()"
+          },
+          "expression" : {
+            "type" : "expression",
+            "value" : {
+              "op" : "d2b",
+              "left" : null,
+              "right" : {
+                "type" : "field",
+                "value" : ["ipv4", "$valid$"]
+              }
+            }
+          },
+          "false_next" : null,
+          "true_next" : "MyIngress.ipv4_lpm"
+        }
+      ]
+    },
+    {
+      "name" : "egress",
+      "id" : 1,
+      "source_info" : {
+        "filename" : "simple_switch.p4",
+        "line" : 124,
+        "column" : 8,
+        "source_fragment" : "MyEgress"
+      },
+      "init_table" : null,
+      "tables" : [],
+      "action_profiles" : [],
+      "conditionals" : []
+    }
+  ],
+  "checksums" : [
+    {
+      "name" : "cksum",
+      "id" : 0,
+      "source_info" : {
+        "filename" : "simple_switch.p4",
+        "line" : 136,
+        "column" : 1,
+        "source_fragment" : "update_checksum( ..."
+      },
+      "target" : ["ipv4", "hdrChecksum"],
+      "type" : "generic",
+      "calculation" : "calc",
+      "verify" : false,
+      "update" : true,
+      "if_cond" : {
+        "type" : "expression",
+        "value" : {
+          "op" : "d2b",
+          "left" : null,
+          "right" : {
+            "type" : "field",
+            "value" : ["ipv4", "$valid$"]
+          }
+        }
+      }
+    }
+  ],
+  "force_arith" : [],
+  "extern_instances" : [],
+  "field_aliases" : [
+    [
+      "queueing_metadata.enq_timestamp",
+      ["standard_metadata", "enq_timestamp"]
+    ],
+    [
+      "queueing_metadata.enq_qdepth",
+      ["standard_metadata", "enq_qdepth"]
+    ],
+    [
+      "queueing_metadata.deq_timedelta",
+      ["standard_metadata", "deq_timedelta"]
+    ],
+    [
+      "queueing_metadata.deq_qdepth",
+      ["standard_metadata", "deq_qdepth"]
+    ],
+    [
+      "intrinsic_metadata.ingress_global_timestamp",
+      ["standard_metadata", "ingress_global_timestamp"]
+    ],
+    [
+      "intrinsic_metadata.egress_global_timestamp",
+      ["standard_metadata", "egress_global_timestamp"]
+    ],
+    [
+      "intrinsic_metadata.lf_field_list",
+      ["standard_metadata", "lf_field_list"]
+    ],
+    [
+      "intrinsic_metadata.mcast_grp",
+      ["standard_metadata", "mcast_grp"]
+    ],
+    [
+      "intrinsic_metadata.resubmit_flag",
+      ["standard_metadata", "resubmit_flag"]
+    ],
+    [
+      "intrinsic_metadata.egress_rid",
+      ["standard_metadata", "egress_rid"]
+    ],
+    [
+      "intrinsic_metadata.recirculate_flag",
+      ["standard_metadata", "recirculate_flag"]
+    ]
+  ],
+  "program" : "./simple_switch.p4i",
+  "__meta__" : {
+    "version" : [2, 18],
+    "compiler" : "https://github.com/p4lang/p4c"
+  }
+}
\ No newline at end of file
diff --git a/contrib/p4/docker_files/switch_bmv2/P4/simple_switch.json.license b/contrib/p4/docker_files/switch_bmv2/P4/simple_switch.json.license
new file mode 100644
index 000000000..c7317e172
--- /dev/null
+++ b/contrib/p4/docker_files/switch_bmv2/P4/simple_switch.json.license
@@ -0,0 +1,5 @@
+# SPDX-FileCopyrightText: 2020-2021 CASTOR Software Research Centre
+# <https://www.castor.kth.se/>
+# SPDX-FileCopyrightText: 2020-2021 Johan Paulsson
+
+# SPDX-License-Identifier: Apache-2.0
\ No newline at end of file
diff --git a/contrib/p4/docker_files/switch_bmv2/P4/tunnel_switch.json b/contrib/p4/docker_files/switch_bmv2/P4/tunnel_switch.json
new file mode 100644
index 000000000..5acaf35e8
--- /dev/null
+++ b/contrib/p4/docker_files/switch_bmv2/P4/tunnel_switch.json
@@ -0,0 +1,752 @@
+{
+  "header_types" : [
+    {
+      "name" : "scalars_0",
+      "id" : 0,
+      "fields" : []
+    },
+    {
+      "name" : "standard_metadata",
+      "id" : 1,
+      "fields" : [
+        ["ingress_port", 9, false],
+        ["egress_spec", 9, false],
+        ["egress_port", 9, false],
+        ["clone_spec", 32, false],
+        ["instance_type", 32, false],
+        ["drop", 1, false],
+        ["recirculate_port", 16, false],
+        ["packet_length", 32, false],
+        ["enq_timestamp", 32, false],
+        ["enq_qdepth", 19, false],
+        ["deq_timedelta", 32, false],
+        ["deq_qdepth", 19, false],
+        ["ingress_global_timestamp", 48, false],
+        ["egress_global_timestamp", 48, false],
+        ["lf_field_list", 32, false],
+        ["mcast_grp", 16, false],
+        ["resubmit_flag", 32, false],
+        ["egress_rid", 16, false],
+        ["checksum_error", 1, false],
+        ["recirculate_flag", 32, false],
+        ["parser_error", 32, false],
+        ["_padding", 5, false]
+      ]
+    },
+    {
+      "name" : "ethernet_t",
+      "id" : 2,
+      "fields" : [
+        ["dstAddr", 48, false],
+        ["srcAddr", 48, false],
+        ["etherType", 16, false]
+      ]
+    },
+    {
+      "name" : "myTunnel_t",
+      "id" : 3,
+      "fields" : [
+        ["proto_id", 16, false],
+        ["dst_id", 16, false]
+      ]
+    },
+    {
+      "name" : "ipv4_t",
+      "id" : 4,
+      "fields" : [
+        ["version", 4, false],
+        ["ihl", 4, false],
+        ["diffserv", 8, false],
+        ["totalLen", 16, false],
+        ["identification", 16, false],
+        ["flags", 3, false],
+        ["fragOffset", 13, false],
+        ["ttl", 8, false],
+        ["protocol", 8, false],
+        ["hdrChecksum", 16, false],
+        ["srcAddr", 32, false],
+        ["dstAddr", 32, false]
+      ]
+    }
+  ],
+  "headers" : [
+    {
+      "name" : "scalars",
+      "id" : 0,
+      "header_type" : "scalars_0",
+      "metadata" : true,
+      "pi_omit" : true
+    },
+    {
+      "name" : "standard_metadata",
+      "id" : 1,
+      "header_type" : "standard_metadata",
+      "metadata" : true,
+      "pi_omit" : true
+    },
+    {
+      "name" : "ethernet",
+      "id" : 2,
+      "header_type" : "ethernet_t",
+      "metadata" : false,
+      "pi_omit" : true
+    },
+    {
+      "name" : "myTunnel",
+      "id" : 3,
+      "header_type" : "myTunnel_t",
+      "metadata" : false,
+      "pi_omit" : true
+    },
+    {
+      "name" : "ipv4",
+      "id" : 4,
+      "header_type" : "ipv4_t",
+      "metadata" : false,
+      "pi_omit" : true
+    }
+  ],
+  "header_stacks" : [],
+  "header_union_types" : [],
+  "header_unions" : [],
+  "header_union_stacks" : [],
+  "field_lists" : [],
+  "errors" : [
+    ["NoError", 0],
+    ["PacketTooShort", 1],
+    ["NoMatch", 2],
+    ["StackOutOfBounds", 3],
+    ["HeaderTooShort", 4],
+    ["ParserTimeout", 5]
+  ],
+  "enums" : [],
+  "parsers" : [
+    {
+      "name" : "parser",
+      "id" : 0,
+      "init_state" : "start",
+      "parse_states" : [
+        {
+          "name" : "start",
+          "id" : 0,
+          "parser_ops" : [
+            {
+              "parameters" : [
+                {
+                  "type" : "regular",
+                  "value" : "ethernet"
+                }
+              ],
+              "op" : "extract"
+            }
+          ],
+          "transitions" : [
+            {
+              "type" : "hexstr",
+              "value" : "0x1212",
+              "mask" : null,
+              "next_state" : "parse_myTunnel"
+            },
+            {
+              "type" : "hexstr",
+              "value" : "0x8000",
+              "mask" : null,
+              "next_state" : "parse_ipv4"
+            },
+            {
+              "value" : "default",
+              "mask" : null,
+              "next_state" : null
+            }
+          ],
+          "transition_key" : [
+            {
+              "type" : "field",
+              "value" : ["ethernet", "etherType"]
+            }
+          ]
+        },
+        {
+          "name" : "parse_myTunnel",
+          "id" : 1,
+          "parser_ops" : [
+            {
+              "parameters" : [
+                {
+                  "type" : "regular",
+                  "value" : "myTunnel"
+                }
+              ],
+              "op" : "extract"
+            }
+          ],
+          "transitions" : [
+            {
+              "type" : "hexstr",
+              "value" : "0x8000",
+              "mask" : null,
+              "next_state" : "parse_ipv4"
+            },
+            {
+              "value" : "default",
+              "mask" : null,
+              "next_state" : null
+            }
+          ],
+          "transition_key" : [
+            {
+              "type" : "field",
+              "value" : ["myTunnel", "proto_id"]
+            }
+          ]
+        },
+        {
+          "name" : "parse_ipv4",
+          "id" : 2,
+          "parser_ops" : [
+            {
+              "parameters" : [
+                {
+                  "type" : "regular",
+                  "value" : "ipv4"
+                }
+              ],
+              "op" : "extract"
+            }
+          ],
+          "transitions" : [
+            {
+              "value" : "default",
+              "mask" : null,
+              "next_state" : null
+            }
+          ],
+          "transition_key" : []
+        }
+      ]
+    }
+  ],
+  "parse_vsets" : [],
+  "deparsers" : [
+    {
+      "name" : "deparser",
+      "id" : 0,
+      "source_info" : {
+        "filename" : "tunnel_switch.p4",
+        "line" : 195,
+        "column" : 8,
+        "source_fragment" : "MyDeparser"
+      },
+      "order" : ["ethernet", "myTunnel", "ipv4"]
+    }
+  ],
+  "meter_arrays" : [],
+  "counter_arrays" : [],
+  "register_arrays" : [],
+  "calculations" : [
+    {
+      "name" : "calc",
+      "id" : 0,
+      "source_info" : {
+        "filename" : "tunnel_switch.p4",
+        "line" : 173,
+        "column" : 1,
+        "source_fragment" : "update_checksum( ..."
+      },
+      "algo" : "csum16",
+      "input" : [
+        {
+          "type" : "field",
+          "value" : ["ipv4", "version"]
+        },
+        {
+          "type" : "field",
+          "value" : ["ipv4", "ihl"]
+        },
+        {
+          "type" : "field",
+          "value" : ["ipv4", "diffserv"]
+        },
+        {
+          "type" : "field",
+          "value" : ["ipv4", "totalLen"]
+        },
+        {
+          "type" : "field",
+          "value" : ["ipv4", "identification"]
+        },
+        {
+          "type" : "field",
+          "value" : ["ipv4", "flags"]
+        },
+        {
+          "type" : "field",
+          "value" : ["ipv4", "fragOffset"]
+        },
+        {
+          "type" : "field",
+          "value" : ["ipv4", "ttl"]
+        },
+        {
+          "type" : "field",
+          "value" : ["ipv4", "protocol"]
+        },
+        {
+          "type" : "field",
+          "value" : ["ipv4", "srcAddr"]
+        },
+        {
+          "type" : "field",
+          "value" : ["ipv4", "dstAddr"]
+        }
+      ]
+    }
+  ],
+  "learn_lists" : [],
+  "actions" : [
+    {
+      "name" : "NoAction",
+      "id" : 0,
+      "runtime_data" : [],
+      "primitives" : []
+    },
+    {
+      "name" : "MyIngress.drop",
+      "id" : 1,
+      "runtime_data" : [],
+      "primitives" : [
+        {
+          "op" : "drop",
+          "parameters" : [],
+          "source_info" : {
+            "filename" : "tunnel_switch.p4",
+            "line" : 105,
+            "column" : 8,
+            "source_fragment" : "mark_to_drop()"
+          }
+        }
+      ]
+    },
+    {
+      "name" : "MyIngress.drop",
+      "id" : 2,
+      "runtime_data" : [],
+      "primitives" : [
+        {
+          "op" : "drop",
+          "parameters" : [],
+          "source_info" : {
+            "filename" : "tunnel_switch.p4",
+            "line" : 105,
+            "column" : 8,
+            "source_fragment" : "mark_to_drop()"
+          }
+        }
+      ]
+    },
+    {
+      "name" : "MyIngress.ipv4_forward",
+      "id" : 3,
+      "runtime_data" : [
+        {
+          "name" : "dstAddr",
+          "bitwidth" : 48
+        },
+        {
+          "name" : "port",
+          "bitwidth" : 9
+        }
+      ],
+      "primitives" : [
+        {
+          "op" : "assign",
+          "parameters" : [
+            {
+              "type" : "field",
+              "value" : ["standard_metadata", "egress_spec"]
+            },
+            {
+              "type" : "runtime_data",
+              "value" : 1
+            }
+          ],
+          "source_info" : {
+            "filename" : "tunnel_switch.p4",
+            "line" : 109,
+            "column" : 8,
+            "source_fragment" : "standard_metadata.egress_spec = port"
+          }
+        },
+        {
+          "op" : "assign",
+          "parameters" : [
+            {
+              "type" : "field",
+              "value" : ["ethernet", "srcAddr"]
+            },
+            {
+              "type" : "field",
+              "value" : ["ethernet", "dstAddr"]
+            }
+          ],
+          "source_info" : {
+            "filename" : "tunnel_switch.p4",
+            "line" : 110,
+            "column" : 8,
+            "source_fragment" : "hdr.ethernet.srcAddr = hdr.ethernet.dstAddr"
+          }
+        },
+        {
+          "op" : "assign",
+          "parameters" : [
+            {
+              "type" : "field",
+              "value" : ["ethernet", "dstAddr"]
+            },
+            {
+              "type" : "runtime_data",
+              "value" : 0
+            }
+          ],
+          "source_info" : {
+            "filename" : "tunnel_switch.p4",
+            "line" : 111,
+            "column" : 8,
+            "source_fragment" : "hdr.ethernet.dstAddr = dstAddr"
+          }
+        },
+        {
+          "op" : "assign",
+          "parameters" : [
+            {
+              "type" : "field",
+              "value" : ["ipv4", "ttl"]
+            },
+            {
+              "type" : "expression",
+              "value" : {
+                "type" : "expression",
+                "value" : {
+                  "op" : "&",
+                  "left" : {
+                    "type" : "expression",
+                    "value" : {
+                      "op" : "+",
+                      "left" : {
+                        "type" : "field",
+                        "value" : ["ipv4", "ttl"]
+                      },
+                      "right" : {
+                        "type" : "hexstr",
+                        "value" : "0xff"
+                      }
+                    }
+                  },
+                  "right" : {
+                    "type" : "hexstr",
+                    "value" : "0xff"
+                  }
+                }
+              }
+            }
+          ],
+          "source_info" : {
+            "filename" : "tunnel_switch.p4",
+            "line" : 112,
+            "column" : 8,
+            "source_fragment" : "hdr.ipv4.ttl = hdr.ipv4.ttl - 1"
+          }
+        }
+      ]
+    },
+    {
+      "name" : "MyIngress.myTunnel_forward",
+      "id" : 4,
+      "runtime_data" : [
+        {
+          "name" : "port",
+          "bitwidth" : 9
+        }
+      ],
+      "primitives" : [
+        {
+          "op" : "assign",
+          "parameters" : [
+            {
+              "type" : "field",
+              "value" : ["standard_metadata", "egress_spec"]
+            },
+            {
+              "type" : "runtime_data",
+              "value" : 0
+            }
+          ],
+          "source_info" : {
+            "filename" : "tunnel_switch.p4",
+            "line" : 129,
+            "column" : 8,
+            "source_fragment" : "standard_metadata.egress_spec = port"
+          }
+        }
+      ]
+    }
+  ],
+  "pipelines" : [
+    {
+      "name" : "ingress",
+      "id" : 0,
+      "source_info" : {
+        "filename" : "tunnel_switch.p4",
+        "line" : 101,
+        "column" : 8,
+        "source_fragment" : "MyIngress"
+      },
+      "init_table" : "node_2",
+      "tables" : [
+        {
+          "name" : "MyIngress.ipv4_lpm",
+          "id" : 0,
+          "source_info" : {
+            "filename" : "tunnel_switch.p4",
+            "line" : 115,
+            "column" : 10,
+            "source_fragment" : "ipv4_lpm"
+          },
+          "key" : [
+            {
+              "match_type" : "lpm",
+              "name" : "hdr.ipv4.dstAddr",
+              "target" : ["ipv4", "dstAddr"],
+              "mask" : null
+            }
+          ],
+          "match_type" : "lpm",
+          "type" : "simple",
+          "max_size" : 1024,
+          "with_counters" : false,
+          "support_timeout" : false,
+          "direct_meters" : null,
+          "action_ids" : [3, 1, 0],
+          "actions" : ["MyIngress.ipv4_forward", "MyIngress.drop", "NoAction"],
+          "base_default_next" : "node_4",
+          "next_tables" : {
+            "MyIngress.ipv4_forward" : "node_4",
+            "MyIngress.drop" : "node_4",
+            "NoAction" : "node_4"
+          },
+          "default_entry" : {
+            "action_id" : 1,
+            "action_const" : false,
+            "action_data" : [],
+            "action_entry_const" : false
+          }
+        },
+        {
+          "name" : "MyIngress.myTunnel_exact",
+          "id" : 1,
+          "source_info" : {
+            "filename" : "tunnel_switch.p4",
+            "line" : 132,
+            "column" : 10,
+            "source_fragment" : "myTunnel_exact"
+          },
+          "key" : [
+            {
+              "match_type" : "exact",
+              "name" : "hdr.myTunnel.dst_id",
+              "target" : ["myTunnel", "dst_id"],
+              "mask" : null
+            }
+          ],
+          "match_type" : "exact",
+          "type" : "simple",
+          "max_size" : 1024,
+          "with_counters" : false,
+          "support_timeout" : false,
+          "direct_meters" : null,
+          "action_ids" : [4, 2],
+          "actions" : ["MyIngress.myTunnel_forward", "MyIngress.drop"],
+          "base_default_next" : null,
+          "next_tables" : {
+            "MyIngress.myTunnel_forward" : null,
+            "MyIngress.drop" : null
+          },
+          "default_entry" : {
+            "action_id" : 2,
+            "action_const" : false,
+            "action_data" : [],
+            "action_entry_const" : false
+          }
+        }
+      ],
+      "action_profiles" : [],
+      "conditionals" : [
+        {
+          "name" : "node_2",
+          "id" : 0,
+          "source_info" : {
+            "filename" : "tunnel_switch.p4",
+            "line" : 145,
+            "column" : 12,
+            "source_fragment" : "hdr.ipv4.isValid() && !hdr.myTunnel.isValid()"
+          },
+          "expression" : {
+            "type" : "expression",
+            "value" : {
+              "op" : "and",
+              "left" : {
+                "type" : "expression",
+                "value" : {
+                  "op" : "d2b",
+                  "left" : null,
+                  "right" : {
+                    "type" : "field",
+                    "value" : ["ipv4", "$valid$"]
+                  }
+                }
+              },
+              "right" : {
+                "type" : "expression",
+                "value" : {
+                  "op" : "not",
+                  "left" : null,
+                  "right" : {
+                    "type" : "expression",
+                    "value" : {
+                      "op" : "d2b",
+                      "left" : null,
+                      "right" : {
+                        "type" : "field",
+                        "value" : ["myTunnel", "$valid$"]
+                      }
+                    }
+                  }
+                }
+              }
+            }
+          },
+          "true_next" : "MyIngress.ipv4_lpm",
+          "false_next" : "node_4"
+        },
+        {
+          "name" : "node_4",
+          "id" : 1,
+          "source_info" : {
+            "filename" : "tunnel_switch.p4",
+            "line" : 150,
+            "column" : 12,
+            "source_fragment" : "hdr.myTunnel.isValid()"
+          },
+          "expression" : {
+            "type" : "expression",
+            "value" : {
+              "op" : "d2b",
+              "left" : null,
+              "right" : {
+                "type" : "field",
+                "value" : ["myTunnel", "$valid$"]
+              }
+            }
+          },
+          "false_next" : null,
+          "true_next" : "MyIngress.myTunnel_exact"
+        }
+      ]
+    },
+    {
+      "name" : "egress",
+      "id" : 1,
+      "source_info" : {
+        "filename" : "tunnel_switch.p4",
+        "line" : 161,
+        "column" : 8,
+        "source_fragment" : "MyEgress"
+      },
+      "init_table" : null,
+      "tables" : [],
+      "action_profiles" : [],
+      "conditionals" : []
+    }
+  ],
+  "checksums" : [
+    {
+      "name" : "cksum",
+      "id" : 0,
+      "source_info" : {
+        "filename" : "tunnel_switch.p4",
+        "line" : 173,
+        "column" : 1,
+        "source_fragment" : "update_checksum( ..."
+      },
+      "target" : ["ipv4", "hdrChecksum"],
+      "type" : "generic",
+      "calculation" : "calc",
+      "verify" : false,
+      "update" : true,
+      "if_cond" : {
+        "type" : "expression",
+        "value" : {
+          "op" : "d2b",
+          "left" : null,
+          "right" : {
+            "type" : "field",
+            "value" : ["ipv4", "$valid$"]
+          }
+        }
+      }
+    }
+  ],
+  "force_arith" : [],
+  "extern_instances" : [],
+  "field_aliases" : [
+    [
+      "queueing_metadata.enq_timestamp",
+      ["standard_metadata", "enq_timestamp"]
+    ],
+    [
+      "queueing_metadata.enq_qdepth",
+      ["standard_metadata", "enq_qdepth"]
+    ],
+    [
+      "queueing_metadata.deq_timedelta",
+      ["standard_metadata", "deq_timedelta"]
+    ],
+    [
+      "queueing_metadata.deq_qdepth",
+      ["standard_metadata", "deq_qdepth"]
+    ],
+    [
+      "intrinsic_metadata.ingress_global_timestamp",
+      ["standard_metadata", "ingress_global_timestamp"]
+    ],
+    [
+      "intrinsic_metadata.egress_global_timestamp",
+      ["standard_metadata", "egress_global_timestamp"]
+    ],
+    [
+      "intrinsic_metadata.lf_field_list",
+      ["standard_metadata", "lf_field_list"]
+    ],
+    [
+      "intrinsic_metadata.mcast_grp",
+      ["standard_metadata", "mcast_grp"]
+    ],
+    [
+      "intrinsic_metadata.resubmit_flag",
+      ["standard_metadata", "resubmit_flag"]
+    ],
+    [
+      "intrinsic_metadata.egress_rid",
+      ["standard_metadata", "egress_rid"]
+    ],
+    [
+      "intrinsic_metadata.recirculate_flag",
+      ["standard_metadata", "recirculate_flag"]
+    ]
+  ],
+  "program" : "./tunnel_switch.p4i",
+  "__meta__" : {
+    "version" : [2, 18],
+    "compiler" : "https://github.com/p4lang/p4c"
+  }
+}
\ No newline at end of file
diff --git a/contrib/p4/docker_files/switch_bmv2/P4/tunnel_switch.json.license b/contrib/p4/docker_files/switch_bmv2/P4/tunnel_switch.json.license
new file mode 100644
index 000000000..c7317e172
--- /dev/null
+++ b/contrib/p4/docker_files/switch_bmv2/P4/tunnel_switch.json.license
@@ -0,0 +1,5 @@
+# SPDX-FileCopyrightText: 2020-2021 CASTOR Software Research Centre
+# <https://www.castor.kth.se/>
+# SPDX-FileCopyrightText: 2020-2021 Johan Paulsson
+
+# SPDX-License-Identifier: Apache-2.0
\ No newline at end of file
diff --git a/contrib/p4/docker_files/switch_bmv2/table_handler.py b/contrib/p4/docker_files/switch_bmv2/table_handler.py
new file mode 100644
index 000000000..20b536156
--- /dev/null
+++ b/contrib/p4/docker_files/switch_bmv2/table_handler.py
@@ -0,0 +1,53 @@
+# SPDX-FileCopyrightText: 2020-2021 CASTOR Software Research Centre
+# <https://www.castor.kth.se/>
+# SPDX-FileCopyrightText: 2020-2021 Johan Paulsson
+
+# SPDX-License-Identifier: Apache-2.0
+
+import json
+import subprocess
+import argparse
+
+PATH_TO_TABLE_JSON = "/app/ip_table.json"
+PATH_TO_CLI = "/usr/local/behavioral-model/tools/runtime_CLI.py"
+
+# Parser
+parser = argparse.ArgumentParser()
+parser.add_argument("table_file")
+
+args = parser.parse_args()
+
+
+def main():
+    with open(args.table_file) as json_file:
+        data = json.load(json_file)
+
+        final_command = PATH_TO_CLI
+
+        with open("/app/table_input.txt", "w") as fstdin:
+            for table_entry in data["table_entries"]:
+                CLI_command = (
+                    "table_add"
+                    " " + str(table_entry["table_name"]) + ""
+                    " " + str(table_entry["action_name"]) + ""
+                    " "
+                    + str(table_entry["match"][0])
+                    + "/"
+                    + str(table_entry["match"][1])
+                    + ""
+                    " => "
+                    + str(table_entry["action_params"]["dstAddr"])
+                    + " "
+                    + str(table_entry["action_params"]["port"])
+                )
+
+                fstdin.write(CLI_command + "\n")
+                final_command += "; " + CLI_command
+
+        with open("/app/table_command_output.txt", "w") as fout:
+            with open("/app/table_input.txt", "r") as pyargs:
+                subprocess.run(PATH_TO_CLI, stdin=pyargs, stdout=fout)
+
+
+if __name__ == "__main__":
+    main()
diff --git a/contrib/p4/docker_files/switch_bmv2/tables/ip_table1.json b/contrib/p4/docker_files/switch_bmv2/tables/ip_table1.json
new file mode 100644
index 000000000..b31dfeb4e
--- /dev/null
+++ b/contrib/p4/docker_files/switch_bmv2/tables/ip_table1.json
@@ -0,0 +1,64 @@
+{
+    "table_entries": [
+        {
+            "table_name": "MyIngress.ipv4_lpm",
+            "action_name": "MyIngress.ipv4_forward",
+            "match": [
+                "192.168.1.1",
+                32
+            ],
+            "action_params": {
+                "dstAddr": "00:01:02:03:04:05",
+                "port": 0
+            }
+        },
+        {
+            "table_name": "MyIngress.ipv4_lpm",
+            "action_name": "MyIngress.ipv4_forward",
+            "match": [
+                "192.168.1.2",
+                32
+            ],
+            "action_params": {
+                "dstAddr": "00:01:02:03:04:05",
+                "port": 1
+            }
+        },
+        {
+            "table_name": "MyIngress.ipv4_lpm",
+            "action_name": "MyIngress.ipv4_forward",
+            "match": [
+                "192.168.1.3",
+                32
+            ],
+            "action_params": {
+                "dstAddr": "00:01:02:03:04:05",
+                "port": 100
+            }
+        },
+        {
+            "table_name": "MyIngress.ipv4_lpm",
+            "action_name": "MyIngress.ipv4_forward",
+            "match": [
+                "192.168.1.4",
+                32
+            ],
+            "action_params": {
+                "dstAddr": "00:01:02:03:04:05",
+                "port": 100
+            }
+        },
+        {
+            "table_name": "MyIngress.ipv4_lpm",
+            "action_name": "MyIngress.ipv4_forward",
+            "match": [
+                "192.168.1.16",
+                32
+            ],
+            "action_params": {
+                "dstAddr": "00:01:02:03:04:05",
+                "port": 100
+            }
+        }
+    ]
+}
\ No newline at end of file
diff --git a/contrib/p4/docker_files/switch_bmv2/tables/ip_table1.json.license b/contrib/p4/docker_files/switch_bmv2/tables/ip_table1.json.license
new file mode 100644
index 000000000..c7317e172
--- /dev/null
+++ b/contrib/p4/docker_files/switch_bmv2/tables/ip_table1.json.license
@@ -0,0 +1,5 @@
+# SPDX-FileCopyrightText: 2020-2021 CASTOR Software Research Centre
+# <https://www.castor.kth.se/>
+# SPDX-FileCopyrightText: 2020-2021 Johan Paulsson
+
+# SPDX-License-Identifier: Apache-2.0
\ No newline at end of file
diff --git a/contrib/p4/docker_files/switch_bmv2/tables/ip_table2.json b/contrib/p4/docker_files/switch_bmv2/tables/ip_table2.json
new file mode 100644
index 000000000..effc758e2
--- /dev/null
+++ b/contrib/p4/docker_files/switch_bmv2/tables/ip_table2.json
@@ -0,0 +1,64 @@
+{
+    "table_entries": [
+        {
+            "table_name": "MyIngress.ipv4_lpm",
+            "action_name": "MyIngress.ipv4_forward",
+            "match": [
+                "192.168.1.1",
+                32
+            ],
+            "action_params": {
+                "dstAddr": "00:01:02:03:04:05",
+                "port": 100
+            }
+        },
+        {
+            "table_name": "MyIngress.ipv4_lpm",
+            "action_name": "MyIngress.ipv4_forward",
+            "match": [
+                "192.168.1.2",
+                32
+            ],
+            "action_params": {
+                "dstAddr": "00:01:02:03:04:05",
+                "port": 100
+            }
+        },
+        {
+            "table_name": "MyIngress.ipv4_lpm",
+            "action_name": "MyIngress.ipv4_forward",
+            "match": [
+                "192.168.1.3",
+                32
+            ],
+            "action_params": {
+                "dstAddr": "00:01:02:03:04:05",
+                "port": 0
+            }
+        },
+        {
+            "table_name": "MyIngress.ipv4_lpm",
+            "action_name": "MyIngress.ipv4_forward",
+            "match": [
+                "192.168.1.4",
+                32
+            ],
+            "action_params": {
+                "dstAddr": "00:01:02:03:04:05",
+                "port": 1
+            }
+        },
+        {
+            "table_name": "MyIngress.ipv4_lpm",
+            "action_name": "MyIngress.ipv4_forward",
+            "match": [
+                "192.168.1.16",
+                32
+            ],
+            "action_params": {
+                "dstAddr": "00:01:02:03:04:05",
+                "port": 51
+            }
+        }
+    ]
+}
\ No newline at end of file
diff --git a/contrib/p4/docker_files/switch_bmv2/tables/ip_table2.json.license b/contrib/p4/docker_files/switch_bmv2/tables/ip_table2.json.license
new file mode 100644
index 000000000..c7317e172
--- /dev/null
+++ b/contrib/p4/docker_files/switch_bmv2/tables/ip_table2.json.license
@@ -0,0 +1,5 @@
+# SPDX-FileCopyrightText: 2020-2021 CASTOR Software Research Centre
+# <https://www.castor.kth.se/>
+# SPDX-FileCopyrightText: 2020-2021 Johan Paulsson
+
+# SPDX-License-Identifier: Apache-2.0
\ No newline at end of file
diff --git a/contrib/p4/docker_files/switch_bmv2/tables/ip_table3.json b/contrib/p4/docker_files/switch_bmv2/tables/ip_table3.json
new file mode 100644
index 000000000..c2b5746b2
--- /dev/null
+++ b/contrib/p4/docker_files/switch_bmv2/tables/ip_table3.json
@@ -0,0 +1,16 @@
+{
+    "table_entries": [
+        {
+            "table_name": "MyIngress.ipv4_lpm",
+            "action_name": "MyIngress.ipv4_forward",
+            "match": [
+                "192.168.1.16",
+                32
+            ],
+            "action_params": {
+                "dstAddr": "00:01:02:03:04:05",
+                "port": 51
+            }
+        }
+    ]
+}
\ No newline at end of file
diff --git a/contrib/p4/docker_files/switch_bmv2/tables/ip_table3.json.license b/contrib/p4/docker_files/switch_bmv2/tables/ip_table3.json.license
new file mode 100644
index 000000000..c7317e172
--- /dev/null
+++ b/contrib/p4/docker_files/switch_bmv2/tables/ip_table3.json.license
@@ -0,0 +1,5 @@
+# SPDX-FileCopyrightText: 2020-2021 CASTOR Software Research Centre
+# <https://www.castor.kth.se/>
+# SPDX-FileCopyrightText: 2020-2021 Johan Paulsson
+
+# SPDX-License-Identifier: Apache-2.0
\ No newline at end of file
diff --git a/contrib/p4/docker_files/switch_bmv2/tables/ip_table4.json b/contrib/p4/docker_files/switch_bmv2/tables/ip_table4.json
new file mode 100644
index 000000000..c2b5746b2
--- /dev/null
+++ b/contrib/p4/docker_files/switch_bmv2/tables/ip_table4.json
@@ -0,0 +1,16 @@
+{
+    "table_entries": [
+        {
+            "table_name": "MyIngress.ipv4_lpm",
+            "action_name": "MyIngress.ipv4_forward",
+            "match": [
+                "192.168.1.16",
+                32
+            ],
+            "action_params": {
+                "dstAddr": "00:01:02:03:04:05",
+                "port": 51
+            }
+        }
+    ]
+}
\ No newline at end of file
diff --git a/contrib/p4/docker_files/switch_bmv2/tables/ip_table4.json.license b/contrib/p4/docker_files/switch_bmv2/tables/ip_table4.json.license
new file mode 100644
index 000000000..c7317e172
--- /dev/null
+++ b/contrib/p4/docker_files/switch_bmv2/tables/ip_table4.json.license
@@ -0,0 +1,5 @@
+# SPDX-FileCopyrightText: 2020-2021 CASTOR Software Research Centre
+# <https://www.castor.kth.se/>
+# SPDX-FileCopyrightText: 2020-2021 Johan Paulsson
+
+# SPDX-License-Identifier: Apache-2.0
\ No newline at end of file
diff --git a/contrib/p4/docker_files/switch_bmv2/tables/ip_table5.json b/contrib/p4/docker_files/switch_bmv2/tables/ip_table5.json
new file mode 100644
index 000000000..c2b5746b2
--- /dev/null
+++ b/contrib/p4/docker_files/switch_bmv2/tables/ip_table5.json
@@ -0,0 +1,16 @@
+{
+    "table_entries": [
+        {
+            "table_name": "MyIngress.ipv4_lpm",
+            "action_name": "MyIngress.ipv4_forward",
+            "match": [
+                "192.168.1.16",
+                32
+            ],
+            "action_params": {
+                "dstAddr": "00:01:02:03:04:05",
+                "port": 51
+            }
+        }
+    ]
+}
\ No newline at end of file
diff --git a/contrib/p4/docker_files/switch_bmv2/tables/ip_table5.json.license b/contrib/p4/docker_files/switch_bmv2/tables/ip_table5.json.license
new file mode 100644
index 000000000..c7317e172
--- /dev/null
+++ b/contrib/p4/docker_files/switch_bmv2/tables/ip_table5.json.license
@@ -0,0 +1,5 @@
+# SPDX-FileCopyrightText: 2020-2021 CASTOR Software Research Centre
+# <https://www.castor.kth.se/>
+# SPDX-FileCopyrightText: 2020-2021 Johan Paulsson
+
+# SPDX-License-Identifier: Apache-2.0
\ No newline at end of file
diff --git a/contrib/p4/docker_files/switch_bmv2/tables/ip_table6.json b/contrib/p4/docker_files/switch_bmv2/tables/ip_table6.json
new file mode 100644
index 000000000..e5b644732
--- /dev/null
+++ b/contrib/p4/docker_files/switch_bmv2/tables/ip_table6.json
@@ -0,0 +1,16 @@
+{
+    "table_entries": [
+        {
+            "table_name": "MyIngress.ipv4_lpm",
+            "action_name": "MyIngress.ipv4_forward",
+            "match": [
+                "192.168.1.16",
+                32
+            ],
+            "action_params": {
+                "dstAddr": "00:01:02:03:04:05",
+                "port": 0
+            }
+        }
+    ]
+}
\ No newline at end of file
diff --git a/contrib/p4/docker_files/switch_bmv2/tables/ip_table6.json.license b/contrib/p4/docker_files/switch_bmv2/tables/ip_table6.json.license
new file mode 100644
index 000000000..c7317e172
--- /dev/null
+++ b/contrib/p4/docker_files/switch_bmv2/tables/ip_table6.json.license
@@ -0,0 +1,5 @@
+# SPDX-FileCopyrightText: 2020-2021 CASTOR Software Research Centre
+# <https://www.castor.kth.se/>
+# SPDX-FileCopyrightText: 2020-2021 Johan Paulsson
+
+# SPDX-License-Identifier: Apache-2.0
\ No newline at end of file
diff --git a/contrib/p4/network_config.json b/contrib/p4/network_config.json
new file mode 100644
index 000000000..afc163902
--- /dev/null
+++ b/contrib/p4/network_config.json
@@ -0,0 +1,254 @@
+{
+    "links": [
+        {
+            "device1": "Node1",
+            "device1_port": 0,
+            "device2": "Switch1",
+            "device2_port": 0,
+            "type": "Node_to_Switch"
+        },
+        {
+            "device1": "Node2",
+            "device1_port": 0,
+            "device2": "Switch1",
+            "device2_port": 1,
+            "type": "Node_to_Switch"
+        },
+        {
+            "device1": "Node3",
+            "device1_port": 0,
+            "device2": "Switch2",
+            "device2_port": 0,
+            "type": "Node_to_Switch"
+        },
+        {
+            "device1": "Node4",
+            "device1_port": 0,
+            "device2": "Switch2",
+            "device2_port": 1,
+            "type": "Node_to_Switch"
+        },
+        {
+            "device1": "Switch2",
+            "device1_port": 51,
+            "device2": "Switch3",
+            "device2_port": 50,
+            "type": "Switch_to_Switch"
+        },
+        {
+            "device1": "Switch3",
+            "device1_port": 51,
+            "device2": "Switch4",
+            "device2_port": 50,
+            "type": "Switch_to_Switch"
+        },
+        {
+            "device1": "Switch4",
+            "device1_port": 51,
+            "device2": "Switch5",
+            "device2_port": 50,
+            "type": "Switch_to_Switch"
+        },
+        {
+            "device1": "Switch5",
+            "device1_port": 51,
+            "device2": "Switch6",
+            "device2_port": 50,
+            "type": "Switch_to_Switch"
+        },
+        {
+            "device1": "Node16",
+            "device1_port": 0,
+            "device2": "Switch6",
+            "device2_port": 0,
+            "type": "Node_to_Switch"
+        },
+        {
+            "device1": "Node1",
+            "device1_port": 1,
+            "device2": "Switch2",
+            "device2_port": 2,
+            "type": "Node_to_Switch"
+        },
+        {
+            "device1": "Node2",
+            "device1_port": 1,
+            "device2": "Switch2",
+            "device2_port": 3,
+            "type": "Node_to_Switch"
+        },
+        {
+            "device1": "Switch1",
+            "device1_port": 100,
+            "device2": "Switch2",
+            "device2_port": 100,
+            "type": "Switch_to_Switch"
+        }
+    ],
+    "nodes": {
+        "Node1": {
+            "id": 0,
+            "ipv4_addr": "192.168.1.1",
+            "used_ports": [
+                0,
+                1
+            ]
+        },
+        "Node10": {
+            "id": 9,
+            "ipv4_addr": "192.168.1.10",
+            "used_ports": []
+        },
+        "Node11": {
+            "id": 10,
+            "ipv4_addr": "192.168.1.11",
+            "used_ports": []
+        },
+        "Node12": {
+            "id": 11,
+            "ipv4_addr": "192.168.1.12",
+            "used_ports": []
+        },
+        "Node13": {
+            "id": 12,
+            "ipv4_addr": "192.168.1.13",
+            "used_ports": []
+        },
+        "Node14": {
+            "id": 13,
+            "ipv4_addr": "192.168.1.14",
+            "used_ports": []
+        },
+        "Node15": {
+            "id": 14,
+            "ipv4_addr": "192.168.1.15",
+            "used_ports": []
+        },
+        "Node16": {
+            "id": 15,
+            "ipv4_addr": "192.168.1.16",
+            "used_ports": [
+                0
+            ]
+        },
+        "Node17": {
+            "id": 20,
+            "ipv4_addr": "",
+            "used_ports": []
+        },
+        "Node2": {
+            "id": 1,
+            "ipv4_addr": "192.168.1.2",
+            "used_ports": [
+                0,
+                1
+            ]
+        },
+        "Node3": {
+            "id": 2,
+            "ipv4_addr": "192.168.1.3",
+            "used_ports": [
+                0
+            ]
+        },
+        "Node4": {
+            "id": 3,
+            "ipv4_addr": "192.168.1.4",
+            "used_ports": [
+                0
+            ]
+        },
+        "Node5": {
+            "id": 4,
+            "ipv4_addr": "192.168.1.5",
+            "used_ports": []
+        },
+        "Node6": {
+            "id": 5,
+            "ipv4_addr": "192.168.1.6",
+            "used_ports": []
+        },
+        "Node7": {
+            "id": 6,
+            "ipv4_addr": "192.168.1.7",
+            "used_ports": []
+        },
+        "Node8": {
+            "id": 7,
+            "ipv4_addr": "192.168.1.8",
+            "used_ports": []
+        },
+        "Node9": {
+            "id": 8,
+            "ipv4_addr": "192.168.1.9",
+            "used_ports": []
+        }
+    },
+    "switches": {
+        "Switch1": {
+            "p4_file_name": "simple_switch.json",
+            "table_entries": [
+                "ip_table1.json"
+            ],
+            "used_ports": [
+                0,
+                1,
+                100
+            ]
+        },
+        "Switch2": {
+            "p4_file_name": "simple_switch.json",
+            "table_entries": [
+                "ip_table2.json"
+            ],
+            "used_ports": [
+                0,
+                1,
+                51,
+                2,
+                3,
+                100
+            ]
+        },
+        "Switch3": {
+            "p4_file_name": "simple_switch.json",
+            "table_entries": [
+                "ip_table3.json"
+            ],
+            "used_ports": [
+                50,
+                51
+            ]
+        },
+        "Switch4": {
+            "p4_file_name": "simple_switch.json",
+            "table_entries": [
+                "ip_table4.json"
+            ],
+            "used_ports": [
+                50,
+                51
+            ]
+        },
+        "Switch5": {
+            "p4_file_name": "simple_switch.json",
+            "table_entries": [
+                "ip_table5.json"
+            ],
+            "used_ports": [
+                50,
+                51
+            ]
+        },
+        "Switch6": {
+            "p4_file_name": "simple_switch.json",
+            "table_entries": [
+                "ip_table6.json"
+            ],
+            "used_ports": [
+                50,
+                0
+            ]
+        }
+    }
+}
\ No newline at end of file
diff --git a/contrib/p4/network_config.json.license b/contrib/p4/network_config.json.license
new file mode 100644
index 000000000..c7317e172
--- /dev/null
+++ b/contrib/p4/network_config.json.license
@@ -0,0 +1,5 @@
+# SPDX-FileCopyrightText: 2020-2021 CASTOR Software Research Centre
+# <https://www.castor.kth.se/>
+# SPDX-FileCopyrightText: 2020-2021 Johan Paulsson
+
+# SPDX-License-Identifier: Apache-2.0
\ No newline at end of file
diff --git a/contrib/p4/p4_run_setup.py b/contrib/p4/p4_run_setup.py
new file mode 100644
index 000000000..a2440e41b
--- /dev/null
+++ b/contrib/p4/p4_run_setup.py
@@ -0,0 +1,65 @@
+# SPDX-FileCopyrightText: 2020-2021 CASTOR Software Research Centre
+# <https://www.castor.kth.se/>
+# SPDX-FileCopyrightText: 2020-2021 Johan Paulsson
+
+# SPDX-License-Identifier: Apache-2.0
+
+from benchexec.model import Run
+from benchexec import result
+import json
+import os
+import logging
+
+
+class P4SetupHandler(object):
+    """
+    This class creates a new set of runs in the given benchmark.
+    It will create on run for each test in the test_dict. If no
+    property file or expected file is given, default will be used.
+    """
+
+    def __init__(self, benchmark, test_dict):
+        self.benchmark = benchmark
+        self.test_dict = test_dict
+
+    def update_runsets(self):
+        for runSet in self.benchmark.run_sets:
+            # Divide the defined run into multiple run if necessery. Check len of runs: If 0, the setup went wrong.
+            if len(runSet.runs) > 0:
+                old_run = runSet.runs[0]
+                expected_result_filename = old_run.identifier
+                if os.path.exists(expected_result_filename):
+                    expected_dict = self._read_expected_result_json(
+                        expected_result_filename
+                    )
+                else:
+                    logging.info(
+                        "Could not identify expected result file. Assuming all test is true."
+                    )
+                    expected_dict = {}
+                prop = old_run.properties
+                runSet.runs = []
+                for module_name in self.test_dict:
+                    for test_name in self.test_dict[module_name]:
+                        run = Run(module_name + "." + test_name, "", "", "", runSet)
+                        prop = [result.Property("/", False, "Yo")]
+                        run.properties = prop
+                        if run.identifier in expected_dict and len(prop) > 0:
+                            run.expected_results[
+                                prop[0].filename
+                            ] = result.ExpectedResult(
+                                expected_dict[run.identifier] == "True"
+                                or expected_dict[run.identifier] == "true",
+                                None,
+                            )
+                        else:
+                            run.expected_results[
+                                prop[0].filename
+                            ] = result.ExpectedResult(True, None)
+                        runSet.runs.append(run)
+
+    def _read_expected_result_json(self, json_file_path):
+        with open(json_file_path) as json_file:
+            data = json.load(json_file)
+
+            return data
diff --git a/contrib/p4/p4execution.py b/contrib/p4/p4execution.py
new file mode 100644
index 000000000..3ad7745ed
--- /dev/null
+++ b/contrib/p4/p4execution.py
@@ -0,0 +1,829 @@
+# SPDX-FileCopyrightText: 2020-2021 CASTOR Software Research Centre
+# <https://www.castor.kth.se/>
+# SPDX-FileCopyrightText: 2020-2021 Johan Paulsson
+
+# SPDX-License-Identifier: Apache-2.0
+
+import os
+import logging
+
+import time
+import threading
+from benchexec import systeminfo
+from p4.p4_run_setup import P4SetupHandler
+from p4.counter import Counter
+
+from benchexec import tooladapter
+from benchexec import util
+from benchexec import BenchExecException
+
+# File handling
+from shutil import copyfile, rmtree
+import json
+from distutils.dir_util import copy_tree
+
+try:
+    import docker
+except ModuleNotFoundError:
+    raise BenchExecException(
+        "Python-docker package not found. Try reinstalling python docker module"
+    )
+
+try:
+    from pyroute2 import IPRoute
+    from pyroute2 import NetNS
+except ModuleNotFoundError:
+    raise BenchExecException(
+        "pyroute2 python package not found. Try reinstalling pyroute2"
+    )
+
+
+STOPPED_BY_INTERRUPT = False
+
+# Static Parameters
+MGNT_NETWORK_SUBNET = "172.19"  # Subnet 192.19.x.x/16
+NODE_IMAGE_NAME = "basic_node"
+SWITCH_IMAGE_NAME = "switch_bmv2"
+PTF_IMAGE_NAME = "ptf_tester"
+
+
+class P4Execution(object):
+    """
+    This Class is for executing p4 benchmarks. The class creates docker containers representing each
+    device in the network. It creates virutal ethenet connections between all the devices. Finally,
+    it sets up a test container connected to all the nodes in the network.
+    """
+
+    def __init__(self):
+        self.nodes = None  # Set by init
+        self.switches = None  # Set by init
+        self.ptf_tester = None  # Set by init
+
+        # Includes all nodes and switches, not the ptf tester
+        self.nr_of_active_containers = Counter()
+
+        self.client = None
+        self.node_networks = []
+        self.mgnt_network = None
+
+    def init(self, config, benchmark):
+        """
+        This functions will set up the docker network to execute the test.
+        As a result, it needs root permission for the setup part.
+        """
+
+        tool_locator = tooladapter.create_tool_locator(config)
+        benchmark.executable = benchmark.tool.executable(tool_locator)
+        benchmark.tool_version = benchmark.tool.version(benchmark.executable)
+
+        # Read test inputs paths
+        (
+            self.switch_source_path,
+            self.ptf_folder_path,
+            self.network_config_path,
+        ) = self.read_folder_paths(benchmark)
+
+        if not os.path.isdir(self.switch_source_path):
+            logging.critical(
+                "Switch folder path not found: %s, {self.switch_source_path}"
+            )
+            raise BenchExecException(
+                "Switch folder path not found. Look over setup definition"
+            )
+        if not os.path.isdir(self.ptf_folder_path):
+            logging.critical(
+                "Ptf test folder path not found: %s, {self.ptf_folder_path}"
+            )
+            raise (
+                BenchExecException(
+                    f"Ptf test folder path not found: {self.ptf_folder_path}"
+                )
+            )
+
+        if not self.switch_source_path or not self.ptf_folder_path:
+            raise BenchExecException(
+                "Switch or Ptf folder path not defined."
+                f"Switch path: {self.switch_source_path} Folder path: {self.ptf_folder_path}"
+            )
+
+        # Extract network config info
+        if not self.network_config_path:
+            logging.error("No network config file was defined")
+            raise BenchExecException("No network config file was defined")
+
+        with open(self.network_config_path) as json_file:
+            self.network_config = json.load(json_file)
+
+        setup_is_valid = self.network_file_isValid()
+
+        if not setup_is_valid:
+            raise BenchExecException("Network config file is not valid")
+
+        # Container setup
+        self.client = docker.from_env()
+        self.switch_target_path = "/app"
+        self.nrOfNodes = len(self.network_config["nodes"])
+
+        try:
+            # Create the ptf tester container
+            mount_ptf_tester = docker.types.Mount(
+                "/app", self.ptf_folder_path, type="bind"
+            )
+            try:
+                self.ptf_tester = self.client.containers.create(
+                    PTF_IMAGE_NAME,
+                    detach=True,
+                    name="ptfTester",
+                    mounts=[mount_ptf_tester],
+                    tty=True,
+                )
+            except docker.errors.APIError:
+                self.ptf_tester = self.client.containers.get("ptfTester")
+
+            # Create node containers
+            self.nodes = []
+
+            for node_name in self.network_config["nodes"]:
+                try:
+                    self.nodes.append(
+                        self.client.containers.create(
+                            NODE_IMAGE_NAME, detach=True, name=node_name
+                        )
+                    )
+                except docker.errors.APIError:
+                    logging.error("Failed to setup node container.")
+
+            self.switches = []
+
+            # Each switch needs their own mount copy
+            for switch_info in self.network_config["switches"]:
+                mount_path = self.create_switch_mount_copy(switch_info)
+                mount_switch = docker.types.Mount(
+                    self.switch_target_path, mount_path, type="bind"
+                )
+
+                try:
+                    self.switches.append(
+                        self.client.containers.create(
+                            SWITCH_IMAGE_NAME,
+                            detach=True,
+                            name=switch_info,
+                            mounts=[mount_switch],
+                        )
+                    )
+                except docker.errors.APIError:
+                    self.switches.append(self.client.containers.get(switch_info))
+
+            logging.info("Setting up network")
+            self.setup_network()
+            self.connect_nodes_to_switch()
+
+        except docker.errors.APIError as e:
+            self.close()
+            raise BenchExecException(str(e))
+
+    def execute_benchmark(self, benchmark, output_handler):
+        """
+        Excecutes the benchmark.
+        """
+
+        self.start_container_listening()
+
+        # Wait until all nodes and switches are setup
+        while self.nr_of_active_containers.value < len(self.nodes + self.switches):
+            time.sleep(1)
+
+        test_dict = self.read_tests()
+        setup_handler = P4SetupHandler(benchmark, test_dict)
+        setup_handler.update_runsets()
+
+        # Read all switch setup logs
+        for switch in self.switches:
+            switch_log_file = (
+                f"{self.switch_source_path}/{switch.name}/log/switch_log.txt"
+            )
+            switch_command_output = (
+                f"{self.switch_source_path}/{switch.name}/table_command_output.txt"
+            )
+
+            switch_log_file_new = benchmark.log_folder + f"{switch.name}_Setup.log"
+            switch_command_output_new = (
+                benchmark.log_folder + f"{switch.name}_table_entry.log"
+            )
+
+            copyfile(switch_log_file, switch_log_file_new)
+
+            # Check for table output file
+            if os.path.exists(switch_command_output):
+
+                copyfile(switch_command_output, switch_command_output_new)
+            else:
+                logging.info("No tables was loaded for switch: %s, {switch.name}")
+
+            # Clear log file
+            with open(switch_log_file, "r+") as f:
+                f.truncate()
+
+            if output_handler.compress_results:
+                self.move_file_to_zip(switch_log_file_new, output_handler, benchmark)
+
+                if os.path.exists(switch_command_output):
+                    self.move_file_to_zip(
+                        switch_command_output_new, output_handler, benchmark
+                    )
+
+        for runSet in benchmark.run_sets:
+            if STOPPED_BY_INTERRUPT:
+                break
+
+            if not runSet.should_be_executed():
+                output_handler.output_for_skipping_run_set(runSet)
+
+            elif not runSet.runs:
+                output_handler.output_for_skipping_run_set(
+                    runSet, "because it has no files"
+                )
+
+            output_handler.output_before_run_set(runSet)
+
+            for run in runSet.runs:
+                # Create ptf command depending on nr of nodes
+                command = f"ptf --test-dir /app {run.identifier}"
+
+                for node in self.nodes:
+                    node_config = self.network_config["nodes"][node.name]
+
+                    command += f" --device-socket {node_config['id']}-{{0-64}}@tcp://{MGNT_NETWORK_SUBNET}.0.{node_config['id'] + 3}:10001"
+
+                command += " --platform nn"
+
+                return_code, test_output = self._execute_benchmark(run, command)
+
+                test_output = test_output.decode("utf-8")
+
+                try:
+                    with open(run.log_file, "w") as ouputFile:
+                        for _i in range(6):
+                            ouputFile.write("\n")
+
+                        # for result in test_results:
+                        ouputFile.write(test_output + "\n")
+                except OSError:
+                    print("Failed")
+
+                values = {}
+                values["exitcode"] = util.ProcessExitCode.from_raw(return_code)
+                run._cmdline = command.split(" ")
+
+                run.set_result(values)
+
+                # Save all switch log_files
+                for switch in self.switches:
+                    switch_log_file = (
+                        f"{self.switch_source_path}/{switch.name}/log/switch_log.txt"
+                    )
+
+                    switch_log_file_new = run.log_file[:-4] + f"_{switch.name}.log"
+
+                    copyfile(switch_log_file, switch_log_file_new)
+
+                    # Clear the log file for next test
+                    with open(switch_log_file, "r+") as f:
+                        f.truncate()
+
+                    if output_handler.compress_results:
+                        self.move_file_to_zip(
+                            switch_log_file_new, output_handler, benchmark
+                        )
+
+                print(run.identifier + ":   ", end="")
+                output_handler.output_after_run(run)
+
+            output_handler.output_after_benchmark(STOPPED_BY_INTERRUPT)
+
+        self.close()
+
+    def _execute_benchmark(self, run, command):
+
+        return self.ptf_tester.exec_run(command, tty=True)
+
+    def setup_network(self):
+        """
+        Creates the managment network, connectes all nodes and the ptf tester
+        to the network.
+        """
+        try:
+            ipam_pool = docker.types.IPAMPool(
+                subnet=MGNT_NETWORK_SUBNET + ".0.0/16",  # "172.19.0.0/16",
+                gateway=MGNT_NETWORK_SUBNET + ".0.1",  # "172.19.0.1"
+            )
+
+            ipam_config = docker.types.IPAMConfig(pool_configs=[ipam_pool])
+
+            self.mgnt_network = self.client.networks.create(
+                "mgnt", driver="bridge", ipam=ipam_config
+            )
+        except docker.errors.APIError as error:
+            # Check if error is network overlap
+            if "overlap" in str(error):
+                self.mgnt_network = self.client.networks.get("mgnt")
+            else:
+                raise error
+
+        self.mgnt_network.connect(
+            self.ptf_tester, ipv4_address=MGNT_NETWORK_SUBNET + ".0.2"
+        )
+
+        for node in self.nodes:
+            node_config = self.network_config["nodes"][node.name]
+
+            ip_addr = MGNT_NETWORK_SUBNET + ".0.{0}".format(node_config["id"] + 3)
+            self.mgnt_network.connect(node, ipv4_address=ip_addr)
+
+    def connect_nodes_to_switch(self):
+        """
+        This will create veth pairs for all links definid in the network config.
+        Each veth will also be moved to the correct network namespace.
+        """
+        client_low = docker.APIClient()
+        self.start_containers()
+
+        ip = IPRoute()
+
+        # Check if netns folder exists. If not, create one for netns to look intp
+        if not os.path.exists("/var/run/netns"):
+            os.mkdir("/var/run/netns")
+
+        for link in self.network_config["links"]:
+            device1 = link["device1"]
+            device2 = link["device2"]
+            pid_device1 = client_low.inspect_container(device1)["State"]["Pid"]
+            pid_device2 = client_low.inspect_container(device2)["State"]["Pid"]
+
+            # Interface names. Naming convention will be different dempending on connection type
+            iface_device1 = ""
+            iface_device2 = ""
+
+            # If connectiong to switch. Make sure it is setup
+            if link["type"] == "Node_to_Switch":
+                switch_is_setup = os.path.exists("/proc/{0}/ns/net".format(pid_device2))
+                # Wait until switch is setup
+                max_wait_seconds = 10
+                seconds_waited = 0
+                while not switch_is_setup and seconds_waited <= max_wait_seconds:
+                    switch_is_setup = os.path.exists(
+                        "/proc/{0}/ns/net".format(pid_device2)
+                    )
+                    time.sleep(1)
+                    seconds_waited += 1
+
+                # Check if namespaces are addad. If not add simlinuk to namespace
+                if not os.path.islink("/var/run/netns/{0}".format(device1)):
+                    os.symlink(
+                        "/proc/{0}/ns/net".format(pid_device1),
+                        "/var/run/netns/{0}".format(device1),
+                    )
+
+                if not os.path.islink("/var/run/netns/{0}".format(device2)):
+                    if not os.path.exists("/var/run/netns/{0}".format(device2)):
+                        os.symlink(
+                            "/proc/{0}/ns/net".format(pid_device2),
+                            "/var/run/netns/{0}".format(device2),
+                        )
+
+                iface_device1 = link["device1"] + "_{0}".format(link["device1_port"])
+                iface_device2 = link["device2"] + "_{0}".format(link["device2_port"])
+
+                # Create Veth pair and put them in the right namespace
+                ip.link("add", ifname=iface_device1, peer=iface_device2, kind="veth")
+
+                id_node = ip.link_lookup(ifname=iface_device1)[0]
+                ip.link("set", index=id_node, state="up")
+                ip.link("set", index=id_node, net_ns_fd=link["device1"])
+
+                id_switch = ip.link_lookup(ifname=iface_device2)[0]
+                ip.link("set", index=id_switch, state="up")
+                ip.link("set", index=id_switch, net_ns_fd=link["device2"])
+
+                # Start all veth port in Nodes
+                ns = NetNS(device1)
+                ns.link("set", index=id_node, state="up")
+                if "ipv4_addr" in self.network_config["nodes"][device1]:
+                    ns.addr(
+                        "add",
+                        index=id_node,
+                        address=self.network_config["nodes"][device1]["ipv4_addr"],
+                        prefixlen=24,
+                    )
+                if "ipv6_addr" in link:
+                    continue
+
+            if link["type"] == "Switch_to_Switch":
+                switch_is_setup1 = os.path.exists(
+                    "/proc/{0}/ns/net".format(pid_device1)
+                )
+                switch_is_setup2 = os.path.exists(
+                    "/proc/{0}/ns/net".format(pid_device2)
+                )
+
+                max_wait_seconds = 10
+                seconds_waited = 0
+                while not switch_is_setup1 and switch_is_setup2:
+                    switch_is_setup1 = os.path.exists(
+                        "/proc/{0}/ns/net".format(pid_device1)
+                    )
+                    switch_is_setup2 = os.path.exists(
+                        "/proc/{0}/ns/net".format(pid_device2)
+                    )
+                    time.sleep(1)
+                    seconds_waited += 1
+
+                # Check if namespaces are addad. If not add simlink to namespace
+                if not os.path.islink("/var/run/netns/{0}".format(device1)):
+                    os.symlink(
+                        "/proc/{0}/ns/net".format(pid_device1),
+                        "/var/run/netns/{0}".format(device1),
+                    )
+
+                if not os.path.islink("/var/run/netns/{0}".format(device2)):
+                    if not os.path.exists("/var/run/netns/{0}".format(device2)):
+                        os.symlink(
+                            "/proc/{0}/ns/net".format(pid_device2),
+                            "/var/run/netns/{0}".format(device2),
+                        )
+
+                iface_switch1 = link["device1"] + "_{0}".format(link["device1_port"])
+                iface_switch2 = link["device2"] + "_{0}".format(link["device2_port"])
+
+                # Create Veth pair and put them in the right namespace
+                ip.link("add", ifname=iface_switch1, peer=iface_switch2, kind="veth")
+                id_switch1 = ip.link_lookup(ifname=iface_switch1)[0]
+                ip.link("set", index=id_switch1, state="up")
+                ip.link("set", index=id_switch1, net_ns_fd=link["device1"])
+
+                id_switch2 = ip.link_lookup(ifname=iface_switch2)[0]
+                ip.link("set", index=id_switch2, state="up")
+                ip.link("set", index=id_switch2, net_ns_fd=link["device2"])
+
+        # Start all veth in all the switches
+        for switch in self.switches:
+            ns = NetNS(switch.name)
+            net_interfaces = ns.get_links()
+
+            for interface in net_interfaces[2:]:
+                iface_name = interface["attrs"][0][1]
+                id_switch = ns.link_lookup(ifname=iface_name)[0]
+                ns.link("set", index=id_switch, state="up")
+
+    def read_tests(self):
+        """
+        Read the test from the ptf container
+        """
+        # Make sure it's started. This is a blocking call
+        self.ptf_tester.start()
+
+        _, test_info = self.ptf_tester.exec_run("ptf --test-dir /app --list")
+        test_info = test_info.decode()
+
+        test_dict = self.extract_info_from_test_info(test_info)
+
+        return test_dict
+
+    def extract_info_from_test_info(self, test_info):
+        test_info = test_info.split("Test List:")[1]
+        test_modules = test_info.split("Module ")
+        nr_of_modules = len(test_modules) - 1
+        test_modules[len(test_modules) - 1] = test_modules[len(test_modules) - 1].split(
+            "\n{0}".format(nr_of_modules)
+        )[0]
+
+        test_dict = {}
+
+        for i in range(nr_of_modules):
+            test = test_modules[i + 1].split("\n")
+            module_name = test.pop(0).split(":")[0]
+
+            test_names = []
+            for test_string in test:
+                if not str.isspace(test_string) and test_string:
+                    test_names.append(test_string.split(":")[0].strip())
+
+            test_dict[module_name] = test_names
+
+        return test_dict
+
+    def get_system_info(self):
+        return systeminfo.SystemInfo()
+
+    def read_folder_paths(self, benchmark):
+
+        switch_folder = ""
+        ptf_folder = ""
+        network_config = ""
+        option_index = 0
+
+        while option_index < len(benchmark.options):
+            if "switch" in benchmark.options[option_index].lower():
+                switch_folder = benchmark.options[option_index + 1]
+            elif "ptf" in benchmark.options[option_index].lower():
+                ptf_folder = benchmark.options[option_index + 1]
+            elif "network_config" in benchmark.options[option_index].lower():
+                network_config = benchmark.options[option_index + 1]
+
+            option_index += 2
+
+        if "~" in switch_folder:
+            switch_folder = self.extract_path(switch_folder)
+        if "~" in ptf_folder:
+            ptf_folder = self.extract_path(ptf_folder)
+        if "~" in network_config:
+            network_config = self.extract_path(network_config)
+
+        return switch_folder, ptf_folder, network_config
+
+    def extract_path(self, path):
+        import subprocess
+
+        split = subprocess.run(["pwd"], capture_output=True).stdout.decode().split("/")
+        home_dir = f"/{split[1]}/{split[2]}"
+
+        new_path = path.replace("~", home_dir)
+
+        return new_path
+
+    def stop(self):
+        """
+        Needed for automatic cleanup for benchec.
+        """
+        self.close()
+
+    def close(self):
+        """
+        Cleans up all the running containers and clear all created namespaces. Should be called when test is done.
+        """
+        logging.info("Closing containers and cleaning up namespace")
+        container_threads = []
+
+        for container in self.nodes:
+            container_threads.append(
+                threading.Thread(
+                    target=lambda x: x.remove(force=True), args=(container,)
+                )
+            )
+            if os.path.islink("/var/run/netns/{0}".format(container.name)):
+                os.remove("/var/run/netns/{0}".format(container.name))
+
+        for container in self.switches:
+            if os.path.isdir(self.switch_source_path + "/{0}".format(container.name)):
+                rmtree(self.switch_source_path + "/{0}".format(container.name))
+
+            container_threads.append(
+                threading.Thread(
+                    target=lambda x: x.remove(force=True), args=(container,)
+                )
+            )
+            if os.path.islink("/var/run/netns/{0}".format(container.name)):
+                os.remove("/var/run/netns/{0}".format(container.name))
+
+        if self.ptf_tester:
+            container_threads.append(
+                threading.Thread(
+                    target=lambda x: x.remove(force=True), args=(self.ptf_tester,)
+                )
+            )
+
+        [x.start() for x in container_threads]
+        [x.join() for x in container_threads]
+
+        # Remove when all containers are closed
+        if self.mgnt_network:
+            self.mgnt_network.remove()
+
+    def thread_remove_container(self, container):
+        container.remove(force=True)
+
+    def start_containers(self):
+        """
+        Start all containers. This is done with thread. This function does not gurantees that
+        containers are started.
+        """
+        containers_to_start = self.nodes + self.switches
+        containers_to_start.append(self.ptf_tester)
+
+        container_threads = []
+
+        for container in containers_to_start:
+            container_threads.append(
+                threading.Thread(target=lambda x: x.start(), args=(container,))
+            )
+
+        # Start and wait for all to finish
+        [x.start() for x in container_threads]
+        [x.join() for x in container_threads]
+
+    def thread_container_start(self, container):
+        container.start()
+
+    def start_container_listening(self):
+        """
+        This will set all the nodes and switches up for testing. This means all nodes runs
+        the ptf agent script and all switches run the switch starup command. All the ports and their
+        configuration are set automatically.
+        """
+
+        container_threads = []
+
+        for node_container in self.nodes:
+            # Read node info
+            node_config = self.network_config["nodes"][node_container.name]
+
+            node_command = (
+                "python3 /usr/local/src/ptf/ptf_nn/ptf_nn_agent.py --device-socket {0}@tcp://".format(
+                    node_config["id"]
+                )
+                + MGNT_NETWORK_SUBNET
+                + ".0.{0}:10001".format(node_config["id"] + 3)
+            )
+            used_ports = self.network_config["nodes"][node_container.name]["used_ports"]
+
+            for port_nr in used_ports:
+                node_command += " -i {0}-{1}@{2}_{1}".format(
+                    node_config["id"], port_nr, node_container.name
+                )
+
+            container_threads.append(
+                threading.Thread(
+                    target=self.thread_setup_node, args=(node_container, node_command)
+                )
+            )
+
+        for switch in self.switches:
+            switch_config = self.network_config["switches"][switch.name]
+
+            switch_command = "simple_switch --log-file /app/log/switch_log --log-flush"
+
+            used_ports = self.network_config["switches"][switch.name]["used_ports"]
+            for port in used_ports:
+                switch_command += (
+                    " -i {0}@".format(port) + switch.name + "_{0}".format(port)
+                )
+
+            switch_command += f" /app/P4/{switch_config['p4_file_name']}"
+
+            container_threads.append(
+                threading.Thread(
+                    target=self.thread_setup_switch, args=(switch, switch_command)
+                )
+            )
+
+        # Wait for all to setup befor leaveing the method
+        [x.start() for x in container_threads]
+        [x.join() for x in container_threads]
+
+    def set_link_state(self, ns, state, iface_ids):
+        available_states = ["UP", "DOWN"]
+
+        if state in available_states:
+            for iface_id in iface_ids:
+                ns.link("set", index=iface_id, state=state)
+
+    def thread_setup_switch(self, switch_container, switch_command):
+        """
+        Sets up a switch. Ment to be ran in a thread.
+        """
+        ns = NetNS(switch_container.name)
+
+        # Check if some interface failed to start
+        while len(ns.link_lookup(IFLA_OPERSTATE="DOWN")) != 0:
+            self.set_link_state(ns, "UP", ns.link_lookup(IFLA_OPERSTATE="DOWN"))
+            time.sleep(1)
+
+        switch_container.exec_run(switch_command, detach=True)
+        switch_is_setup = False
+        switch_log_file_path = (
+            self.switch_source_path
+            + "/"
+            + switch_container.name
+            + "/log/switch_log.txt"
+        )
+
+        # This loop will wait until server is started up
+        while not switch_is_setup:
+            with open(switch_log_file_path, "r") as f:
+                info_string = f.read()
+                switch_is_setup = "Thrift server was started" in info_string
+
+            time.sleep(1)
+
+        # Load tables
+        if "table_entries" in self.network_config["switches"][switch_container.name]:
+            for table_name in self.network_config["switches"][switch_container.name][
+                "table_entries"
+            ]:
+                table_file_path = (
+                    self.switch_source_path
+                    + "/"
+                    + switch_container.name
+                    + "/tables/{0}".format(table_name)
+                )
+                if os.path.exists(table_file_path):
+                    switch_container.exec_run(
+                        "python3 /app/table_handler.py "
+                        + self.switch_target_path
+                        + "/tables/{0}".format(table_name),
+                        detach=True,
+                    )
+                else:
+                    logging.info("Could not find table: \n %s", table_file_path)
+
+        self.nr_of_active_containers.increment()
+
+    def thread_setup_node(self, node_container, node_command):
+        node_container.exec_run(node_command, detach=True)
+        self.nr_of_active_containers.increment()
+
+    def create_switch_mount_copy(self, switch_name):
+        switch_path = self.switch_source_path + "/" + switch_name
+        os.mkdir(switch_path)
+
+        # Copy relevant folders
+        os.mkdir(switch_path + "/log")
+        os.mkdir(switch_path + "/P4")
+        os.mkdir(switch_path + "/tables")
+
+        # Create log file for switch to use
+        open(switch_path + "/log/switch_log.txt", "x")
+        copy_tree(self.switch_source_path + "/P4", switch_path + "/P4")
+        copy_tree(self.switch_source_path + "/tables", switch_path + "/tables")
+
+        copyfile(
+            self.switch_source_path + "/table_handler.py",
+            switch_path + "/table_handler.py",
+        )
+
+        return switch_path
+
+    def move_file_to_zip(self, file_path, output_handler, benchmark):
+        log_file_path = os.path.relpath(
+            file_path, os.path.join(benchmark.log_folder, os.pardir)
+        )
+        output_handler.log_zip.write(file_path, log_file_path)
+        os.remove(file_path)
+
+    def network_file_isValid(self):
+        """
+        Simple chech throught the network file
+        """
+        if not self.network_config:
+            logging.debug("No network file is defined for validation")
+            return False
+        else:
+            # Check nodes
+            if "nodes" not in self.network_config:
+                logging.debug("No nodes defined in network config")
+                return False
+            elif len(self.network_config["nodes"]) == 0:
+                logging.debug("No nodes defined in network config")
+                return False
+
+            # Check for duplicate node names TODO Check duplicate ids
+            node_names = list(self.network_config["nodes"].keys())
+
+            for node_name in node_names:
+                if node_names.count(node_name) > 1:
+                    logging.debug("Duplicate node name detected")
+                    return False
+
+            # Check switches
+            if "switches" not in self.network_config:
+                logging.debug("No switches defined")
+                return False
+            elif len(self.network_config["switches"]) == 0:
+                logging.debug("No nodes defined in network config")
+                return False
+
+            switch_names = list(self.network_config["switches"].keys())
+
+            for switch_name in switch_names:
+                if switch_names.count(switch_name) > 1:
+                    logging.debug("Duplicate switch name detected")
+                    return False
+
+            # Check links
+            if "links" in self.network_config:
+                all_devices = switch_names + node_names
+
+                for link in self.network_config["links"]:
+                    if (
+                        not link["device1"] in all_devices
+                        or not link["device2"] in all_devices
+                    ):
+                        logging.debug("Link between none defined devices detected")
+                        return False
+                    if (
+                        not type(link["device1_port"]) == int
+                        or not type(link["device2_port"]) == int
+                    ):
+                        return False
+
+        return True


Test patch:
diff --git a/contrib/p4/docker_files/ptf_tester/.dockerignore b/contrib/p4/docker_files/ptf_tester/.dockerignore
new file mode 100644
index 000000000..1c420b51f
--- /dev/null
+++ b/contrib/p4/docker_files/ptf_tester/.dockerignore
@@ -0,0 +1,7 @@
+# SPDX-FileCopyrightText: 2020-2021 CASTOR Software Research Centre
+# <https://www.castor.kth.se/>
+# SPDX-FileCopyrightText: 2020-2021 Johan Paulsson
+
+# SPDX-License-Identifier: Apache-2.0
+
+/tests/*
diff --git a/contrib/p4/docker_files/ptf_tester/Dockerfile b/contrib/p4/docker_files/ptf_tester/Dockerfile
new file mode 100644
index 000000000..e825b090f
--- /dev/null
+++ b/contrib/p4/docker_files/ptf_tester/Dockerfile
@@ -0,0 +1,30 @@
+# SPDX-FileCopyrightText: 2020-2021 CASTOR Software Research Centre
+# <https://www.castor.kth.se/>
+# SPDX-FileCopyrightText: 2020-2021 Johan Paulsson
+
+# SPDX-License-Identifier: Apache-2.0
+
+#This dockerfile defines the container responisble for executing the tests.
+#It uses the ptf test framework to do so
+
+FROM ubuntu:18.04
+
+RUN apt-get update
+RUN apt-get install -y python3
+RUN apt-get install -y python3-pip
+
+RUN apt-get install -y libnanomsg-dev
+RUN pip3 install nnpy
+RUN apt-get install -y git
+RUN apt install -y iproute2
+RUN apt-get install -y iputils-ping
+RUN pip3 install scapy
+
+#PTF install
+RUN git clone https://github.com/p4lang/ptf /usr/local/src/ptf
+
+RUN cd /usr/local/src/ptf && pip3 install .
+
+CMD tail -f /dev/null
+
+
diff --git a/contrib/p4/docker_files/ptf_tester/tests/test_no_fail.py b/contrib/p4/docker_files/ptf_tester/tests/test_no_fail.py
new file mode 100644
index 000000000..bd359e140
--- /dev/null
+++ b/contrib/p4/docker_files/ptf_tester/tests/test_no_fail.py
@@ -0,0 +1,140 @@
+# SPDX-FileCopyrightText: 2020-2021 CASTOR Software Research Centre
+# <https://www.castor.kth.se/>
+# SPDX-FileCopyrightText: 2020-2021 Johan Paulsson
+
+# SPDX-License-Identifier: Apache-2.0
+
+import os
+import ptf
+from ptf.base_tests import BaseTest
+from ptf import config
+import ptf.testutils as testutils
+
+TYPE_IPV4 = 0x0800
+
+
+class DataplaneBaseTest(BaseTest):
+    def __init__(self):
+        BaseTest.__init__(self)
+
+    def setUp(self):
+        self.dataplane = ptf.dataplane_instance
+        self.dataplane.flush()
+        if config["log_dir"] is not None:
+            filename = os.path.join(config["log_dir"], str(self)) + ".pcap"
+            self.dataplane.start_pcap(filename)
+
+    def tearDown(self):
+        if config["log_dir"] is not None:
+            self.dataplane.stop_pcap()
+
+
+class IPV4OneSwitchTest(DataplaneBaseTest):
+    def __init__(self):
+        DataplaneBaseTest.__init__(self)
+
+    def runTest(self):
+        pkt = testutils.simple_tcp_packet(
+            pktlen=100,
+            ip_dst="192.168.1.2",
+            ip_ttl=64,
+            eth_dst="00:01:02:03:04:05",
+            eth_src="00:01:02:03:04:05",
+        )
+        pkt_expected = testutils.simple_tcp_packet(
+            pktlen=100,
+            ip_dst="192.168.1.2",
+            ip_ttl=63,
+            eth_dst="00:01:02:03:04:05",
+            eth_src="00:01:02:03:04:05",
+        )
+        try:
+            testutils.send_packet(self, (0, 0), pkt)
+        except Exception as e:
+            print(e)
+
+        testutils.verify_packet(self, pkt_expected, (1, 0))
+
+
+class IPV4OneSwitchTest2(DataplaneBaseTest):
+    def __init__(self):
+        DataplaneBaseTest.__init__(self)
+
+    def runTest(self):
+        pkt = testutils.simple_tcp_packet(
+            pktlen=100,
+            ip_dst="192.168.1.3",
+            ip_ttl=64,
+            eth_dst="00:01:02:03:04:05",
+            eth_src="00:01:02:03:04:05",
+        )
+        pkt_expected = testutils.simple_tcp_packet(
+            pktlen=100,
+            ip_dst="192.168.1.3",
+            ip_ttl=63,
+            eth_dst="00:01:02:03:04:05",
+            eth_src="00:01:02:03:04:05",
+        )
+        try:
+            testutils.send_packet(self, (1, 1), pkt)
+        except Exception as e:
+            print(e)
+
+        testutils.verify_packet(self, pkt_expected, (2, 0))
+
+
+class Ipv4TwoSwitchesTest(DataplaneBaseTest):
+    def __init__(self):
+        DataplaneBaseTest.__init__(self)
+
+    def runTest(self):
+        pkt = testutils.simple_tcp_packet(
+            pktlen=100,
+            ip_dst="192.168.1.4",
+            ip_ttl=64,
+            eth_dst="00:01:02:03:04:05",
+            eth_src="00:01:02:03:04:05",
+        )
+        pkt_expected = testutils.simple_tcp_packet(
+            pktlen=100,
+            ip_dst="192.168.1.4",
+            ip_ttl=62,
+            eth_dst="00:01:02:03:04:05",
+            eth_src="00:01:02:03:04:05",
+        )
+
+        testutils.send_packet(self, (0, 0), pkt)
+
+        testutils.verify_packet(self, pkt_expected, (3, 0))
+
+
+class LongChainOfSwitches(DataplaneBaseTest):
+    def __init__(self):
+        DataplaneBaseTest.__init__(self)
+
+    def runTest(self):
+        pkt = testutils.simple_tcp_packet(
+            pktlen=100,
+            ip_dst="192.168.1.16",
+            ip_ttl=64,
+            eth_dst="00:01:02:03:04:05",
+            eth_src="00:01:02:03:04:05",
+        )
+
+        pkt_expected = testutils.simple_tcp_packet(
+            pktlen=100,
+            ip_dst="192.168.1.16",
+            ip_ttl=58,
+            eth_dst="00:01:02:03:04:05",
+            eth_src="00:01:02:03:04:05",
+        )
+
+        testutils.send_packet(self, (0, 0), pkt)
+
+        testutils.verify_packet(self, pkt_expected, (15, 0))
+
+
+def log_to_file(msg):
+    f = open("/app/app.log", "w")
+    f.write(msg + "\n")
+    f.close()
diff --git a/contrib/p4/docker_files/ptf_tester/tests/test_with_fail.py b/contrib/p4/docker_files/ptf_tester/tests/test_with_fail.py
new file mode 100644
index 000000000..fd3584464
--- /dev/null
+++ b/contrib/p4/docker_files/ptf_tester/tests/test_with_fail.py
@@ -0,0 +1,48 @@
+# SPDX-FileCopyrightText: 2020-2021 CASTOR Software Research Centre
+# <https://www.castor.kth.se/>
+# SPDX-FileCopyrightText: 2020-2021 Johan Paulsson
+
+# SPDX-License-Identifier: Apache-2.0
+
+import os
+import ptf
+from ptf.base_tests import BaseTest
+from ptf import config
+import ptf.testutils as testutils
+
+
+class DataplaneBaseTest(BaseTest):
+    def __init__(self):
+        BaseTest.__init__(self)
+
+    def setUp(self):
+        self.dataplane = ptf.dataplane_instance
+        self.dataplane.flush()
+        if config["log_dir"] is not None:
+            filename = os.path.join(config["log_dir"], str(self)) + ".pcap"
+            self.dataplane.start_pcap(filename)
+
+    def tearDown(self):
+        if config["log_dir"] is not None:
+            self.dataplane.stop_pcap()
+
+
+class OneTest(DataplaneBaseTest):
+    def __init__(self):
+        DataplaneBaseTest.__init__(self)
+
+    def runTest(self):
+        pkt = testutils.simple_tcp_packet(pktlen=100)
+
+        try:
+            testutils.send_packet(self, (0, 1), pkt)
+        except Exception as e:
+            print(e)
+
+        testutils.verify_packet(self, pkt, (1, 1))
+
+
+def log_to_file(msg):
+    f = open("/app/app.log", "a")
+    f.write(msg + "\n")
+    f.close()
diff --git a/contrib/p4/p4-test-tool.py b/contrib/p4/p4-test-tool.py
new file mode 100644
index 000000000..9150b1374
--- /dev/null
+++ b/contrib/p4/p4-test-tool.py
@@ -0,0 +1,26 @@
+# This file is part of BenchExec, a framework for reliable benchmarking:
+# https://github.com/sosy-lab/benchexec
+#
+# SPDX-FileCopyrightText: 2007-2020 Dirk Beyer <https://www.sosy-lab.org>
+#
+# SPDX-License-Identifier: Apache-2.0
+
+import benchexec.tools.template
+
+
+class Tool(benchexec.tools.template.BaseTool2):
+
+    # Needed for benchexec to run, but irrelevant for p4 extension
+    def executable(self, tool):
+        return "/"
+
+    def name(self):
+        return "P4 Test"
+
+    def determine_result(self, run):
+        # Traverse through the output and check for and Ok
+        for line in run.output:
+            if "OK" in line:
+                return benchexec.result.RESULT_CLASS_TRUE
+        else:
+            return benchexec.result.RESULT_CLASS_FALSE
diff --git a/contrib/p4/test_input/expected_results.json b/contrib/p4/test_input/expected_results.json
new file mode 100644
index 000000000..462fde5c9
--- /dev/null
+++ b/contrib/p4/test_input/expected_results.json
@@ -0,0 +1,8 @@
+{
+    "FileName_1.TestName1": "False",
+    "FileName_1.TestName2": "True",
+    "FileName_2.TestName1": "False",
+    "test_with_fail.TwoTest": "false",
+    "test_with_fail.SimpleUdpPacket": "false",
+    "test_with_fail.OneTest": "False"
+}
\ No newline at end of file
diff --git a/contrib/p4/test_input/expected_results.json.license b/contrib/p4/test_input/expected_results.json.license
new file mode 100644
index 000000000..c7317e172
--- /dev/null
+++ b/contrib/p4/test_input/expected_results.json.license
@@ -0,0 +1,5 @@
+# SPDX-FileCopyrightText: 2020-2021 CASTOR Software Research Centre
+# <https://www.castor.kth.se/>
+# SPDX-FileCopyrightText: 2020-2021 Johan Paulsson
+
+# SPDX-License-Identifier: Apache-2.0
\ No newline at end of file
diff --git a/contrib/p4/test_input/simpleP4Test.xml b/contrib/p4/test_input/simpleP4Test.xml
new file mode 100644
index 000000000..7670c4ce7
--- /dev/null
+++ b/contrib/p4/test_input/simpleP4Test.xml
@@ -0,0 +1,21 @@
+<?xml version="1.0"?>
+<!-- SPDX-FileCopyrightText: 2020-2021 CASTOR Software Research Centre
+<https://www.castor.kth.se/>
+SPDX-FileCopyrightText: 2020-2021 Johan Paulsson
+
+SPDX-License-Identifier: Apache-2.0 -->
+
+<!DOCTYPE benchmark PUBLIC "+//IDN sosy-lab.org//DTD BenchExec benchmark 2.3//EN" "https://www.sosy-lab.org/benchexec/benchmark-2.3.dtd">
+
+<benchmark tool="p4-test-tool" displayName="P4 test">
+    <option name="switch_folder">~/benchexec/contrib/p4/docker_files/switch_bmv2</option>
+    <option name="ptf_test_folder">~/benchexec/contrib/p4/docker_files/ptf_tester/tests</option>
+    <option name="network_config">~/benchexec/contrib/p4/network_config.json</option>
+
+    <rundefinition>
+        <tasks>
+            <!-- <withoutfile>Run 1</withoutfile> -->
+            <include>expected_results.json</include>
+        </tasks>
+    </rundefinition>
+</benchmark>