Tools and resources

This page provides an overview of all the tools and support resources we have provided to help you build your implementation. As you get started, skim over this list to get a sense of what is available for you to use.

Note: Some of the items here just link to other pages–thanks for your understanding as we try to figure out the best way to organize all this information!

Essential code examples

  • UDP-in-IP demo (C version): Look here for an example of how to encapsulate IP packets inside UDP packets, parse the IP header, and how to compute and validate the checksum. This should help you understand what encapsulation looks like as you implement your link layer!
  • Lnx file parsers (All languages): We have provided parsers for lnx files in all languages–look here for instructions on how to use them in your code.

Language-specific resources

Here are some language-specific recommendations on libraries and data types:

Go
  • We highly recommend using Go’s netip package for representing IP addresses and prefixes. This is the best Go datatype for storing IP addresses in data structures like maps.

  • For logging, Go’s log package is a pretty standard logging package you could use. For more structured logging, we recommend slog, which is new to Go and covers a lot of common use cases (tutorial)

C/C++
  • A linked list and hash table implementation are available here. You can find examples for how to use each of these in this repo in the examples directory.

  • In addition, you can find a C code example that outlines a structure for the driver’s command-line parsing using readline here. The readline library provides some helpful features for implementing a command line (like pressing the up key to get your last command).

Rust
  • The rustyline crate provides a Rust implementation of GNU readline; you may find it useful in creating a REPL.

  • etherparse provides a bunch of utilities for working with IP packet headers (e.g. serde, checksum computation, etc).

Third-party libraries

If you are using Go or Rust, feel free to use other external packages that provide similar features for logging, command line parsing, etc. If you have questions about whether a library is acceptable, just ask!

If you are using C/C++, please check with us before using any external libraries so we can make sure they will work in our grading environment.

Ways to use vnet_run

vnet_run has a lot of config options to run networks in different ways. You may find this useful for testing your nodes with the reference, as well as debugging your nodes!

Note: For a complete tutorial on how to use vnet_run see here.

Here are our favorite vnet_run commands (replace <net dir> with a directory that has lnx files):

  • Run your vhost and vrouter on some network: util/vnet_run <net dir>
  • Run the reference version: util/vnet_run --bin-dir reference <net dir>
  • Run specific binaries for vhost and vrouter (here, use reference for vhost, your vrouter): util/vnet_run --host ./reference/host --router ./vrouter <net dir>
  • Pass extra arguments to each node (here, running the reference in debug mode):
    util/vnet_run --bin-dir reference <net dir> -- --log-level debug

Reference command-line options

The reference vhost and vrouter have several command-line flags you can use to help you debug. For a full list, run vhost or vrouter with --help. Here are our favorite options:

Option Example Description
--log-level LEVEL --log-level debug Turn on debug logging. Can also use info, warn, error for various debug levels. Default is warn.
--drop N --drop 0.02 (vrouter only) Drop the specified proportion of packets when forwarding. Must be a decimal number between 0 and 1. 1 means drop all packets, 0 means forward all packets.
--drop-rate-seed N --drop-rate-seed 1234 (vrouter only) Control random seed used with --drop. Using the same seed value means the same order of packets will be dropped each time.
--disable-tcp-checksum --disable-tcp-checksum (vhost only) Skip TCP checksum validation
--disable-ip-checksum --disable-ip-checksum Skip IP checksum validation

Debugging tip: To save you time, you can quickly set up vnet_run to run each node in your network with specific command-line flags! Take a look at Using custom vnet_run configurations for a guide.

Testing your work (with wireshark)

This section is mostly about testing for the IP project, but you should refer to what’s here for testing TCP as well!

One of the best ways to test your work is to look at your virtual IP packets in wireshark as you send them. This can help you make sure your packets are formatted properly, and make sure you are forwarding packets as you expect. We will do this using Wireshark when we grade your project, so it is in your best interest to check your work this way!

See the Wireshark testing guide for a conceptual overview of how to think about IP-in-UDP encapsulation, and a tutorial on how to configure your UDP sockets, format packets, and examine them in Wireshark in various ways, including:

We highly recommend using these techniques to help you debug your work! If you come to us with a debugging problem, one of the first things we’ll ask is “what does it look like in wireshark?”, as it’s one of the best and most objective ways to see what’s happening.

Using the reference in debug mode

The reference version uses log levels to hide debugging messages by default, but make them easy to display if you want. In debug mode, the reference will print output when packets are dropped (invalid checksum, interface down, bad UDP port, etc.) as well as other useful stuff!

You can turn on logging by passing the option --log-level debug when you use the reference.

To do this with vnet_run, run it like this:

cs1680-user@3d58a59f4be7:~/iptcp-ndemarinis$ util/vnet_run --bin-dir reference <dir with net files> -- --log-level debug

When started in debug mode, your tmux session should look like this!

Want to use log levels in your implementation? Check out go’s slog package! (Here’s how to use it with color!)

Custom vnet_run configurations (binaries.json)

See the video for TCP Gearup III for a demo of how to do this.

Want to run some nodes using the reference version, and some nodes with your programs? Want to pass different arguments to different nodes? Do you want unlimited power in how you run stuff with vnet_run? Look no further!

vnet_run can take in a special JSON file that we call a “per-binary config” that tells it exactly how to run each node and what arguments to use. You can use this to specify custom configurations like “run r2 as my node, use the reference for the others”, or to use specific command-line arguments for each node.

  1. In the directory for the network you want to run, open the file binaries.example.json. This is a template you can use!
  2. Save this file under a different name for the kind of test you want to run (eg. ours-and-reference.json).

  3. Next, modify the file to have the configuration you want (see the example below).

Let’s say we wanted to run the network in this guide with the reference host as h2 and reference router as r2, and otherwise use our nodes. Expand the box below for an example.

