.. _tap-tutorial: ################################### Tapping a Link Within an Experiment ################################### In this tutorial we will demonstrate how to effectively use the :ref:`layer2.tap_mc` Model Component to mirror traffic from one VM to another VM within an experiment. This could be useful in experiments where in-experiment traffic needs to be processed by some collector/analysis platform, such as `Zeek <https://zeek.org>`__, `Splunk <https://www.splunk.com>`__, or `Elastic Stack <https://www.elastic.co>`__. For our purposes, we will build off of the topology built in the :ref:`simple-server-tutorial` (the completed topology can be cloned from FIREWHEEL's git repository). If you haven't already cloned the :ref:`tutorials.simple_server_mc` Model Component into FIREWHEEL, do the following:: $ cd /opt/firewheel/model_components $ git clone ssh://<git server hostname>:firewheel/model_components/tutorials/simple-server.git ********************** Updating Simple Server ********************** The simple server tutorial will be updated here to use the :ref:`layer2.tap_mc` MC *after* the topology has been created. This Model Component walks the graph looking for :py:class:`Edges <firewheel.control.experiment_graph.Edge>` that have the ``tap`` attribute set. Specifically, the ``tap`` attribute on the :py:class:`Edge <firewheel.control.experiment_graph.Edge>` must specify a list of collectors that will receive the mirrored traffic. The list of of collectors can contain either the actual :py:class:`Vertex <firewheel.control.experiment_graph.Vertex>` object (recommended) or the name of the collector :py:class:`Vertex <firewheel.control.experiment_graph.Vertex>`. Generally, this "collector" of the mirrored traffic will be some sort of analysis platform such as `Zeek <https://zeek.org>`__ or `Splunk <https://www.splunk.com>`__, but for our purposes here we'll just use a :py:class:`Ubuntu1604Server <linux.ubuntu1604.Ubuntu1604Server>`. Tap a Link in the Topology ========================== First, we need to choose a link to tap in the topology. Let's assume that the traffic to the Server needs to be monitored. Then the topology would look like: .. image:: simple_server/images/simple_server_tap_topology.png :alt: Tapped Topology Open the :ref:`tutorials.simple_server_mc` ``plugin.py`` file to start editing the topology. In our case, we will first want to build a new "collector". First import the :py:class:`linux.ubuntu1604.Ubuntu1604Server` class at the top of ``plugin.py``: .. code-block:: python from linux.ubuntu1604 import Ubuntu1604Server Then, at the end of the ``run()`` method, add the following: .. code-block:: python collector = Vertex(self.g, name="Collector") collector.decorate(Ubuntu1604Server) Now that we have created a collector, we can add the ``tap`` attribute to the :py:class:`Edge <firewheel.control.experiment_graph.Edge>` between the Server and the Switch. In this example, we will add this attribute as soon as we build the connecting edge via the :py:class:`Edge <firewheel.control.experiment_graph.Edge>` object's :py:meth:`connect <base_objects.VMEndpoint.connect>` method. To add the tap to the edge, modify the statement: .. code-block:: python server.connect( switch, # The Switch Vertex server_ip, # The IP address for the server "255.255.255.0", # The subnet mask for the IP address network ) to look like: .. code-block:: python :emphasize-lines: 1 interface_name, edge = server.connect( switch, # The Switch Vertex server_ip, # The IP address for the server "255.255.255.0", # The subnet mask for the IP address network ) The :py:meth:`connect <base_objects.VMEndpoint.connect>` method will return a tuple with the interface name and the edge object (e.g. ``("eth0", <firewheel.control.experiment_graph.Edge>)``), and so we will grab the edge object and then set its ``tap`` attribute to point to the collector (i.e., the VM which will receive the mirrored traffic). .. code-block:: python # Build a collector vertex collector = Vertex(self.g, name="Collector") collector.decorate(Ubuntu1604Server) # Add the collector to the edge as a tap edge.tap = [collector] .. note:: You can also use the :py:meth:`find_vertex <firewheel.control.experiment_graph.ExperimentGraph.find_vertex>` and :py:meth:`find_edge <firewheel.control.experiment_graph.ExperimentGraph.find_edge>` methods in the experiment graph to help locate this :py:class:`Edge <firewheel.control.experiment_graph.Edge>`. For example: .. code-block:: python server = self.g.find_vertex("Server") switch = self.g.find_vertex("Switch") edge = self.g.find_edge(server, switch) **************************** Launching the New Experiment **************************** Because we want our :ref:`layer2.tap_mc` to run after our topology, we have a couple of options for launching our new experiment. The easiest approach is to simply add the :ref:`layer2.tap_mc` to the command line arguments when launching the experiment. With this approach, your new command will look like: .. code-block:: bash $ firewheel experiment -f tutorials.simple_server:10 layer2.tap minimega.launch Alternatively, there is a way of specifying a post-MC-execution dependency within the ``MANIFEST`` called *precedes*. That is, a *precedes* relationship indicates that a second MC must be installed and executed **AFTER** another MC. See :ref:`dependency_management` for more information on this relationship. To use the *precedes* relationship, we can open up the :ref:`tutorials.simple_server_mc` ``MANIFEST`` file and add the attribute ``tap`` to the attributes precedes block. .. code-block:: yaml :emphasize-lines: 4,5 attributes: depends: - graph precedes: - tap provides: - topology model_component_objects: model_component_objects.py model_components: depends: - linux.ubuntu1604 - base_objects precedes: [] name: tutorials.simple_server plugin: plugin.py vm_resources: - vm_resources/* Then when you re-launch the experiment, the :ref:`layer2.tap_mc` MC will automatically run after :ref:`tutorials.simple_server_mc`. Regardless of the method used to add the :ref:`layer2.tap_mc` to the experiment, the resulting output from the :ref:`helper_experiment` should look similar to: .. code-block:: bash :emphasize-lines: 10,11 Dependency Graph 0005.308 s ******************************************************************************** misc.blank_graph OK in 0000.002 s base_objects OK in 0000.017 s generic_vm_objects OK in 0000.000 s linux.base_objects OK in 0000.027 s linux.ubuntu OK in 0000.021 s linux.ubuntu1604 OK in 0000.012 s layer2.ovs OK in 0000.014 s tutorials.simple_server OK in 0004.563 s layer2.tap OK in 0000.005 s minimega.emulated_entities OK in 0000.002 s minimega.testbed_available OK in 0000.030 s vyos OK in 0000.033 s vyos.helium118 OK in 0000.014 s minimega.create_mac_addresses OK in 0000.003 s minimega.resolve_vm_images OK in 0000.014 s minimega.configure_ips OK in 0000.003 s minimega.send_miniweb_arp OK in 0000.001 s minimega.schedules_ready OK in 0000.000 s vm_resource.schedule OK in 0000.055 s vm_resource.validate OK in 0000.014 s minimega.parse_experiment_graph OK in 0004.013 s minimega.launch OK in 0000.001 s ******************************************************************************** Total time 0026.712 s ************************ Viewing Mirrored Traffic ************************ Once the experiment is running and configured, there should be an interface on the collector that follows the naming convention of ``tap<Integer>``. By default, the ``tap`` model component will start the integer portion of interface naming with the value ``1000``. This value actually represents a GRE tunnel ``key`` value. (See the :ref:`layer2.tap_mc` documentation for more information on the GRE tunnels and how the traffic is actually being mirrored.) At this point, we need to generate some traffic so that it can be mirrored. Let's ``ping`` from ``client-0`` to ``Server`` (IP address 1.0.0.1). We have connected to miniweb, and we have also connected to ``client-0``: .. image:: simple_server/images/connect_to_client0.png :alt: Miniweb connection Once you login, `ping <https://linux.die.net/man/8/ping>`_ the Server at ``1.0.0.1``. .. image:: simple_server/images/ping_from_client_to_server.png :alt: Pinging to Server Leave the ``ping`` running and connect to the collector. We can now look for the ``tap<X>`` interface:: $ ip address show .. image:: simple_server/images/connector_connection.png :alt: Connector VM You should see an interface named ``tap1000@NONE``. This is the interface that is receiving all the mirrored traffic from the tap. The traffic can be viewed with the `tcpdump <https://linux.die.net/man/8/tcpdump>`_ command:: $ sudo tcpdump -i tap1000 The ``ping`` traffic that was initiated from ``client-0`` should be visible. .. image:: simple_server/images/connector_tcpdump.png :alt: Connector tcpdump traffic Analysis Platforms ================== Most use cases will probably incorporate some kind of analysis platform like `Zeek <https://zeek.org>`__ or `Splunk <https://www.splunk.com>`__. Many of these analysis platforms have a way of specifying to which network interfaces they should attach. As mentioned above, all mirrored traffic shows up at a collector on interfaces that follow the ``tap<X>`` naming convention. Therefore, direct the analysis platform at all interfaces named ``tap*`` in order to ingest the mirrored traffic. ********************* Updated ``plugin.py`` ********************* .. code-block:: python import random from base_objects import Switch from tutorials.simple_server import SimpleClient, SimpleServer from linux.ubuntu1604 import Ubuntu1604Server from firewheel.control.experiment_graph import Vertex, AbstractPlugin class Plugin(AbstractPlugin): """tutorials.simple_server plugin documentation. This Plugin creates a basic topology with a Server and several clients. The clients all have a random delay on their outbound connection. """ def run(self, num_clients="1"): """Run method documentation. This method contains the primary logic for the Plugin. Arguments: num_clients (str): The number of clients in the topology. This should be convertible to an integer. """ try: # Convert the number of clients to an integer num_clients = int(num_clients) except (TypeError, ValueError): print("The number of clients has to be a valid integer.") raise # Create the Server server = Vertex(self.g, name="Server") server.decorate(SimpleServer) # Create the switch switch = Vertex(self.g, name="Switch") switch.decorate(Switch) # Connect the server and the switch server_ip = "1.0.0.1" edge_tup = server.connect( switch, # The Switch Vertex server_ip, # The IP address for the server "255.255.255.0", # The subnet mask for the IP address network ) # Create all of our clients for i in range(num_clients): client = self.create_client(f"client-{i}", server_ip) delay = random.randint(1, 100) # Connect the client and the switch client.connect( switch, # The Switch Vertex f"1.0.0.{i+2}", # The IP address for the client "255.255.255.0", # The subnet mask for the IP address network delay=f"{delay}ms", ) collector = Vertex(self.g, name="Collector") collector.decorate(Ubuntu1604Server) edge = edge_tup[1] edge.tap = collector def create_client(self, name, server_ip): """Create a single client. Arguments: name (str): The name of the vertex. server_ip (str): The IP Address of the Server. Returns: tutorials.simple_server.SimpleClient: The newly created client. """ client = Vertex(self.g, name=name) client.decorate(SimpleClient) client.grab_file(server_ip) return client