Changelog:

  • 17 Sep 2024: update skeleton code in git re: calling myutil.py decode_packet_in_metadata function with wrong number of arguments, and add note to about manual change for this below
  • 26 Sep 2024: update skeleton code in git re: decode_packet_in_metadata being a buggy version
  • 26 Sep 2024: more detailed desription re: retrieving information from packets copied to the controller; add hint for verification step
  • 27 Sep 2024: add note about extra messages during network setup
  • 1 Oct 2024: correct tutorials/cs4457_f24 to tutorials/exercises/cs4457_f24
  • 2 Oct 2024: ARM64 VM instructions link
  • 2 Oct 2024: note print_counters.py update
  • 5 Oct 2024: split out advice about using wireshark to separate hints section and refer to it from all the steps, not just the last one. Also note that if checking print_counters.py, there will be packets from autoconfiguraiton, etc. counted there too in step 1 instructions.
  • 7 Oct 2024: note that step 1 is intended to be done main() (not something that won’t consistently run)

1 Your Task

1.1 Part 1

  1. Download the supplied VM image for Virtualbox (x86-64) at https://virginia.box.com/s/vl6jdfo27jooxbb07i3setll1l7tac14

    (or:

    )

  2. Run the VM and login (username/password p4/p4 for the Virtualbox image)

  3. Go to the tutorials/exercises/cs4457_f24 directory.

    Run git pull to make sure you have the latest version of the skeleton code.

  4. Following the instructions below, setup a simulated network with four simulated hosts (h1, h2, h3, and h4) one simulated switch (s1) running a data plane specified in mydataplane.p4 and a control plane implemented in mycontroller.py.

    To do this, as described below, you’ll run make to start the network emulator mininet. This will give you a prompt allowing you to run commands on each of the hosts. In another terminal, run python3 mycontroller.py to run the controller for the switch s1.

    Verify that the initial implementation:

    • does not allow hosts to be reachable from each other until the controller is run to initialize the switch (using commands like h1 ping h2 or pingall or iperfudp 1M h1 h2 in mininet);
    • allows all hosts to be reachable from each other when the switch is initialize
    • broadcasts all frames sent by any simulated host to all other simulated hosts. This means:
      • running the command python3 print_counters.py in a separate terminal will show a count of the number of packets and bytes sent to each of the switch’s output ports. You should see that ports 1 through 4 all have the same number of packets/bytes output, no matter what commands you run.
      • the packet traces in pcaps/s1-ethX_out.pcap will show what the switch outputted to each port. Examined with wireshark or with a command like tcpdump -er pcaps/s1-ethX_out.pcap, they should show that each port received the same data.
  5. There is no submission for part 1, but if your VM setup is not working, then you will not be ready to do part 2.

1.2 Part 2

  1. The initially supplied switch implementation sends all packets to all ports, rather than directing packets to a particular port based on their destination MAC address.

    Modify it so that it does a simple form of MAC learning:

    • The controller should learn the association between MAC addresses and switch ports.
    • Based on this association, the controller should setup tables in the dataplane to route packets to each MAc address to the appropriate port instead of having them broadcast to all ports.

    (A better version of this functionality would implement ARP (address resolution protocol), but that is not expected for this assignment.)

    As part of your implementation, you will need to arrange for some packets to be sent to the controller. You should arrange for packets to only be sent to the controller if the dataplane cannot route them to the correct port.

    We recommend following the instructions below under Part 2 recommendation, but this is not required.

  2. Verify that in your implementation:

    • packets are directed to the correct port rather than being broadcast (without the MAC addresses being hard-coded in the switch in advance)

    • once the controller learns about a host, few packets from the host are sent to the controller (most packets are handled exclusively by the data plane fast path of the switch)

    Describe how you verified your implementation in a file called results.txt or results.pdf. Include any relevant output.

  3. Submit your modified mycontroller.py, mydataplane.p4 and results.txt or results.pdf.

    (Note that this assignment will be manually graded.)

2 Part 2 recommendation

2.1 Step 1: Setting a fixed table entry

  1. Modify mycontroller.py to make the following call in main() to write_or_overwrite_table_entry to make it so packets destined to 08:00:00:00:01:01 (h1’s MAC address in topology.json) are sent to port 1:

    write_or_overwrite_table_entry(
        p4info_helper=p4info_helper,
        switch=s1,
        table_name='MyIngress.mac_dst_lpm',
        match_fields={
            # first 48 bits match this MAC address
            'hdr.ethernet.dstAddr': ('08:00:00:00:01:01', 48),
        },
        action_name='MyIngress.forward_to_port',
        action_params={
            'port': 1,
        }
    )

