# Programmable Network Switches
## Lab2 - Using P4 and P4Runtime to Implement the Learning Switch Protocol
### Overview
In this lab, you will write P4 code to program the dataplane and utilize the P4 Runtime Shell package in Python to perform the controller's tasks.
### Environment
* VM Image (tested on VirtualBox): http://140.113.216.90:8000/p4_vm.ova
* Sample Code: http://140.113.216.90:8000/p4.zip
### Setup
1. Import the .ova file into VirtualBox and start the VM.
* You can use`p4/p4` for normal usage
* Or `vagrant/vagrant` for root permission
2. Download the sample code archive.
```shell
wget http://140.113.216.90:8000/p4.zip
```
3. Unzip the sample code into the VM and place it in `~/tutorials`. Overwrite if prompted. The folder should contain the following files:
* `exercises/`
* `learning_switch/`
* `disable_ipv6.sh`
* `learning_switch.p4`
* `Makefile`
* `mycontroller.py`
* `README.md`
* ...
* `utils/`
* `vm/`
* `vm-ubuntu-20.04/`
* `LICENSE`
* `P4_tutorial.pdf`
* `p4-cheat-sheet.pdf`
* `README.md`
* `run_controller.py`
3. Open a console and execute the following command:
```shell
pip3 install p4runtime-shell
```
### Topology
In this lab, you will construct a hierarchical tree topology as illustrated below:

The IP and MAC addresses of each host are listed as follows:
* h1: 10.0.0.1, 08:00:00:00:01:11
* h2: 10.0.0.2, 08:00:00:00:02:22
* h3: 10.0.0.3, 08:00:00:00:03:33
* h4: 10.0.0.4, 08:00:00:00:04:44
**Note: Since all topology setups have been completed for you, you don't need to perform any actions in this step. If you are interested in knowing how to set MAC, IP, ARP, and port configurations, please refer to `~/tutorials/learning_switch/sig-topo`.**
### Test
After setting up your VM, open two consoles and navigate to `~/tutorials/learning_switch/`. Run the following commands:
```shell
# terminal 1
make run
# terminal 2
python3 run_controller.py
```
Once you see
```
# terminal 1
...
To view the P4Runtime requests sent to the switch, check the
corresponding txt file in /home/p4/tutorials/exercises/learning_switch/logs:
for example run: cat /home/p4/tutorials/exercises/learning_switch/logs/s1-p4runtime-requests.txt
mininet>
```
You can run
```shell
mininet> pingall
```
This command tests ping reachability between all hosts, equivalent to individual pings between hosts (e.g., h1 ping h2, h1 ping h3, …).
You will see the result:
```shell
mininet> pingall
*** Ping: testing ping reachability
h1 -> X X X
h2 -> X X X
h3 -> X X X
h4 -> X X X
*** Results: 100% dropped (0/12 received)
```
You can use `Ctrl+C` to terminate the ping, and use `exit` to stop the Mininet.
The other console should display some messages like below:
```shell
$ python3 run_controller.py
[127.0.0.1:50051]: setup success!
[127.0.0.1:50052]: setup success!
[127.0.0.1:50053]: setup success!
[127.0.0.1:50051]: 08:00:00:00:01:11 08:00:00:00:02:22 0
[127.0.0.1:50051]: 08:00:00:00:01:11 08:00:00:00:03:33 0
[127.0.0.1:50051]: 08:00:00:00:01:11 08:00:00:00:04:44 0
[127.0.0.1:50051]: 08:00:00:00:02:22 08:00:00:00:01:11 0
[127.0.0.1:50051]: 08:00:00:00:02:22 08:00:00:00:03:33 0
[127.0.0.1:50051]: 08:00:00:00:02:22 08:00:00:00:04:44 0
[127.0.0.1:50052]: 08:00:00:00:03:33 08:00:00:00:01:11 0
[127.0.0.1:50052]: 08:00:00:00:03:33 08:00:00:00:02:22 0
[127.0.0.1:50052]: 08:00:00:00:03:33 08:00:00:00:04:44 0
[127.0.0.1:50052]: 08:00:00:00:04:44 08:00:00:00:01:11 0
[127.0.0.1:50052]: 08:00:00:00:04:44 08:00:00:00:02:22 0
[127.0.0.1:50052]: 08:00:00:00:04:44 08:00:00:00:03:33 0
```
The socket address `127.0.0.1:50051` corresponds to s1, `127.0.0.1:50052` is for s2, and `127.0.0.1:50053` is for s3.
Because the unmatched packet will be sent to the controller, and the controller currently only displays packet source, destination, and incoming port without any further functionality, the ping requests will not reach the destination side now. Additionally, the incoming port is reported as 0 because the switch is not providing this information to the controller. Your task is to modify the P4 code and the controller logic to enable the learning switch functionality.
The overall logic for the learning switch implementation involves the following steps:
1. **When receiving a packet:**
- Save the (source MAC address -> incoming port) mapping.
2. **If the destination MAC address is in the mapping:**
- Retrieve the corresponding port from the mapping.
- Install a forwarding rule based on both the destination MAC address and the source MAC address.
3. **If the destination MAC address is not in the mapping:**
- Perform flooding to ensure the packet is forwarded out of all ports except the incoming port. (Using the packet out header)
4. **Forward the packet:**
- Use the obtained port to forward the packet out. (The ingress port, egress port, and the decision of whether to flood or not should be communicated using the packet out header)
### Implementation Steps
* In `learning_switch.p4`
* TODO 1: After extracting the packet_out header from the switch packet, you should store the necessary fields into metadata.
* TODO 2: In the `send_to_cpu` action, some information should be placed into the packet_in header to inform the controller. Please fill in these details.
* TODO 3: The source MAC address and destination MAC address should be utilized in the table match.
* TODO 4: If the packet_out header is valid, it indicates that the packet is from the switch. In this case, you should retrieve the egress port decided by the controller and set it to flooding if the flooding flag is set.
* In `mycontroller.py`
* TODO 1: Write a mapping of (source MAC -> incoming port). Check if the destination MAC is in the mapping. If it is, set the out_port to that value; otherwise, set it to FLOODING.
* TODO 2: Select the table and action that will be used. Set the match field and action parameters. Then, insert the entry into the table.
* TODO 3: Build a packet to be sent out by unicast.
* TODO 4: Build a packet to be sent out by flooding.
### Submission
* `{student_id}_lab2.zip`
* `learning_switch.p4`
* `mycontroller.py`
### Tips
* Feel free to use the `self.log` method for debugging purposes. Its usage is the same as `print()` in Python, but it will add the switch address in front of the print message.
* Insert table entry:
```python
te = sh.TableEntry('MyIngress.some_table')(action="some_action")
te.match['hdr.ethernet.some_field'] = some_value
te.action['some_param'] = param
te.insert()
```
* Build packet out:
```python
packet_out = sh.PacketOut()
packet_out.payload = payload
packet_out.metadata['some_field'] = some_value
packet_out.send()
```
* If you encounter issues with Mininet not opening, execute the command `make clean`.
* The architecture used here is `v1model`. If you are looking for reference data, don't get it wrong!