**[&laquo; Back to the main CSCI1680 website](https://cs.brown.edu/courses/csci1680/f22/)** # "Lab 2": Important tasks for IP You may have noticed that IP doesn't have a lab like Snowcast. In place of a full lab, this document describes some of the most important to keep in mind as you start building IP, and some pointers for how to implement them. In addition, we will cover some super important debugging techniques for checking your work. Specifically, this "lab" will cover: 1. How to send well-formed virtual IP packets encapsulated in UDP packets 2. How to view and debug virtual IP packets in Wireshark 3. How to view and debug RIP packets in Wireshark Why are these topics important? 1. Sending properly formatted IP packets ensures that you can interoperate with our reference node, which is how we'll be grading. **Even if you can already send IP packets when you read this lab, you should still follow/check your work against these steps**, since you MUST conform to the specification on sending IP packets over virtual links in order for us to grade your work in a reasonable way. 2. **For this project and the next, Wireshark is one of the best ways to debug your work**, and the best way to make sure you're conforming to the specification. Spending a bit of time now getting accustomed to these tools now will save you **a lot** of time later, we promise! #### Okay, but this *really* looks like a lab... Okay sure, but we are making this way too late in the project to make it part of the assignment. Instead, we are releasing it as a set of steps you can try and keep in mind as you work on your implementation. We created this "lab" based on the most common questions and points of confusion from last year's offering in the course. **In particular, we have explained in detail the most important ways you can debug your work.** Therefore, even if you don't do all of it, we *highly* recommend skimming over everything here to get a sense of how it can help you later! To sweeten the deal and give you some incentive for doing this now, we will give you +5 points extra credit on IP for completing the tasks here by **Tuesday, October 18 by 11:59pm EDT**. For details on what to submit, see [this section](#How-to-turn-in-this-lab-ie-I-want-my-extra-credit). Have fun, and I hope this helps! Please also understand that this is a "first draft" of a lab: if you have questions or notice any issues with the material, please post on Edstem--thanks for your patience and understanding! ## Background: example network topology In this document, we'll frequently use the ABC network as an example. The relevant components of this network are shown here: ![](https://hackmd.io/_uploads/SkVs-6Xfj.png) ... where: - The red squares represent each node (A, B, and C) - The number in each red box (after the colon) is the **UDP port** for that host - The purple squares represent the **interfaces** for each node - The black lines represent the links between the interfaces on each node - Below each node is a table mapping each interface on that node to a virtual IP address Now that you've done your milestone, let's review some key points about our virtual network works: * Each node has some number of *virtual interfaces* (also called "links") on which it receives packets. Each interface is a link to **one** other node. When a node has a direct link to another node, we will call it a *neighbor* of that node. In this example, A is a neighbor of B, but not C. * Nodes have *one IP address per virtual interface*. **This means that one node can have multiple IP addresses!** For example, B has addresses 192.168.0.2 and 192.168.0.3, corresponding to its links to A and C, respectively. * Each node listens on **one** UDP port, which it can use to send/receive packets to its neighbors. In this example, nodes A, B, and C listen on ports 5000, 5001, and 5002, respectively. A or C can send packets to B by sending them to UDP port 5001. ## Sending to virtual links As we mentioned above, we represent virtual links by sending UDP packets between nodes. Each node listens on **one** UDP port to receive packets from **all** of its neighboring nodes. For example, in the ABC network above, both A and C send packets to B by sending them to UDP port 5001. An important question at this stage is: when B receives a UDP packet, how does it know which virtual link it came from? When we *send* packets from a node, they must also originate from that node's UDP port. Thus, when B receives a UDP packet from port 5000, it knows the packet came from A. When B receives a packet from port 5002, it knows the packet came from C. What does this mean, you ask? Let me show you! With Wireshark! ### Viewing virtual links in Wireshark We can see the virtual link layer in action by viewing the traffic in Wireshark. To get a sense what it should look like, we'll first demonstrate it using the reference node, and then you can check your own traffic in the same way as you work on your node. This guide assumes that you can already start Wireshark in your container. For instructions on starting Wireshark, take a look at the [container setup guide](https://cs.brown.edu/courses/csci1680/f22/pages/container_setup/) and [lab 1](https://hackmd.io/@csci1680/snowcast-wireshark-lab). :::success **Task**: In your container environment, open Wireshark and select **Capture** > **Options** and select the loopback interface. At the bottom of the window, find the box labeled "Enter a capture filter..." and enter `udp`, like this: ![](https://hackmd.io/_uploads/HyXFjHNmo.png) :::info <details markdown="1"><summary markdown="span"><b>What's a "capture filter?"</b></u></summary> Here, we use a "capture filter" to tell Wireshark to ignore everything except UDP packets when capturing, since we know that all of our packets are UDP packets. Certain Docker-related things may also use the loopback interface (like X11, and file sharing between the container and your host), filtering these out helps to reduce clutter and save memory. Capture filters are really useful because they run *inside the kernel*--ie, where Wireshark hooks into the network stack to capture packets. Packets ignored by a capture filter never reach Wireshark's memory buffer, so filtering out packets this way can save on memory and improve performance when capturing on high-traffic links. Because capture filters run in the kernel, they are simpler than the "display filters" we learned about in lab 1 and have a simpler syntax. You can read more about capture filters here: https://wiki.wireshark.org/CaptureFilters </details> ::: Now that you have Wireshark running, we can observe some virtual IP traffic by running the reference node. :::success **Task**: In your container environment, start up 3 terminals. In the first terminal **start node A** of the ABC network. (Don't start the others yet!) :::info <details markdown="1"><summary markdown="span"><b>How do I run the reference node?</b></u></summary> The reference node lives in your repository at `tools/ref_node` (or, if you have an M1 mac, at `tools/arm64/ref_node`). To run each node, you will need to provide a .lnx file describing that node's links, which you create using the `net2lnx` utility. For information on how to use `net2lnx`, see [here](https://github.com/brown-csci1680/ip-tcp-starter/tree/main/tools). </details> ::: Once node A starts up, you should see it start sending some UDP packets: ![](https://hackmd.io/_uploads/HyUUrU4Qi.png) These packets actually correspond to RIP messages, though we can't see that yet (we will, though!). For now, look at the UDP header of the packet in the second pane and notice the ports, which should look like this: ![](https://hackmd.io/_uploads/SJatBI4Xi.png) This packet was sent by A, and we can see that the UDP source port is 5000, which corresponds to A's UDP port. The packet is destined for node B, so the UDP destination port is port 5001, which is B's port. > Also, note that node B isn't even up, yet A can send packets to its port! This is because UDP is connectionless--the other endpoint doesn't even need to be up for us to send packets. (And A doesn't know if the packet got to the destination or not.) :::success **Task**: In your other terminals, start up nodes B and C. You should now see traffic sent from ports 5001 and 5002 as well, corresponding to messages sent from nodes B and C. Based on the port numbers, you should be able to which node sent the packet, and which node should receive it, like this: ![](https://hackmd.io/_uploads/HyaXuIV7j.png) ::: Now that you can see how the UDP packets should look, take a look at the following section for some details on how to implement it. If you have already implemented sending UDP packets in your node, try to observe the same things here using your node instead of the reference. If things look the same, continue reading at the section [Making Virtual IP packets](#Making-virtual-IP-packets). ### Configuring your UDP sockets To implement this, we need to "bind" the node's UDP socket to its designated port, which will set the source port on all packets originating from that socket. In Go, we can do this using `ListenUDP`: ```go= // Turn the address string into a UDPAddr for the connection bindAddrString := fmt.Sprintf(":%s", bindPort) bindLocalAddr, err := net.ResolveUDPAddr("udp4", bindAddrString) if err != nil { // ... } // Bind on the local UDP port: this sets the source port // and creates a conn conn, err := net.ListenUDP("udp4", bindLocalAddr) if err != nil { // . . . } // All sends using this conn will have UDP source port == bindPort ``` A key detail here is that **you will use this one UDP socket to both send and receive packets**. Recall that UDP is connectionless: there is no need to have one socket for each endpoint like with TCP. Instead, each time we send a packet, we specify the destination `address:port`, like this (in Go, for example): ```go remoteAddr := net.ResolveUDPAddr(...) bytesWritten, err := conn.WriteToUDP(buffer, remoteAddr) ``` Similarly, when we receive a UDP packet, the kernel will provide some information about the sender (ie, the source address and port). In Go, it might look like this: ```go // SourceAddr is a net.UDPAddr containing info about the sender bytesRead, sourceAddr, err := conn.ReadFromUDP(buffer) if err != nil { // ... } fmt.Printf("Received from %s: ...", sourceAddr.String(), ...) ``` With this, you should be able to send and receive UDP packets on your node's UDP ports and be able to match them to virtual links. For more complete code example, take a look at the [UDP-in-IP demo](https://github.com/brown-csci1680/lecture-examples/tree/main/ip-demo), and also take a look at the documentation for the functions listed here. :::success Task: Now that you have read about how to set up your UDP sockets, try to replicate what you saw in Wireshark with the reference node using two of your nodes. Watch your traffic in Wireshark to make sure that your UDP port numbers look like the reference (and the figures above). ::: ## Making virtual IP packets Now that we have actual UDP sockets, we need to send some actual data to our nodes. All of our messages are sent as IP packets, so each UDP message you send should start with an IP header, and then the actual message content. In this project, the "actual message content" is either a RIP message, or a "test packet" generated by the `send` command (which is just a string). Another way to say this is that our virtual IP packets are "encapsulated" inside UDP packets. Let's start by seeing what this means in Wireshark. ### Viewing virtual IP traffic in Wireshark ("Decode As" rules) :::success **Task**: If you haven't done so already, start Wireshark and run the reference node on the ABC network. ::: As you have seen in our earlier lab, Wireshark knows how to parse (or "dissect", in Wireshark terms) IP packets. Wireshark actually knows how to parse hundreds of different protocols, and it has rules to decide when to run them. Usually, these are based on common port numbers: for example, HTTP traffic normally runs on TCP port 80, so Wireshark will try to interpret anything on port 80 as HTTP traffic. By default, Wireshark doesn't know about our project, so we need to tell it to expect to see IP packets on the port numbers we're using. We can configure Wireshark do this using "Decode As" rules, like this: :::success In Wireshark's main window, right click on one of the packets from node A to node B and select **Decode As...**. You should see a window like the following: ![](https://hackmd.io/_uploads/SJP1tP47o.png) From here, do the following: 1. Make sure the "Field" and "Value" columns are set to one of your node's UDP ports 2. Click on the protocol in the last column and set the type to **IPv4** 3. Click **"Save"**, then **"OK"**. ::: After adding the rule, you should see Wireshark has updated its display and now decodes the packet! For example, this packet is identified as a RIP response (more on this in a moment): ![](https://hackmd.io/_uploads/BJ4wqw4mi.png) However, notice that this rule only applies to traffic on port 5000 (node A), but the traffic from nodes B and C (ports 5001 and 5002, respectively) is still just showing as UDP. **To fix this, we need to add one "Decode As" rule for each UDP port we are using.** :::success **Task**: Add "Decode As" rules for nodes B and C, using the same process as above. ::: You should now see decoded versions of all the UDP packets for our nodes. Yay! :grin: Now let's look at an IP packet. ### Examining IP packets (and test packets) A good place to start sending IP packets is our "test packets", which just send a string from one node to another. To view test packets in Wireshark, it helps to filter out all of the RIP traffic and just focus on the test packets. We can do this by filtering out RIP traffic, which uses the `cs168rip` protocol--a custom protocol we made for the assignment! Then, we can inspect some test packets. :::success **Task**: Do the following: 1. Enter the display filter `not cs168rip`, which should hide all of the RIP traffic. Unless you have sent any packets, this will leave an empty Wireshark buffer. 2. With the ABC network running, send a test packet from A to B (ie, on A enter something like, `send 192.168.0.2 0 Hello world!`). ::: After sending the test packet, you should see a packet listed as `IPv6 hop-by-hop options [Malformed]`, like this: ![](https://hackmd.io/_uploads/ByOFeFNmj.png) **Don't be scared by this name!** This is simply Wireshark trying to parse the string in the packet--it doesn't know what test packets are, so it's just doing its best! To inspect the packet for real, read on. :::success **Task**: To view useful details about the packet, click on the packet and look at the various headers in Wireshark's middle and lower pane, similar to how you inspected DNS packets in lab 1. Note how the lower pane shows all of the bytes in the packet--as you click on the headers, Wireshark will highlight which bytes correspond to that portion. You should see something like this: ![](https://hackmd.io/_uploads/H17gLtV7i.png) As before, you should see the "real" IP and UDP headers that direct the packet from `127.0.0.1:5000` to `127.0.0.1:5001`, which corresponds to sending the packet across our virtual link layer. Below this, you should see another IP header--this is our virtual IP header! After that, click on the `IPv6 Hop-by-Hop Option` header and look at the bytes in the lower pane--this is the string you sent! ::: The key part for debugging is the IP header: expand this section of the header (as in the figure) and look at the fields. Wireshark parsed the header and found these values for all of the fields. When you make your own IP packets, you can come back to Wireshark to make sure the values are what you expect. ### Examining IP forwarding Now let's look at what happens when we forward packets by sending a packet from A to C: :::success **Task**: On node A, send a test packet to C, ie. on node A, enter: `send 192.168.0.4 0 Yay IP forwarding!!` In Wireshark, you should see the same IP packet **twice**: once for when it was sent the packet from A to B, and then from B to C. Look at the headers on both packets--what fields changed as the packet was forwarded? Create a file in your repository called `lab.md` and write down your answer. ::: As you implement IP forwarding, you should expect to see similar results! ### Making your own IP packets In your implementation, will need to create and parse IP packets using the same IPv4 header format you have been seeing here. Note that 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 you can use it: * In **Go**, the supplemental [`net`](https://pkg.go.dev/golang.org/x/net/ipv4) package contains an IP packet header struct, and a function `ParseHeader`. For an example of how to use it, see the [UDP-in-IP demo](https://github.com/brown-csci1680/lecture-examples/tree/main/ip-demo). You can read more about this library here: https://pkg.go.dev/golang.org/x/net/ipv4. * **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](https://github.com/brown-csci1680/lecture-examples/tree/main/ip-checksum) from last year's version of the course. * In **Rust**, the [`etherparse`](https://docs.rs/etherparse/latest/etherparse/) crate provides an IP packet header struct. :::success **Next Step**: Start building some IP packets in your own implementation! Use what you've learned about dissecting IP packets in Wireshark to explore the fields and make sure your packets look correct. You don't need to do this just now, but you now know enough to get started! As you work on this part, see the note below about working with the IP checksum. For more lab-like content, see the section [Examining RIP packets](#Examining-RIP-packets) below. ::: #### The IP checksum How do you compute the checksum? The IP checksum is defined in [RFC1071](https://www.rfc-editor.org/rfc/rfc1071). **However, you do NOT need to implement the IP checksum algorithm yourself.** In Go, the [UDP-in-IP demo](https://github.com/brown-csci1680/lecture-examples/tree/main/ip-demo) shows an example for a library you can use for this in Go. For other languages, see the libraries and examples listed in the previous section. If you find another library other than the one listed here, feel free to use it. Usually, the checksum function takes in a byte array and produces a 16-bit result, which should be filled into the "checksum" field in the IP header. Some things to keep in mind for computing the checksum: * The checksum is computed over the **IP header only**, not the whole virtual IP packet. See the [UDP-in-IP demo](https://github.com/brown-csci1680/lecture-examples/tree/main/ip-demo) for an example. * When computing the checksum over the header, the bytes for the checksum field should be set to zero * When you forward a packet, you must decrement the TTL. **Since this changes the IP header, you MUST recompute the checksum each time you forward a packet.** ##### Checking the checksum Wireshark can also validate your IP header's checksum, just like a router. By default, this functionality is disabled in Wireshark, so we just need to enable it. :::success **Task**: When examining a virtual IP packet, right click on the header (as in the figure) and select **Protocol Preferences** > **Validate IPv4 Checksum if Possible**, like this: ![](https://hackmd.io/_uploads/HkKqqKVms.png) ::: For all of your IP packets, you should now see Wireshark reports if the checksum is correct. When you send your own IP packets, use this to make sure you are using the checksum correctly! ## Examining RIP packets Once you feel comfortable with IP forwarding, you can begin to implement our version of the RIP protocol to exchange routing information between nodes. The assignment document describes the RIP protocol format we will use, as well as the mechanics for how to send messages. For this project, we are using a slightly modified version of RIP compared to the standard version. To help you view and debug RIP messages, we created a custom plugin (called a "dissector") for Wireshark to decode this protocol. In wireshark, we call our version the CS168 RIP protocol. > If you are using the course container, the CS168RIP dissector should be installed automatically in Wireshark. If you don't see CS168RIP packets show up automatically, see [these instructions](https://github.com/brown-csci1680/ip-tcp-starter/tree/main/rip_dissector) to install it manually. :::success **Task**: With Wireshark open, run the ABC network using 3 reference nodes (as before) and select a RIP packet. If you are following along from a previous step, make sure you remove the `not cs168rip` filter from the previous section. **If you do not see any RIP packets**, make sure you have added "Decode As" rules for your nodes' UDP ports, as described earlier. ::: Expanding the header fields should show you the full routing update, like this: ![](https://hackmd.io/_uploads/rkLz7qVQi.png) Now that you can see RIP messages for all your nodes, you should have a good view into how routing messages are exchanged--which you should find useful for debugging. :::success **Task**: In your repository, include a screenshot of your Wireshark window decoding a RIP packet from the reference node expanded like the figure above--the screenshot doesn't need to have a particular filename. In your `lab.md` notes file, add the following: * What node sent this RIP update? To which node was it sent? * What entries are sent in the update? Do you observe instances of split horizon/poison reverse? As you debug your own RIP implementation, consider asking yourself the same questions to help with debugging! ::: ## How to turn in this lab (ie, I want my extra credit!) To show that you did the lab, you should commit your `lab.md` notes file and wireshark screenshot to your repository by **Tuesday, October 18 at 11:59pm**. We will check this when we do the final grading for the assignment--**so no need to sync up with the grading server or anything yet, just push your code**. If you do this, we will give you **+5 points on the IP assignment**! **Do you have feedback on the lab?** Feel free to include this in your `lab.md` file too! ## Extra stuff: automation for running nodes (`run_net`) To assist you with running many nodes, we have provided a script called `run_net` that runs a bunch of nodes at once in a `tmux` session. `tmux` is a terminal multiplexer, a program that allows you to run multiple processes in the same terminal and navigate between them. The `run_net` script should be located in the `tools` directory of your repository. (If it is not there for some reason, get it [here](https://github.com/brown-csci1680/ip-tcp-starter/blob/main/tools/run_net).) For a full guide to using `run_net` and some pointers on how to use `tmux`, see its README, located [here](https://github.com/brown-csci1680/ip-tcp-starter/blob/main/tools/README.md#run_net). One day, we will make this README part of the lab, but today is not that day. :wink: