Project 2: IP 🐦

Milestone due: Friday, October 4, 11:59pm EDT

Final submission due: Thursday, October 17, 11:59pm EDT

Looking for a stencil link? We will send you a stencil link via email when teams are assigned on Friday, September 27.

Introduction

In this assignment. you will be constructing a Virtual IP Network that implements a link layer, IP forwarding, and routing. Normally, these functions are provided by a host or router’s OS, drivers, and physical hardware–these make up that device’s “network stack”, ie. the set of software and hardware that implement the protocols and layer of abstraction that make these devices work. To demonstrate how these critical protocols work, and to give you practice thinking at different layers of abstraction, we will construct our own virtual networking stack, implemented entirely in our own programs and by sending packets over UDP sockets.

Beyond just teaching you about IP and routing, this project is designed to teach you how to build and work at different layers of abstraction—both in terms of networking and in software design. On the networking side, you practice with handling data encapsulated in IP packets. On the software side, you will build a library that will realize your “IP stack” and an API to use it across two different programs. In your next project, you will add to your networking stack by implementing TCP, getting closer to the set of functionality provided by an OS!

Warning: Do not leave this project until the last minute. This project can be tricky and a lot of it is about design. The best way to stay on top of it is to start working on your design early so that you have opportunities to ask questions.

At the start of the project, you will have a milestone design meeting with a TA to encourage you to plan your design–you should use this time wisely, so you can bring questions and get feedback during this meeting! Start talking with your partner as soon as you receive your team assignment, and get ready to get connected!

Collaboration

This is a 2-person assignment. To form a group, you should fill out the group assignment form, located here.

If you prefer to work with a specific person, you can indicate it on the form — both students on a team should fill out the form, and only mutual requests will be honored. Otherwise, we will match each student to a partner based on their language preferences, in-person/remote status, time zones, and so on.

Once the groups are set, you’ll be assigned a mentor TA to follow you in your design and grading meetings for this project and TCP. TCP will build on this project, so your effort on your IP design will pay off twice.

Capstone students: If you are using this class to fulfill your capstone requirement, you will need to complete one of two extra components. If both students are taking the class as a capstone, both capstone components must be completed.

Development Environment

You can continue to use the course container for development, which you can also use to run Wireshark. Wireshark will be incredibly helpful in both IP and TCP, as it allows you to observe network traffic in real-time to debug your work.

If you had issues using the course container environment during Snowcast, either for developing code or running Wireshark, please let us know on Edstem. We are happy to help debug any issues you may encounter — we want you to have a good workflow for development and testing!

Overview

Core Requirements

There are two main parts to this assignment:

  • IP forwarding: this includes handling for receiving packets, delivering them locally if appropriate, or looking up a next hop destination and forwarding them out to an appropriate interface. For this part, you will need to build and maintain a forwarding table that you will use to make decisions about each packet.
  • IP stack API: you will create an API to send and receive packets using your forwarding implementation that will provide the basis for implementing “higher-layer” protocols in this project, and your next project (TCP).
  • IP Routing with RIP: Routing is the process of exchanging information to populate the forwarding table. We will do this using RIP, a relatively simple routing protocol for small networks. We will discuss RIP in detail in the next few lectures, so it’s not expected you’ll know what this means when the project starts. For more info, see Routing with RIP.

You will implement two programs, vhost and vrouter, that will import your IP stack library and act as a host and router, respectively. For the most part, your host and router programs be very similar* and will behave mostly the same way, except that routers will implement RIP. This is intentional–we want you to see how much of the network layer is common across all IP nodes! In the next project, we’ll then build a lot more functionality in to our hosts by adding TCP.

The following sections contain an overview of how each part works and links to more specifications elsewhere on this site.

Note on navigation: We’re still trying to figure out the best way to organize all of the information here in a way that’s easy to navigate, especially as we add more resources. If you ever can’t find something, use the search box at the top of this page! This will search all pages on the site.

Capstone Requirements