Verify that this change causes packets destined for h1 not to be duplicated on other ports but should still leave h1 reachable.

I would suggest examining the pcaps files as described below under the heading Examining pcaps to do this. Alternately, you can send pings and see how it changes the values from print_counters.py, but note that its counts includes autoconfiguration packets, not just your pings and the like.

In later steps rather than hard-coding the MAC address of h1 and its corresponding port, you will infer that information from packets the switch receives.

2.2 Step 2: make packets be sent to the controller

  1. Modify mydataplane.p4 to uncomment the call copy_to_controller() before mac_dst_lpm.apply().

    [Edited 17 Sep 2024]: Make sure the process_packet function contains the fixed code (which it should if you ran git pull since 17 Sep 2024):

    metadata = decode_packet_in_metadata(
        p4info_helper,
        switch,
        packet.metadata
    )

    and not:

    metadata = decode_packet_in_metadata(packet.metadata)

    [Edited 26 Sep 2024]: And make sure myutil.py is the latest version from git (which it should be if you ran git pull since 26 Sep 2024, or you can download from here)

    After doing this (and restarting the network simulation), mycontroller.py should start outputting information about every packet sent when you run commands in mininet. This output is produced by the process_packet function that you will modify later.

    (You might see some extra messages when mininet is getting setup from IPv6 autoconfiguration. These messages can be sent before mininet finishes configuring the MAC addresses in the hosts in the simulated topology, so they may use MAC addresses that aren’t part of the configured topology.)

    If you run a command like h1 ping h2, you should see ICMP echo request and ICMP echo reply messages.

2.3 Step 3: Set mac_dst_lpm entries dynamically

  1. Modify process_packet in mycontroller.py to add entries similar to the ones we added manually above for h1 dynamically. Rather than hard-coding any MAC addresses and port numbers, you should look at each transmitted packet that comes from a MAC address not yet handled and add entries to the tables to handle forwarding for that MAC address.

    You can use frame.src and frame.dst to retrieve the MAC addresses sent in the incoming frames.

    You can use metadata['inPort'][0] to retrieve the input port number of the packet.

    Verify that your solution makes it so that, after each host sends some traffic (such as because you ran pingall), each host does only receives packets destined for its own MAC address (or packets sent to a broadcast/multicast MAC address).

    (Like with step 1, I would suggest examining the pcaps files to do this.)

2.4 Step 4: Limit which packets are sent to the controller

  1. Add a new table mac_src_lpm or mac_port_src_lpm, which is like mac_dst_lpm, but:

    • matches based on the source mac address and (optionally) input port instead of the destination mac address
    • has a default action of copy_to_controller and another possible action of NoAction
  2. Replace the call to copy_to_controller() with an applicatoin of this table. Since the default action in the table is copy_to_controller, this will initially have the same effect

  3. Modify process_packet to:

    • add a table entry to new table to override the default copy_to_controller setting to NoAction, so the switch does not receive future packets for that source MAC address and (optionally) port, and
    • (optional) if a machine changes from one port to another, to overwrite or delete any table mac_port_src_lpm entry added for the prior port. This will ensure that if the machine changes back to the original port, then the controller will handle that operation.

2.5 Step 5: Verification

  1. One way to verify that the correct behavior is happening is to run a bunch of hX ping hY commands (where hX and hY would be h1 through h4) and examine the packet traces in the pcaps directory as described below under the heading Examining pcaps. Note that each of these packet traces represent a single direction on a link.

3 Hints

3.1 Examining pcaps

  1. As mentioned above the simulation produces a bunch of packet traces in the pcaps subdirectory of the tutorials/exercises/cs4457_f24 directory. Each one represents the packets sent in one direction from the switch.

    You can open these with wireshark:

    • type wireshark or wireshark & at the command line to open wireshark;

    • use File > Open to find the .pcap files in wireshark;

    • if you want to view traffic going in both directions to one of the ports, you can open the in file with File > Open, then open the out file with File > Merge. You should then examine both of the traces;

    You can also examine these from the command line with a command like:

    tcpdump -er pcaps/FILE.pcap