Click to expand

Danger: Don’t copy this example directly! This example is annotated with comments using //, which isn’t actually part of the JSON standard! If you copy this into a JSON file and try to run it with vnet_run, it will break!

“Wait, really? JSON doesn’t support comments?” Yup. Every time you see comments in JSON, it’s being parsed by a non-standard parser.


{
 "h1": {
  "binary_path": "./vhost" // Relative path to host binary, from where vnet_run is run
 },
 "h2": {
  "binary_path": "./reference/vhost", // Use the reference host instead!
  "extra_args": "--log-level debug"  // Extra args to add when running this
                                     // binary (--config is added automatically)
 },
 "h3": {
  "binary_path": "./vhost"
 },
 "r1": {
  "binary_path": "./vrouter"
 },
 "r2": {
   "binary_path": "./reference/vrouter", // Use the reference router
   "extra_args": "--log-level debug"
 }
}
  1. Use vnet_run to start the network with the config, like this:
    util/vnet_run --bin-config <net dir>/ours-and-reference.json
    

This will start up each node listed using the specified binary and extra arguments! Unlimited power!

Note: Make sure you’re running the correct program for each node and that your paths are correct. In this mode, vnet_run doesn’t care which node is a host or a router–it just runs what you tell it to run. :slightly_smiling_face:

Using a debugger

Want to run one of your nodes in a debugger? Wondering how to do that since all of your nodes are run using our vnet_run script? Look no further!

The main idea for this is that we want to run all of the nodes using vnet_run except the one we want to run using the debugger. For Go, we highly recommend using VSCode’s debugger, which is quite easy to set up. For C/C++, you can follow the same general procedure by running gdb from a terminal, or use VSCode.

Setting up the VSCode debugger

Similar to Snowcast, you can configure VSCode’s debugger by creating one file in your project repository: .vscode/launch.json. This file contains a list of ways to run your project, which VSCode can use to start the debugger. To make this work, the file basically needs to tell VSCode two things:

  • What programs to run (in our case, vhost and vrouter), and where they live in your code
  • What arguments to run the programs with (for us, the program arguments

To set up your debugger, do the following:

  1. Create a new file .vscode/launch.json in your project repo

  2. Expand the example below, paste it into your blank launch.json file, and modify it to suit your needs (see TODOs in comments–it will not work unless you modify it):

Click to expand
{
    "version": "0.2.0",
    "configurations": [
        {
            "name": "Run vhost (example)",
            "type": "go",
            "request": "launch",
            "mode": "auto",
            "program": "${workspaceFolder}/cmd/vhost", // TODO:  Fill in path to where your vrouter main.go lives
            "console": "integratedTerminal",
            "cwd": "${workspaceFolder}",
            "args": ["--config", "nets/some_net/h1.lnx"] // TODO: Fill in args to run program (modify for each net you want to run)

        },
        {
            "name": "Run vrouter (example)",
            "type": "go",
            "request": "launch",
            "mode": "auto",
            "program": "${workspaceFolder}/cmd/vrouter", // TODO: Fill in path to where your vrouter main.go lives
            "console": "integratedTerminal",
            "cwd": "${workspaceFolder}",
            "args": ["--config", "nets/some_net/r1.lnx"] // TODO:  Fill in args to run program (modify for each net you want to run)
        }
    ]
}

Note: For an example of a working debug configuration on a real project, see this lecture example, which uses this setup for the client and server programs.

Once you have your configuration set up, save the file. If you are in VSCode, it should automatically syntax-check the file and let you know if there are any issues.

Starting the network

If you want to test your node’s behavior when it’s part of a network, it’s best to start the network first. If the problem you’re trying to debug doesn’t involve any interaction with other nodes yet, skip this step for now and go to Running the debugger.

The basic idea is to start all the nodes using vnet_run normally, then kill the one we want to debug so that we can start it in the debugger.

To start the network:

  1. Run the network using vnet_run as usual. For details on how to do this, see here.

  2. In the vnet_run tmux window, go to the pane for the node you want to debug (to do this, use tmux commands like Ctrl+b o)

  3. When you’re at the terminal for the node you want to debug, press Ctrl+C. This should stop that node and drop back to a shell.

Now we can start the debugger!

Running the debugger

Once you’ve created your debug configuration, VSCode automates most tasks for running the debugger.

Here’s how to start it (each step marked on the figure below):

  1. Open VSCode’s “Run and Debug” mode in the sidebar on the left

  2. Set a breakpoint at some point in your code

  3. In the top-left, select the debug configuration you want to use (run vhost or run vrouter, based on our examples) and press the Start Debugging button

The debugger should take a few moments to start up, but you should see VSCode stop at a breakpoint, like the figure below! You can now use the debugger, yay!

If there was an error starting the debugger (say, a file path was wrong), it should appear in the terminal at the bottom. VSCode should start up in Debug mode, which should look like this:

Important stuff about the debug view

Take a look at the figure above for the most important parts of the debug view. Even if you’ve used VSCode’s debugger before, there are some super important things to note about debugging our project:

  1. There are two debug terminals:
    • The Debug console (shown in the figure) is how you enter commands to the debugger itself (to print variables, call functions, etc.)
    • The terminal: which has a standard VSCode terminal that with the stdin/stdout for the program you are debugging. This is where your node’s REPL is.

To open the terminal with your node’s REPL, select the Terminal tab in the bottom pane of VSCode, like this:

  1. Don’t forget to stop your node! Remember that all your vhost and vrouter nodes bind to UDP ports. If you leave your debugger running and start a network with vnet_run, it will fail to start the node that occupies the same port, leading to an “address already in use” error! To stop your node, use the stop button in the debugger controls.