Capstone students must implement at least one extra feature in their project. For details, see Capstone Requirements

Background: our virtual network

You will build our IP stack in a virtual network: instead of dealing with real hardware or drivers or the kernel, we’ll build our IP stack entirely in userspace programs that will make up our hosts and routers, and send packets between using UDP sockets. This creates a similar abstraction without the hassle.

The next few sections describe exactly what we mean by a “virtual” network. To start, here’s an example network topology we might want to create:

flowchart LR
    H1[H1] --- R1(R1)
	R1 --- R2(R2)
	R2 --- H2[H2] 
	R2 --- H3[H3] 

This network has 3 hosts and two routers. To run this network and others, we define the topology using configuration files.

Configuration files

We set up our network from a set of configuration files that define how the nodes connect to each other, what interfaces they have, and their IP addresses. There are two types of configuration files: network definition files, and lnx files.

Network definition files (network-name.json)

These files define the network topology, including how many nodes are in each network (number of hosts, number of routers, and how they are connected together.

Note that you don’t need to use these files directly, but you will run scripts that do—we provide a script, vnet_generate that reads these files and creates a unique config file for each node in the network called an lnx file. Your nodes will read these lnx files to determine their network settings. For examples of some networks we’ll use, see Sample networks.

Lnx files (<node name>.lnx)

This file defines the network settings for a host or router—you will read this file at startup to initialize your IP stack by creating interfaces, assigning IP addresses, and so on. You will use this to populate your node’s forwarding table, and (for routers) determine your initial configuration for RIP. For a full reference on what can be configured in an Lnx file, see the specification.

Important: You do not need to write a parser for the lnx files–we have made one for you. To import our parsers, look here.

Interfaces and IPs

Each node has one or more interfaces, which are defined by the lnx file passed in at startup. Our interfaces will be simulated using UDP sockets: each interface has its own UDP port where it can send/receive packets: sending a packet from this UDP port is equivalent to sending the packet from that interface.

For each interface, you can think of that node’s UDP port similar to a MAC address: a unique value (within our network) to identify that interface.

Notation: Similar to snowcast, all of our virtual interfaces will listen on the real host’s localhost interface (ie. 127.0.0.1). We typically write an interface’s address as 127.0.0.1:5555 where the UDP port is 5555.

Each node’s interfaces are defined by interface directive in its lnx file. For example here are r2’s interface definitions from the example above (we’ll unpack the rest in the following sections):

interface if0 10.1.0.2/24 127.0.0.1:5003 # to network r1-r2
neighbor 10.1.0.1 at 127.0.0.1:5002 via if0 # r1

interface if1 10.2.0.1/24 127.0.0.1:5004 # to network r2-hosts
neighbor 10.2.0.2 at 127.0.0.1:5005 via if1 # h2
neighbor 10.2.0.3 at 127.0.0.1:5006 via if1 # h3

This line says that r2 has two interfaces, if0 and if1, corresponding to the two links in the figure, which listen on UDP ports 5003 and 5004, respectively. For more info, see here.

Virtual IPs

Just like any real IP network interface, a virtual interface has a virtual IP address, netmask, and other settings for how to communicate with hosts on the local network. These IP addresses and networks do not route to the Internet–they exist solely within our virtual network.

Following the example above, r2 is a member of two virtual IP networks:

  • 10.1.0.0/24 with virtual IP 10.1.0.2 (shared with r1)
  • 10.2.0.0/24 with virtual IP 10.2.0.1 (shared with hosts h2 and h3)

Neighbors and local networks

In our virtual network, an interface is connected to one or more other nodes on a single IP subnet (eg. 10.1.0.0/24). All nodes on the same subnet can always communicate with each other, and always know each other’s IP addresses and “link-layer” UDP port information—this is provided in the node’s lnx file using the neighbor directive.

For example, for each of r2’s subnets, there is a neighbor directive to tell r2 about each other host on the network, as follows:

interface if0 10.1.0.2/24 127.0.0.1:5003 # to network r1-r2
neighbor 10.1.0.1 at 127.0.0.1:5002 via if0 # r1

interface if1 10.2.0.1/24 127.0.0.1:5004 # to network r2-hosts
neighbor 10.2.0.2 at 127.0.0.1:5005 via if1 # h2
neighbor 10.2.0.3 at 127.0.0.1:5006 via if1 # h3

This means that r2 (and any node: host or router) will always know how to reach its neighbors connected to the same subnet. For example, r2 always knows that there are two neighbors reachable from interface if1 (which has prefix 10.2.0.1/24):

  • 10.2.0.3 at 127.0.0.1:5005
  • 10.2.0.2 at 127.0.0.1:5006

This is a simplification from how it works IRL: we’ll learn in a couple of lectures how hosts need to discover each other’s MAC addresses to send packets on the link layer using the ARP protocol—we avoid needing a protocol like ARP by specifying all of this info ahead of time in the lnx file.

You can use this information when you define your interface: you may assume that you always know the UDP addresses and ports to send to your neighbors. From there, the challenge in this project is learning how to communicate between subnets.

What you will build

Your implementation will consist of two programs called vrouter and vhost to implement a router and host on our virtual network. However, most of your implementation will be your IP stack, which will be shared between the two programs: you will do most of your work in library (or module, package, etc—whatever the term for your language) that you will import into both programs.

The IP stack

The core of your IP stack will be a virtual “link layer” and “network layer”, which together make up a framework to send and receive IP packets over the virtual network. To do this, you will create a representation for a node’s virtual “interfaces” (actually UDP sockets) and an API for how the rest of your code will send and receive IP packets across the links.

The following figure shows an overall architecture for the major components (detailed below):

When your nodes start up, you will listen for packets on each interface and send them to your virtual network layer for processing—you will parse the packet, determine if it’s valid, and then decide what to do with it based on the forwarding table. Routers have multiple interfaces and can forward packets to another interface. In our network, hosts have only one interface and only send or receive packets.

From there, you can use your IP stack to send and receive packets over our virtual network. To do this in an extensible way, you will design and build an API for key functions for how higher layers will use your IP stack.

At this stage, we have two “higher layers”:

  • Test packets (both hosts and routers): A simple interface for sending packets from the command line on each host/router
  • RIP (routers only): Routers will communicate with each other to build a global view of all networks in the system and adapt to network changes.

We will build on this in our next project to implement TCP!

Command line interface: Both hosts and routers will have a small command line interface (defined here) send packets, gather information about your network stack, and enable/disable interfaces. We will use these commands to test your network.

The following sections describe some fundamental background on how our virtual network is structured and the major components you will implement.

IP-in-UDP encapsulation

You will use UDP as your link layer for this project. Each node will create an interface for every line in its links file - those interfaces will be implemented by a UDP socket. All of the virtual link layer frames it sends should be directly encapsulated as payloads of UDP packets that will be sent over these sockets. You must observe an Maximum Transfer Unit (MTU) of 1400 bytes; this means you must never send a UDP packet (link layer frame) larger than 1400 bytes.

Wait, what? Why are we putting IP packets inside UDP packets?

Normally, IP packets are wrapped in an Ethernet (link layer) frame before being sent across a physical medium. However, this requires a network driver and its functionality is implemented on the kernel level, which is not the purpose of this assignment.

Instead, we will use IP packets wrapped in UDP packets, which provides a virtual link layer abstraction that emulates link layer functionality without delving into specifics of Ethernet and the physical medium. On reliable networks (i.e. localhost), the connectionless/unreliability aspect of UDP is not a concern; thus, as an implementer, (de-)serializing packets to/from a UDP packet is the same interface as (de-)serializing to/from an Ethernet frame.

To reiterate: for this project, instead of encapsulating IP packets within Ethernet frames (which is what would actually happen in the real world, when using Ethernet as a link layer), you will encapsulate your IP packets within UDP packets. Sending these IP-in-UDP packets back and forth between the nodes in your test networks will simulate a link layer for this project and the next one (TCP).

IP Forwarding

In addition, you will design a network layer that sends and receives IP packets using your link layer. Overall, your network layer will read packets from your link layer, then decide what to do with the packet: deliver it locally delivery or forward it to the next hop destination.

Definition: Next hop destination — An adjacent node in the network through which you should send a packet so that the packet travels through the minimum number of “hops” required to reach its destination IP address.

In general, your IP forwarding process should follow the IPv4 protocol, as described in RFC791, but you are not required to implement all features of this standard: this section provides details what parts we can ignore. In general, when you receive a packet, your IP forwarding implementation must validate the packet’s checksum, inspect the destination address to determine how it should be processed and (if necessary) forward it to the appropriate next hop.

Longest-prefix matching

Your IP forwarding implementation must be able to perform longest prefix matching in order to handle cases when there are multiple matches for a destination address. However, you are NOT required to implement longest-prefix matching efficiently—a linear search over your forwarding table to collect matching entries is fine.

How would efficient longest-prefix matching work?

High-performance routers implement forwarding tables using fast, purpose-built hardware called TCAM (Ternary Content-Addressable Memory). TCAMs can check all forwarding table entries simultaneously, in constant time–this is extremely important for high-performance routers, which can forward packets at many terabits per second.

An efficient way to do forwarding table lookups in software is to build a prefix trie, which organizes prefixes in a tree-like structure, allowing faster searches. You are NOT required to implement a prefix trie in your project, though you certainly can do so if you want. For more info, see this section of the Dordal textbook.

For some extra reading on how the Linux kernel does forwarding table lookups, see here and here. (This is much more than we need for the project, though!)

IP header format

Your IP packets must use the standard IPv4 header format, described in Section 3.1 of RFC791. When forwarding, you must decrement the packet’s TTL value and recompute its checksum. You can use a starting TTL value of at least 16, which is more than sufficient for all the networks we will test in this assignment.

You do not need to manually define a struct or write serialization/deserialization code for the header yourself. Any language you choose should have a library that can do this for you, and provide types and utilities for working with IP addresses:

  • In Go, the supplemental netip package contains several types for working with IP addresses and prefixes. We have also posted a go module to parse the IP header—our version uses some new Go features not present in Go’s standard IP header struct that will be useful for this project. For an example of how to use it, see the IP-in-UDP example
  • In C/C++, a struct for the IP packet header is available in /usr/include/netinet/ip.h as struct ip. For an example of how to use it, see this code example.
  • In Rust, the etherparse crate provides an IP packet header struct.

Essential code example: be sure to take a look at the IP-in-UDP example for a demo of how to encapsulate IP packets inside UDP packets, parse the IP header, and compute the checksum! See the example’s README, and Gearup II, for more details.

Unsupported features and robustness

Your IP stack is NOT required to support advanced IP features such as fragmentation or IP options. In addition, all other IP advanced header fields (IP Identification, Type of Service, ECN, flags) may be set to zero when you generate packets, as we do not use any of these features. However, your design should be able to receive these packets and act on them in a sensible way, meaning that you shouldn’t crash if you receive a packet with a feature you don’t know how to process.

Similarly, you are not required to send fragmented packets, or implement reassembly of fragmented packets that you receive. Instead, just forward/deliver these packets normally. You are not required to handle IP options and MAY simply drop any packets that use them (which is what most Internet routers will do).

Interoperation

Overall, your vhost and vrouter programs MUST be able to interoperate with the reference versions when forwarding packets. Make sure you test your node in networks that contain the reference node. As you work to implement forwarding, remember that you have Wireshark to help you! You can use Wireshark to view the contents of your IP packets (and those sent by the reference) to make sure they are formatted properly.

The Warmup (online after sencils are released) has more information on how to use Wireshark to inspect your IP packets. In addition, we provide a custom RIP dissector for our custom protocol. As always, feel free to post Wireshark related questions on Ed.

IP stack API

The majority of your implementation code will be shared between your host and router programs. Indeed, a goal of this project is to demonstrate how IP is a common protocol used by all network devices! As you build your implementation, you should be thinking about how to construct a reusable “library” that can be included in both your vhost and vrouter program and an API (ie, set of public functions) that these programs can use to interact with your IP implementation.

What does “library” mean? We just mean a modular piece of code that you include in both programs–the actual term will vary depending on your language:

  • For C/C++, this would just be some functions you include with a header file (like protocol.h in the socket examples)
  • For Go, a separate module in your repo (like protocol.go in the socket examples)

You do NOT need to build a redistributable library (eg. something publishable for other people to use)–it just needs to be something that you can import into both vhost and vrouter.

We are asking you to do a bit of software engineering here–at minimum, the part of your code that handles IP packets MUST be shared between your vhost and vrouter programs in some kind of library. In other words, you may not copypaste/rewrite this code between programs.

Example API

To show you what we mean by an API, here are some example functions you might consider—the idea here is to expose a set of functions to that the host and router can use to implement other functionality.

This is only an example—you can design your API functions any way you like, but at minimum we recommend that your IP stack provide at least the following functionality:

Initialization

Initialize(configInfo IpConfig) (error)           // Example 1 (pick ONE, or something like it)
Initialize(configinfo IpConfig) (*IPStack, error) // Example 2

Set up your IP stack based on information passed in from the config file. (Remember, we give you parsers for the config file–for Go, this creates the IpConfig struct in the example above) This would perform tasks like creating interfaces and opening sockets, starting up threads, etc.

Why are there two examples?

In general, there are two ways to think about representing your IP stack to the rest of the code using it:

  • Example 1: Use global variables to represent your IP stack’s state (interfaces, forwarding table, etc.) and then have some public functions that use them
  • Example 2: Return a struct or object (depending on your language) that holds all your IP stack’s state. If you do this, the other functions listed here would become methods that use this struct.

Both of these options are equal for the project–you can pick whichever is more comfortable for you based on your language and preferences.

Send packets

SendIP(dst netip.Addr, protocolNum uint8, data []byte) (error)

This is the interface to send packets! For this project—and for the network layer in general—the goal is to expose functionality that allows the user (in this case, the program calling the function) to send a packet to any IP in our network. From the perspective of the network layer, the packet could be any arbitrary array of bytes. The protocolNum sets the protocol field in the IP header, which is used by the receiver to denote the packet’s type (more on this in the next section).

Add protocol handler

type HandlerFunc func(...)...  // You decide what this function looks like

RegisterRecvHandler(protocolNum uint8, callbackFunc HandlerFunc)

To receive packets, you will need to provide a way for the user (ie, you, the person implementing vhost and vrouter) to receive different types of packets and connect them to “higher layers” of your networking stack that will process them. The IP header can differentiates between “types” with the IP Protocol field.

Since we will handle a few different types of packets (and add more in the next project), you must make this interface extensible: for a given protocol number, your API should register a handler function (a callback) that gets called when you receive a valid packet of that type.

In this project, we will need to support two different IP protocols:

  • Test Protocol (hosts and routers, protocol value == 0): to test our implementation, your devices will implement a command to send an arbitrary string in a packet using the send command. Your test protocol handler just needs to print out the packet’s content; see the Driver section for more details. The test protocol’s IP protocol value is 0.

  • Routing Information Protocol (RIP) (routers only, protocol value == 200): routers will communicate with each other using the RIP protocol. When you receive a RIP packet, you will need to pass it off to an appropriate handler that will process the packet according to our RIP specification and update the forwarding table as necessary.

In the next project, you will use this same mechanism to add another handler for TCP. This will be the starting point for receiving TCP packets, mapping them to sockets, etc.

Here are some examples of what handler functions might look like in each language (feel free to make your own, though!):

Go

One potential type definition could be

type HandlerFunc = func(*Packet, []interface{})
RegisterRecvHandler(protocolNum uint8, callbackFunc HandlerFunc)

where []interface{} contains data that a handler could need.

C/C++

For some some_data_t,

typedef void (*handler_t)(some_data_t *, struct ip *);

void net_register_recv_handler(uint8_t protocol_num, handler_t handler);
Rust

This one is a little trickier; one potential type might be

type Handler = Arc<Mutex<dyn FnMut(IPPacket) -> Result<()> + Send>>;

Alternatively, you could create a trait with a corresponding register function:

pub trait Handler: Send {
    fn handle_packet(&self, packet: IpPacket);
}

pub fn register<H: Handler + 'static>(table: &mut HandlerTable, protocol: u8, handler: H) {...}

For example, for RIP packets, the RIP packet should be the payload for the IP packet. As a protocol, an RIP handler should be able to be registered with the following in order to receive incoming packets with an IP protocol field of 200:

RegisterHandler(200, RIPHandler)
RegisterHandler(0, TestPacketHandler)

Routing with RIP

One part of this assignment is to implement routing using a modified version of RIP protocol, which is defined by RFC2453. We will discuss the RIP protocol in detail during class within the first week of the assignment’s release. You can also read more about the RIP protocol in Sections 13.1–13.2 of the Dordal textbook or in Section 4.2.2 of the Peterson textbook.

Our implementation of RIP is slightly different than the RFC version. For essential details on our version, see the RIP specification.

Command-line interface (REPL)

Your vhost and vrouter programs must provide a command-line interface (ie, a REPL) that can be used to send packets and print out network information.

Similar to Snowcast, your command-line interface MUST follow a specific format for how commands are structured and what output you print. For details, see the REPL commands specification.

Program arguments

Both your vhost and vrouter programs MUST take in one command-line option --config, that specifies the lnx file that specifies the lnx file used to configure it, like this:

vhost --config <lnx file>
vrouter --config <lnx file>

You can add other options if you want (for debugging, logging, etc), so long as they are optional.

Extra capstone requirements

Capstone students will need to implement at least one of the following extra features in their implementation. Each student completing a capstone must implement one feature—so if both students on a team are doing capstone, you must implement both requirements.

These features are open-ended graded manually during interactive grading, mainly by demonstration and discussion. You have broad freedom on how you implement each feature and can choose your own behavior for any design decisions not specified here, as long as they do not break any other assignment requirements.

Traceroute (Capstone Only)

Your driver should include the following command for demonstrating traceroute:

traceroute <vip>

Which should print out the sequence of hops in the following format: <hop num> <vip>. So an example output would be:

Traceroute from 192.168.0.2 to 192.168.0.5
    1 192.168.0.2
    2 192.168.0.8
    3 192.168.0.5
Traceroute finished in 3 hops

From this command, we should be able to see changes in the path when any node in the network is brought up or down. If a host is not in the network, or is unreachable, you should print that information.

In your README (or a separate file in your repo), you should submit a writeup about how your traceroute works, your major design decisions implementing it, and any problems you observed.

Neighbor discovery (Capstone Only)

In this project, we assume that all hosts on a local network always know about all of their neighbors. For example, in the configuration below, r2 knows about two neighboring hosts on its r2-hosts network:

interface if1 10.2.0.1/24 127.0.0.1:5004 # to network r2-hosts
neighbor 10.2.0.2 at 127.0.0.1:5005 via if1 # h2
neighbor 10.2.0.3 at 127.0.0.1:5006 via if1 # h3

What if we didn’t want to know about all hosts ahead of time? Design and implement a small protocol for a host to connect to a router and “register” itself as a neighbor on a particular network.

What is the minimum amount of information that the host and router would need to know about each other ahead of time?

You can design this protocol any way you want. You don’t need to have a fully-complete implementation for the grading meeting, but you should have a complete specification of what kinds of messages are involved and how they would work. As you built your implementation, what kind of problems did you notice? In your README (or a separate document) you should submit a writeup with your protocol design, an overview of how you implemented it, and any problems you encountered.

How to get started

Stencil repo

Once teams are defined, you will be assigned a Github classroom link to create a repository for your team. You will receive an email with your team assignment—an announcement will be posted on Ed after these are sent so you can make sure you received it. Until then, there is no stencil to clone. We appreciate your patience!

Once your teams are made, it’s time to start building your design and (after some design work) writing code! See the Getting started guide for info on how to clone the stencil and use the reference version.

Essential scripts and examples

We provide several programs to help you run networks in different configurations:

  • util/vnet_generate: Generate configuration files (.lnx files) from a network JSON file
  • util/vnet_run: Run all hosts and routers in a network automatically in a tmux session

See the Getting started guide for a tutorial on how to use these, as well as the Tools and Resources for more tutorials and information on how to test your work.

Reference version

Similar to Snowcast, we have provided a reference version of the vhost and vrouter binaries in the reference directory of your repository so you can get a feel for how the project should work and test our your implementation to make sure it interoperates with ours. We strongly encourage you to test and debug your implementation by running your networks with a combination of our routers/hosts and yours.

See the Getting started guide for info on how to run the reference version with our scripts vnet_run and vnet_generate.

Note: If you are using an M1 Mac, you should use the versions of these binaries in the reference/arm64 directory—these binaries are compiled for your CPU architecture.

Getting Help

This project has a large scope, but we don’t intend for it to be painful! We have provided a number of resources to help you, and we are always happy to answer questions. To start, make sure you have read this handout and the linked specifications and resources to help understand how the virtual network architecture works. Ed is always a good place to get help on general topics—please read the (pinned) FAQ/Reading list post, for a list of common questions. During office hours, we are happy to talk about concepts and how to approach your software design, or help with more focused debugging.

Note: Whenever you link us to your repository (for an Ed post, final submission, etc.), please make sure it contains a Makefile per our submission requirements so we know how to build your code.

How to submit your work

Similar to Snowcast, please include a Makefile such that make builds your project and places your vhost and vrouter binaries in the top-level directory of your repository.

Collaboration

This assignment has some very tightly-coupled components, so it is important that you work closely with your partner to design and implement the core parts of the project together. It will not be possible for you to go off into separate rooms, implement your half, and “just hook them up.”

We recommend designing the core components of your work as a team, such as the layer interfaces and the forwarding table. After you have defined your interfaces and tested some of the low-level components, you might divide up additional work on the link layer interface and RIP, but you can do whatever you feel is appropriate.

Your stencil repo should be set up so that you and your and your partner can use it to collaborate using git. For some resource on using git, we recommend the following lectures from CS0060 (an older course on systems fundamentals):

However, please note that your Git repos should be private, and you are permitted to share code with other groups. You are welcome to talk to other groups about concepts, algorithms, etc., but each group’s code must be their own.

Mentor TAs

Each group will have a member of the course staff (TA or instructor) assigned as their mentor. During the milestone phase of the assignment, you’ll meet with your mentor TA to discuss your project’s design. At the conclusion of the project, you will meet with your mentor TA again to grade your work (see Handing In and Interactive Grading for more info).

While the project is out, your mentor TA will be best-equipped to help answer questions about, debugging, discussing design, etc., in their office hours or on Ed, but they are not your only support resource: you should feel free to ask questions of any member of the course staff. You should not be contacting your mentor TA for help directly outside of hours—please use our standard course resources (Ed, hours, etc) for this.

Grading

Milestone - 20%

You will schedule a milestone design meeting with your mentor TA by Friday, October 4th at the latest. An announcement will be made on Ed with instructions on how to schedule—we will be creating meeting slots in the week leading up to the milestone deadline. At this milestone, you should have a design for your program and be ready to ask about any areas where you have questions.

For this meeting, you should:

  • (Each team member) Follow the instructions in the Getting Started Guide to clone your repo and start running the reference version—this will make sure you know how to run the tools. At the end of the Guide, you’ll be asked to commit a screenshot to your repo to demonstrate that you’ve done this part.
  • (As a group) Commit something to your repo to demonstrate your design to us — this could be some data structure definitions, interface designs for the different layers, diagrams, etc. We aren’t going to constrain you with requirements here, but we urge you to use the time before your meeting wisely and plan what you want to show us, and have questions. The feedback you receive will pay off for your implementation.

As you think about your design and prepare for the meeting, be ready to answer specific questions about your design, for instance:

  • What are the different parts of your IP stack and what data structures do they use? How do these parts interact (API functions, channels, shared data, etc.)?
  • What fields in the IP packet are read to determine how to forward a packet?
  • What will you do with a packet destined for local delivery (ie, destination IP == your node’s IP)?
  • What structures will you use to store routing/forwarding information?
  • What happens when a link is disabled? (ie, how is forwarding affected)?

Note: We may not have covered routing with RIP in class by the time you have your milestone meeting–you aren’t expected to know the details of how RIP works for your meeting, but you should understand how it fits into your overall program architecture as a component that will update your forwarding table.

Functionality - 60%

Most of your grade will be based on how well your program conforms to our specification. As in the Snowcast project, you will be expected to interoperate with the reference implementation. Among other details, your program will be expected to maintain forwarding tables, handle packet forwarding, network loops, and maintain interfaces that may go up or down at any time without causing the network to crash.

Historically, this project has been graded manually. We are working on automated testing for some parts of the assignment, but these will only inform how much manual review we do and what we ask you about during grading meetings—your grade will be determined entirely by a human.

Interface Design / Code Quality - 15%

Because part of the specification prescribes the programming interfaces for the link layer and for the upper layer handlers to IP, we will be evaluating the design of those interfaces as well as general code quality.

README - 5%

Please include a README that describes your design decisions, including:

  • How you build your abstractions for the IP layer and interfaces (What data structures do you have? How do your vhost and vrouter programs interact with your shared IP stack code?)
  • How you use threads/goroutines
  • The steps you will need to process IP packets

You should also list any other notable design decisions and any known bugs. For any discussion of known bugs, please include a description of how you have attempted to fix the bug/where you think the bug originates from in your code—we will take off fewer points for bugs that we do not find ourselves.

Handing in and Interactive Grading

How we will build your code

Similar to Snowcast, please include a Makefile such that make builds your project and places your vhost and vrouter binaries in the top-level directory of your repository. Make sure your binaries follow our specification for command line arguments.

How to submit

Once you have completed the project you should commit and push your work to the main branch of your git repository, then upload it to the IP assignment on Gradescope. Only one team member needs to submit, but you MUST add your partner to your submission.

Post-project form: Within 24 hours of your final submission (late or not), each team member MUST fill out a form to discuss their overall contributions to the project, and how collaboration worked on their team. The instructor will review all form responses and may ask questions of individual team members to ensure all parties contributed to the project. Filling out the form is required to receive a grade—if you do not fill it out, you will not receive a grade on the project.

How grading works: We will primarily grade your projects interactively, mainly by pulling your code from your git repository and looking at some automated test feedback on Gradescope. After the deadline, your team will meet with your mentor to demonstrate the functionality of your program.

Bugfixes: Between the time you’ve handed in and the demo meeting, you MAY continue to make minor tweaks and bug fixes (documentation, README, fixing small issues, refactoring) and push them to your git repo. However, you shouldn’t be making any major changes or getting any functionality to work for the first time. For example, fixing a small bug in a mostly-working component is fine, but implementing RIP or fixing a bunch of code you pushed without prior testing is not. We add this flexibility because you’ll be re-using your codebase for TCP, so we want you to have the ability think about maintaining and cleaning up your code as you prepare for this next step. We reserve the right refer to your original version during grading to ask you about how your code has changed.


Table of contents