3.2 Understanding mydataplane.p4:

  1. mydataplane.p4: This contains an implementation of the data plane of a very simple switch:

    • Initially, frames are parsed in MyParser(), which extracts headers for use by later steps.

      In this stage, we also setup a special header (called hdr.packet_in in the code) that is used to send packets1 to the control plane for special processing. Corresponding code in MyEgress() will disable this header before sending out packets to destinations other than the control plane.

    • Then packets enter ingress processing MyIngress(). This stage does table lookups based on the destination MAC addresses of the packet. The tables can be configured by the control plane to choose one of four actions:

      • forwarding the packet to a particular output port;
      • broadcasting the packet to all (non-controller) output ports;
      • doing nothing

      There’s also an action which is initially not used called copy_to_controller() which sends a copy of the current packet to the controller. (In addition, the packet also undergoes normal processing.)

    • If a packet is sent on any output port (including to the controller), then MyEgress() runs. Currently this has logic which will:

      • avoid sending the control-plane information header to non-control-plane ports
      • increments counters to track how many bytes and packets are sent to each destination port
    • Before packets are sent out, MyDeparser() runs, which converts headers back into bytes.

3.3 Understanding mycontroller.py

  1. mycontroller.py which contains an implementation of the control plane.

    The code in the main() function:

    • connects to the switch
    • loads the P4 program into the switch (from a compiled file which will be produced by make)
    • configures the switch to support functionality to send packets to multiple destinations
    • runs a loop where it receives packets sent from the switch and sends them to the process_packet function
  2. Initially, the loop that receives packets from the switch won’t do anything because the cs4457_f24.p4 does not run the copy_to_controller action.

  3. The process_packet function decodes some metadata fields that are setup by cs4457_f24.p4. These are defined in the header packet_in_header_t in cs4457_f24.p4 and set in MyIngress (by modifying hdr.packet_in).

    To prevent the metadata fields from being output as part of packets sent to the simulated hosts, code in MyEgress marks this metadata as invalid except when the switch is outputting to the controler.

    The controller’s code in process_packet extracts the fields from the metadata into local variables for your convenience later. When the fields are sent to the controller, each metadata field is sent separately, but without only an index instead of a name, and without information about the size or format of the field. This means that if you modify the metadata fields in cs4457_f24.p4 without corresponding modifications to mycontroller.py, mycontroller will read the wrong data.

3.4 Running the simulated network

  1. topology.json specifies the layout of the simulated network and how the hosts are configured.

    In this case, there will be one switch called s1 connected to four hosts h1, h2, h3, h4.

    The hosts are assigned MAC addresses and informed of the MAC addresses of all other hosts.

    Running make will compile the .p4 file and start a simulated network based on topology.json, and give you a prompt in the mininet tool.

    In the mininet tool you can:

    • Run a command FOO on a particular simulated host, like h1, using a command h1 FOO.

      In the command you run the names of other hosts will be replaced by their IP addresses.

      For example h1 ping h2 will run the command ping 10.0.1.2 (since 10.0.1.2 is the configured IP address of h2 in topology.json). The ping command sends echo requests (using the IP control protocol ICMP) to a particular host and prints out messages when it gets echo replies.

      The simulated hosts are implemented by running a normal program with its network configuration changed, so you can run arbitrary commands.

    • Get a terminal within a simulated host h1 using xterm h1

    • Test whether each host is reachable from each other with pingall

    • Send data from host h1 to host h2 using TCP using a command like iperf h1 h2 (named after the a well known networking testing tool called iperf)

    • Send data from host h1 to host h2 using UDP at 1Mbps using a commnad like iperfudp 1M h1 h2.

    (and more, see also the command help in mininet)

  2. We need to run our controller program to setup the emulated switch. Until this is done, none of the hosts will be able to contact each other.

    You can run python3 mycontroller.py in another terminal to do this.

    Note that exiting mininet or terminating the python3 program will stop the network.

  3. We provide a utility program called print_counters.py which will contact the emulated switch and retrieve counts of how many packets and bytes have been sent on each port.

    You can run it in a separate terminal using python3 print_counters.py. (Note that you will need a version from git on/after 2 October because it previously expected filenames in build/ that didn’t match mydataplane.p4.)

    The utility program uses the same code as mycontroller.py to connect to this switch, but does send any commands that would register it as the controller of the switch or any commands that would reconfigure the switch.


  1. really, layer-2 frames, but we will use the term packet since that is consistent with the P4 documentation.↩︎