Assignment 3: Supporting ARP, ICMP, RIP in the Router
Released: 03/07/2024
Due: 03/28/2024
- Part 0: Setup
- Part 1: ICMP
- Part 2: ARP
- Part 3: RIP
- Submission
- Appendix: Table Insertion API for
controller.py
Part 0: Setup
Like Assignment 2, we will use the cs356-p4
profile in this assignment.
To get the skeleton code, clone the git repository and create a private repository as in A1. (Refer to A1 setup.) Make sure to copy the provided .gitignore
to your private repository and other files.
The task is to implement features on the Layer-3 router incrementally.
Important Note
- You should execute
$ bash setup.sh
upon every$ git clone
and$ git pull
to automate updating your change in thesrc
directory to thelabs
directory. Otherwise, you must copy and paste your code whenever you make changes. - Do NOT modify the provided skeleton code other than the commented as
PARTn_TODO
(e.g.,PART1_TODO
). In this assignment, a lot of skeleton code is doing tedious work on your behalf so you can focus on the core logic. You can just read the comments to understand the skeleton code. - Part 3 topology has six routers, so use tmux on your choice of terminal instead of XTerm terminal. Other than splitting a window to panes, you can create a new tmux window within a session by typing
Ctrl+b c
and navigate the windows usingCtrl+b p
andCtrl_b n
. (Don’t forget to pause after Ctrl+b) Please refer to here for more details on how to usetmux
.
Part 1: ICMP
Overview
Your tasks are implementing part of ICMP protocol support on src/l3_routing.p4
. The tasks are commented as PART1_TODO
in the p4 file. If you have done the setup correctly, your source code also should appear in the labs/part1_icmp/shared
directory. The changes on one file will be reflected in the other. The ARP and routing table are static in this part of the assignment. The Kathara lab for this part is located in labs/part1_icmp
. The virtual network topology is illustrated below.
Tasks
All the tasks are on the src/l3_routing.p4
file.
Task 1: If the arriving IPv4 packet’s TTL field is 1, send an ICMP Time Exceeded message to the sender of the packet.
- First, call
action send_ICMP_error
with typeICMP_TYPE_TIME_EXCEEDED
(11 or 0x0b) and code0
. - Then modify the source IP address of the packet to the IP address of the ingress port by applying the
icmp_ingress_port_ip
table.
Task 2: If the arriving IPv4 packet’s destination IP is one of the router’s IP, handle the packet.
- If the packet is an ICMP echo request packet, send an ICMP echo reply by calling
action send_ICMP_echo_reply
- Complete the
action send_ICMP_echo_reply
following the instructions on the skeleton code. You may refer to theaction send_ICMP_error
. - Else if the packet is either TCP or UDP packet, send an ICMP error using
send_ICMP_error
with typeICMP_TYPE_DEST_UNREACAHBLE
(3) and codeICMP_CODE_PORT_UNREACHABLE
(3).
Test your implementation
- Compile the P4 code and launch the P4 and controller program on the routers (
r[1-3]
).- All the necessary commands are provided as script files in the Kathara lab’s
shared
directory. - After starting the Kathara lab, compile the P4 code with
$ bash compile_p4.sh
on one of the routers (e.g.,r1
) after$ cd /shared
. - Then, on each router, launch the compiled P4 program with
$ bash run_router.sh
and the controller with$ bash r[1-3]_run_controller.sh
.
- All the necessary commands are provided as script files in the Kathara lab’s
- Test the functionality.
- For Task 1, try to run
$ traceroute 20.0.0.10
onh1
. It basically sends packets with small RTTs (incrementing by 1 until the packet reaches the destination) to examine the route to the destination. If you implement it correctly, you should be able to see the route to20.0.0.10
(h2
) fromh1
. - For Task 2, try to run
$ ping 10.0.0.1
onh1
(i.e., ping tor1
). It should work properly if you implement Task 2 correctly. Also, try to run$ wget 10.0.0.1
onh1
. It will try to send a TCP packet tor1
and should return an error immediately.
- For Task 1, try to run
Part 2: ARP
Overview
Your tasks are implementing part of ARP protocol support on src/l3_routing.p4
and src/controller.py
. The tasks are commented as PART2_TODO
in the p4
and python
files. If you have done the setup correctly, your source code also should appear in the labs/part2_arp/shared
directory. The changes on one file will be reflected in the other. The routing table is static in this part of the assignment. The Kathara lab for this part is located in labs/part2_icmp
. The virtual network topology is illustrated below.
Tasks
Task 1 is on the src/l3_routing.p4
file and Task 2 is on the src/controller.py
file.
Task 1: Complete the action send_ARP_response
following the instructions on the skeleton code.
This action will be invoked upon ARP request on one of the router’s network interfaces. It basically changes an incoming ARP request to an ARP reply and sends the reply to who sends the request.
Task 2: Complete the ARP reply handling following the instructions on the skeleton code.
Once the router receives the ARP reply, it forwards the reply to the controller. Then the controller should install the ARP table and forwarding table using the information in the ARP reply packet. Your job here is to insert the arp_table
and dmac_forward
table entries into the router data plane. It should be quite similar to what you’ve done in A2.
Test your implementation
- Compile the P4 code and launch the P4 and controller program on the routers (
r[1-3]
).- All the necessary commands are provided as script files in the Kathara lab’s
shared
directory. - After starting the Kathara lab, compile the P4 code with
$ bash compile_p4.sh
on one of the routers (e.g.,r1
) after$ cd /shared
. - Then, on each router, launch the compiled P4 program with
$ bash run_router.sh
and the controller with$ bash r[1-3]_run_controller.sh
.
- All the necessary commands are provided as script files in the Kathara lab’s
- After implementing both tasks, try running
$ ping 20.0.0.10
onh1
. It should work, and the ARP request and reply packets should appear along the path. You may check these with$tcpdump—i any arp
onr1
andr2
.
Part 3: RIP
Overview
Your tasks are implementing part of RIP protocol support on src/controller.py
. The tasks are commented as PART3_TODO
in the python
file. If you have done the setup correctly, your source code also should appear in the labs/part3_rip/shared
directory. The changes on one file will be reflected in the other. The routing table is static in this part of the assignment. The Kathara lab for this part is located in labs/part3_rip
. The virtual network topology is illustrated below.
Tasks
All the tasks are on the src/controller.py
file.
Task 1: Complete the mergeRoute
method of the Route
class. The Route
class stores routing information (i.e., next hop IP and cost). The information would be associated with a certain destination IP address and stored in the routing_table
dictionary. Refer to the skeleton code for detailed specifications.
Task 2: Complete the RIP response handling logic. Once the router receives an RIP response, it forwards the packet to the controller. Then, the controller should update or install the routing table entries if the routes in the RIP response are for unknown destinations or known but with cheaper costs. Your job is handling the routing table entry updates and installations. Please take a look at the skeleton code for more details. Other than the RIP-specific logic, it should be quite similar to what you’ve done in A2.
Notes for Task 2:
- When you build the table entry for
ipv4_route
(a LPM table), use a prefix length of 32 for thematch_fields
parameter ofp4info_helper.buildTableEntry
. In part 3, we don’t use the notion of a subnet for simplicity. - When you update an existing table entry, specify
is_modify=True
as the parameter ofs1.WriteTableEntry
.
Test your implementation
- Compile the P4 code and launch the P4 and controller program on the routers (
r[1-6]
). You may want to test your program on the subset of the routers to ease debugging.- All the necessary commands are provided as script files in the Kathara lab’s
shared
directory. - After starting the Kathara lab, compile the P4 code with
$ bash compile_p4.sh
on one of the routers (e.g.,r1
) after$ cd /shared
. - Then, on each router, launch the compiled P4 program with
$ bash run_router.sh
and the controller with$ bash r[1-6]_run_controller.sh
.
- All the necessary commands are provided as script files in the Kathara lab’s
- After implementing both tasks, try to ping to
h[2-6]
onh1
. It should work properly. You may verify the routing table information on each controller’s stdout. It will dump the routing table every 10 seconds.
Submission
Please submit your code (modified assignment3 repository) to the Canvas Assignments page in either tar.gz
or zip
format.
The naming format for the file is assign3_groupX.[tar.gz/zip]
.
Appendix: Table Insertion API for controller.py
- Create a table entry you want to insert.
In the skeleton code, thehelper
class instance,p4info_helper
, is already created and initialized. Its role is to interpret the human-readable format of P4 objects as the P4 program understands. To build a table entry in a way that the P4 program understands, you can use thehelper
class’sbuildTableEntry()
method. Its API is described below.def buildTableEntry(self, table_name, # human-readable table name in string match_fields=None, # a dictionary with a human-readable match field as a key and its value as a value default_action=False, # human-readable default action name in string (This is not necessary if you already define the default_action in the P4 code) action_name=None, # human-readable action name in a string (This action is executed upon hit) action_params=None, # a dictionary with a human-readable action parameter name as a key and its value as a value priority=None # unused in our case ):
For the value for the
match_fields
andaction_params
dictionaries, here’s a guideline to the format of the value:- IP address: String in the dotted decimal notation (e.g.,
10.0.0.1
) - IP prefix for longest prefix matching: a tuple with IP prefix in string and prefix length in integer. (e.g.,
(10.0.0.0, 24)
which means the table key is in the range10.0.0.[1-255]
) - MAC address: String in the
xx:xx:xx:xx:xx:xx
format (e.g.,00:00:0a:00:00:01
) - Port number: Integer
To set a timeout, you should change the attribute,
idle_timeout_ns
, of the table entry as below in integer and the unit of nanoseconds.table_entry.idle_timeout_ns = int(1 * 1e9) # timeout is 1 second
For more details, you can refer to the file,assignment2/labs/star_four_hosts/shared/utils/p4runtime_lib/helper.py
.
- IP address: String in the dotted decimal notation (e.g.,
- Send a table entry to the switch.
TheBmv2SwitchConnection
object,s1
, is provided and initialized. It is an abstraction of a connection between the switch and the controller. To add a table entry you built in the above step, you can simply call theWriteTableEntry
method of theBmv2SwitchConnectoin
object withtable_entry
as a parameter (e.g.,s1.WriteTableEntry(table_entry)
). When you update an existing table entry, specifyis_modify=True
as the parameter ofs1.WriteTableEntry
(e.g.,s1.WriteTableEntry(table_entry, is_modify=True)
).
Usage example
Say there’s a l2_simple_switch.p4
, which defines a table in the ingress control block, as below.
...
control MyIngress() {
table dmac_forward {
key = {
hdr.ethernet.dstAddr: exact;
}
actions = {
forward_to_port;
drop;
}
size = 4;
default_action = drop;
}
...
}
When you compile this program, the compiler will create l2_simple_switch.p4info.txt
. The P4info file contains a human-readable name to an instance id (integer) mapping. You can retrieve the arguments for the buildTableEntry
method from here. Below is the example code snippet from the controller code used in the in-class demo.
mac_to_port = {"00:00:0a:00:00:01":1,
"00:00:0a:00:00:02":2,
"00:00:0a:00:00:03":3,
"00:00:0a:00:00:04":4}
for eth_src_addr, port_id in mac_to_port.items():
table_entry = p4info_helper.buildTableEntry(
table_name="MyIngress.dmac_forward",
match_fields={"hdr.ethernet.dstAddr": eth_src_addr},
action_name="MyIngress.forward_to_port",
action_params={"egress_port": port_id}
)
s1.WriteTableEntry(table_entry)