You are provided with a fair amount of scaffolding code, some of which you will need to use or modify to implement your router. This page provides an overview of the files that you will need to modify or be familiar with. Most of the documentation on individual functions can be found in the header files themselves.
This header file defines the main data structures of the router. All these
data structures are accessible through the router context struct (
that is passed to most functions.
Our code takes care of creating and populating this struct with all the
information relevant to the router:
An array of
chirouter_interface_t structs, each representing an Ethernet
interface in the router.
An array of
chirouter_rtable_entry_t structs, each representing an entry
in the routing table. Each entry in the routing table, in turn, contains
the destination network (specified by an IPv4 address and a subnet mask), the
gateway for that entry, and the Ethernet interface for that entry (a pointer
Take into account that, in this project, you can ignore the gateway. i.e., you can assume that there is a single router in the network, and that its interfaces are directly connected to their destination networks.
An ARP cache, represented as an array of
A list of pending ARP requests, representing ARP requests that have been sent but for which the router has not received a reply yet. This is explained in more detail in Assignment: Implementing an IP router.
A mutex that must be locked any time the ARP cache or the list of pending ARP requests is accessed.
This header file also defines an
ethernet_frame_t struct representing an inbound
Ethernet frame. This struct contains not just the frame itself, but also a pointer to the
chirouter_interface_t struct corresponding to the Ethernet interface on which the
You should not modify this header file in any way.
Most of your work will take place in this file. In particular, you must implement the
chirouter_process_ethernet_frame function. This function will get called every
time an Ethernet frame is received by
the router. The router uses a single thread to process inbound Ethernet
frames, so you can assume that
chirouter_process_ethernet_frame is always
called sequentially (there will never be concurrent calls to this function).
Your implementation of the
chirouter_process_ethernet_frame function must
process the frame meeting the requirements described in Assignment: Implementing an IP router.
You are allowed, and encouraged, to use helper functions to implement
For example, it would make sense to write separate functions to handle ARP messages,
ICMP messages directed to the router, and IP datagrams.
Part of your work will take place in this file. In particular, you must implement the
chirouter_arp_process_pending_req function. Besides the router’s main thread (which
is in charge of calling
chirouter_process_ethernet_frame when an Ethernet frame
arrives), the router has an additional thread, the ARP thread, that runs function
This thread will wake up every second to purge stale entries in the ARP cache
(entries that are more than 15 seconds old) and to traverse the list of pending ARP requests.
For each pending request in the list, it will call
which must either re-send the pending ARP request or cancel the request and send
ICMP Host Unreachable messages in reply to all the withheld frames (this is
described in more detail in Assignment: Implementing an IP router.).
Because the main thread and the ARP thread may both need to access the ARP cache or the
list of pending ARP requests at the same time, you must always lock the
(in the router context) before accessing either the ARP cache or the list of pending ARP
requests (even if you are just reading them).
You must not modify any code in this file other than
However, this file does provide several functions to access and/or manipulate the
ARP cache and list of pending ARP requests, which you can use in your implementation.
Take into account that these functions assume that the
lock_arp mutex has already been
locked before the functions are called.
When an Ethernet frame arrives and is processed in
chirouter_process_ethernet_frame you will,
in many cases, have to send an Ethernet frame through one of the router’s interfaces.
This is done using the
chirouter_pox_send_frame function, defined in the
pox.h header file.
These header files (located in the
src/router/protocols directory) provide structs that
allow easy access to the values contained in Ethernet, ARP, ICMP, and IPv4 headers.
Given an inbound Ethernet frame (an
ethernet_frame_t struct), you can access the
different headers like this:
/* Accessing the Ethernet header */ ethhdr_t* hdr = (ethhdr_t*) frame->raw; /* Accessing the IP header */ iphdr_t* ip_hdr = (iphdr_t*) (frame->raw + sizeof(ethhdr_t)); /* Accessing an ARP message */ arp_packet_t* arp = (arp_packet_t*) (frame->raw + sizeof(ethhdr_t)); /* Accessing an ICMP message */ icmp_packet_t* icmp = (icmp_packet_t*) (frame->raw + sizeof(ethhdr_t) + sizeof(iphdr_t));
This module provides two useful functions: one to compute an IP or ICMP checksum, and one to
compare MAC addresses. If you need to add functions in your implementation that need to
be shared by
arp.c, you should add them to this module.
chirouter prints out detailed information to standard output using a
series of logging functions declared in
src/router/log.h. We encourage you
to use these logging functions instead of using
printf directly. More
specifically, you should use the printf-style
chilog() function to print
chilog(DEBUG, "Received Ethernet frame with unsupported Ethertype: %i)", ntohs(hdr->type));
chilog_icmp() functions to dump the contents of an Ethernet header,
ARP message, IP header, or ICMP message. For example:
int reply_len = sizeof(ethhdr_t) + sizeof(iphdr_t) + ICMP_HDR_SIZE + payload_len; uint8_t reply[reply_len]; memset(reply, 0, reply_len); ethhdr_t* reply_ether_hdr = (ethhdr_t*) reply; iphdr_t* reply_ip_hdr = (iphdr_t*) (reply + sizeof(ethhdr_t)); icmp_packet_t* reply_icmp = (icmp_packet_t*) (reply + sizeof(ethhdr_t) + sizeof(iphdr_t)); /* Set values in all the headers */ chilog(DEBUG, "Sending ICMP packet"); chilog_ip(DEBUG, reply_ip_hdr, LOG_OUTBOUND); chilog_icmp(DEBUG, reply_icmp, LOG_OUTBOUND);
The last parameter of these functions can be
to designate a message that is being received or sent, respectively (this
affects the formatting of the message in the log).
LOG_NO_DIRECTION can also
be used to indicate that the message is neither inbound nor outbound.
In all the functions, the first parameter is used to specify the log level:
CRITICAL: Used for critical errors for which the only solution is to exit the program.
ERROR: Used for non-critical errors, which may allow the program to continue running, but a specific part of it to fail (e.g., an individual socket).
WARNING: Used to indicate unexpected situation which, while not technically an error, could cause one.
INFO: Used to print general information about the state of the program.
DEBUG: Used to print detailed information about the state of the program.
TRACE: Used to print low-level information, such as function entry/exit points, dumps of entire data structures, etc.
The level of logging is controlled by the
-v argument when running
-vargument: Print only
-v: Also print
-vv: Also print
-vvv: Also print
We recommend running at the
-vv level, which will print all the inbound
Ethernet frames. The
-vvv contains much lower-level information that
the instructors may need to debug a specific issue, but which is typically
not relevant in most situations when implementing chirouter.