This is the last exercise of our Basic L2 Switch
series. In the first exercise we implemented a very
basic l2 forwarding switch, then in the second exercise we made the switch a bit more realistic and added
the feature of forwarding packets for unknown and broadcast destinations.
In this exercise we will add the cherry on the cake. We will make the switch a bit smarter and add the
capability of learning Mac addresses to port mappings autonomously, as a regular L2 switch would do. Thus,
we will not need to add manually the mac_address
to output_port
mapping as we were doing in the previous
exercises. Instead, now we will leave that table empty, and will let the switch (with the help of a controller)
fill it automatically.
L2 learning works as follows:
-
For every packet the switch receives, it checks if it has seen the
src_mac
address before. If its a new mac address, it sends to the controller a tuple with (mac_address, ingress_port). The controller receives the packet and adds two rules into the switch's tables. First it tells the switch thatsrc_mac
is known. Then, in another table it adds an entry to map the mac address to a port (this table would be the same we used in the previous exercises). -
The switch also checks if the
dst_mac
is known (using a normal forwarding table), if known the switch forwards the packet normally, otherwise it broadcasts it. This second part of the algorithm has been already implemented in the previous exercise.
In this exercise we will implement a learning switch. For that we need a controller code, and instruct the switch to send the (mac, port) tuple to the controller. For the sake of learning, we will show you two different ways of sending packets to the controller.
For this exercise the files we provide you are:
-
p4app_cpu.json
andp4app_digest.json
: two p4 utils configuration files. They define the same star topology used in previous exercises.p4app_cpu.json
has an extra option enabled,cpu_port
which tells P4 utils to add an extra port to connect with the controller. -
p4src/l2_learning_copy_to_cpu.p4
andp4src/l2_learning_digest.p4
: p4 program skeletons. Each skeleton will be used for one version of the solution. -
l2_learning_controller.py
: since there is not much documentation on how to write controllers with P4 utils, we provide you some useful skeleton that you just have to fill with the l2 algorithm.
Unlike in previous exercises, for this exercise we do not need the automatic ARP table population at each host. Actually, during this exercise we want to disable this feature. Once our switches get the feature of broadcasting packets ARP requests will be sent everywhere and thus ARP tables will be filled without any problem.
To disable automatic ARP population we added the following line to the topology
section of the p4app.json
:
"auto_arp_tables": false
Note that p4app_cpu.json
adds a cpu_port: true
option to s1
. This will make P4 utils add an extra port to s1
, this port then will be used
by the controller to receive control plane packets.
Note: This option is already disabled in the provided configuration files.
Furthermore, during this exercise you will need to use the --conf
option when calling p4run
. By default, if you do not specify
anything it tries to find a configuration file named p4app.json
, which has to be located in the same path. Since in this exercise we
provided you with two different configuration files you will have call it as follows:
sudo p4run --conf <json conf file>
You can find all the documentation about p4app.json
in the p4-utils
documentation.
For this exercise we will also use two different techniques, as described above. Since the learning switch also need to
flood unknown packets you will be able to reuse code from the previous exercise (however, all the step will be listed in the
TODOS
).
To complete this exercise we will need to clone packets. When a learning packet needs to be sent to the controller the switch
will have to make a copy of the packet, send it to the controller, and then continue the pipeline normally with the original packet.
In order to help you with the cloning part, a section in the new Simple Switch
documentation explains how to clone packets
using the simple switch target.
Your tasks are:
-
Read the documentation section that talks about packet cloning.
-
Define a
cpu_t
header that will be added to our original packet. This header needs two fields, one for the source mac address, and one for the input port (48 and 16 bits respectively). Remember to cast thestandard_metadata.ingress_port
before assigning it to this header field. -
Cloned packets get all the metadata reset. If we want to be able to know the
ingress_port
for our cloned packet we will need to put that in a metadata field. -
Add the new header to the headers struct.
-
Define a normal forwarding table, and call it
dmac
. The table should match to the packet's destination mac address, and call a functionforward
that sets the output port. SetNoAction
as default. Copy this from the previous exercise. -
Define a table named
broadcast
that matches toingress_port
and calls the actionset_mcast_grp
which sets the multicast group for the packet, if needed. Define also theset_mcast_grp
action. Copy this from the previous exercise. -
Define a third and new table (and name it
smac
). This new table will be used to match source mac addresses. If there is a match nothing should happen, if there is a miss, an actionmac_learn
should be called. Themac_learn
action should set the metadata field you defined in 3 tostandard_metadata.ingress_port
and callclone3
withCloneType.I2E
and mirroring ID = 100. -
Write the apply logic. First apply the
smac
table. Then thedmac
and if it does not have a hit, apply thebroadcast
table. -
When you call
clone3
the packet gets copied to the egress pipeline. Here you have to do several things.- First check that the
instance_type
is equal to 1 (which means that the packet is an ingress clone). - Now you will use the
cpu
header you defined in 2 to add the learning information we want to send to the controller. To enable the header you need to set it valid usingsetValid()
. Fill thecpu
headers fields with the mac source port and ingress_port. - Finally set the
hdr.ethernet.etherType
to0x1234
. The controller uses to filter packets.
- First check that the
-
Emit the new header you created (only valid headers are put back to the packet).
-
Implement the controllers learning function:
At this point you will have your P4 program ready. Now is time to implement the controller. However, since we did not explain how to write controller code using the P4 utils library we will provide you an almost complete solution. You will only need to implement a function called
learn
. The controller will handle automatically for you the following:- Reset the switch state.
- Add Broadcast groups automatically.
- Add the mirror session ID and map it to the
CPU_PORT
. - It will listen for
learning
packets and will parse them.
Look for the
learn
function in the controller programl2_learning_controller.py
. The function learn will be automatically called by the controller when the switch sends a packet to it. As a parameter it receives a list of tuples with (src_macs, ingress_ports). Use the methodself.controller.table_add()
to populate thesmac
anddmac
tables accordingly. Thetable_add
does the same than the CLItable_add
command line.
Once you have the l2_learning_copy_to_cpu.p4
program finished you can test its behaviour:
-
Start the topology (this will also compile and load the program).
sudo p4run --conf p4app_cpu.json
-
Start the controller in another terminal window:
sudo python l2_learning_controller.py s1 cpu
We tell the controller from which switch listen from. The
cpu
parameter tells the controller which technique it should use to receive packets. In this case, sniffing an ethernet port. -
Ping between all hosts using the cli, and check that you have complete connectivity:
*** Starting CLI: mininet> pingall *** Ping: testing ping reachability h1 -> h2 h3 h4 h2 -> h1 h3 h4 h3 -> h1 h2 h4 h4 -> h1 h2 h3 *** Results: 0% dropped (12/12 received) mininet>
-
Verify that the switch table was populated:
simple_switch_CLI --thrift-port 9090 Obtaining JSON from switch... Done Control utility for runtime P4 table manipulation RuntimeCmd: table_dump dmac
Now we will solve the same exercise but using the extern
digest. To do this you should be able to reuse a big part of the copy_to_cpu
solution.
Your tasks are:
-
Read the simple switch digest documentation section.
-
Define a
struct
variable with a mac source address and ingress_port fields (48 and 16 bits respectively). Originally theingress_port
field is 9 bits, you will have to cast it to 16 bit so the controller can decode it. -
Add the newly created struct into the metadata fields.
-
Copy the ingress pipeline from the previous exercise. This time you only need to modify the
mac_learn
action. Now instead of cloning the packet you need to save the (src_mac_address, ingress_port) into the metadata struct you created in 2. And then digest the packet, as explained in the documentation. -
Copy the ingress control logic from the previous exercise.
Once you have the l2_learning_digest.p4
program finished you can test its behaviour:
-
Start the topology (this will also compile and load the program).
sudo p4run --conf p4app_digest.json
-
Start the controller in another terminal window:
sudo python l2_learning_controller.py s1 digest
We tell the controller from which switch listen from. The
digest
parameter tells the controller which technique it should use to receive packets. In this case it will use thenanomsg
socket to receive digests.
From now you can do the same than in the previous section.
Even though we provide you with the controller code almost finished, you should make the effort and try to understand it. This controller code has some good examples of how to receive CPU packets, how to receive digested packets, how to populate tables, how to automatically add the multicast groups, etc.
The implementation of the controller heavily uses two features from p4-utils
. The Topology
object and the SimpleSwitchAPI
object:
-
Topology object: when a topology is created with
p4run
all the useful topology information is encoded in a thetopolog.db
file which can be then loaded by the p4-utilsTopology
object. Using this object you can get rich information about the topology. To check when is this object used in the controller's code look forself.topology
. Documentation regarding this will be released soon. -
SimpleSwitchAPI: the simple switch API is python object that connects to a switch
thrift
server and provides you a Python API that is able to do the same than thesimple_switch_CLI
. However, the advantage here is that you can dynamically read, add and modify entries in the switch without having to use a CLI.
If you managed to finish the above exercises, now you can test your solution with bigger networks (they must not contain loops). Build a topology with several switches and run a controller for all of them, you should also be able to ping amongst all hosts.
We have added a small guideline in the documentation section. Use it as a reference when things do not work as expected.