# Lab 1: Snowcast and Wireshark **Due September 20, 2022 at 11:59PM EST** --- # Introduction In this assignment, you will go through the basics of socket programming, and create a basic client and server that you can use for your Snowcast project. You will also learn how to use Wireshark to inspect network packets. ## Getting Started **You may implement Lab 1 in Go, C, C++, or Rust**. If you are unsure, we recommend using Go, even if it is new to you, as class examples this year will use it. We have curated a list of resources for each language [here](https://cs.brown.edu/courses/csci1680/f22/documents/). **If you want to use Rust**, note that only some members of the course staff can provide language support (though we are all happy to discuss conceptual questions!). For details, see the [course staff list](http://cs.brown.edu/courses/csci1680/f22/index.html#course-staff). # Assignment This assignment has two parts: - First, you will implement a client and a server such that your client can successfully connect to your server, send a Hello command, and then wait for and print the Welcome reply. - Second, you will work through a few exercises using Wireshark, which will familiarise you with Wireshark's interface and capabilities. # Part 0: Initial Setup ### Development environment **Before starting this lab**, please set up your course development environment using [this guide](https://hackmd.io/@csci1680/SkPoVy3Jj). This will set up a development environment you can use to build your projects. The guide also explains how to run Wireshark within your container, which will be helpful for debugging your projects and understanding network traffic. This lab will also help you get started using Wireshark for debugging. ### Repository This lab is designed to be your starting point for the Snowcast project. Since you will likely end up using what you implement here in your project, we recommend starting work in your Snowcast repository. To create it, use the Github Classroom link in the [Snowcast Assignment](https://hackmd.io/0rLQPRqlRpmtODPsj7yY5Q). # Part 1: Intro to Socket Programming ## Some definitions ### What is a socket? [Beej's Guide](https://beej.us/guide/bgnet/html/) is an excellent resource for understanding the basics of socket programming - although it uses C as the main language, many of the concepts presented in the guide are transferrable to other languages. Here is how Beej's Guide defines a socket: > You hear talk of “sockets” all the time, and perhaps you are wondering just what they are exactly. Well, they’re this: a way to speak to other programs using standard Unix file descriptors. > > What? > > Ok—you may have heard some Unix hacker state, “Jeez, everything in Unix is a file!” What that person may have been talking about is the fact that when Unix programs do any sort of I/O, they do it by reading or writing to a file descriptor. A file descriptor is simply an integer associated with an open file. But (and here’s the catch), that file can be a network connection, a FIFO, a pipe, a terminal, a real on-the-disk file, or just about anything else. Everything in Unix is a file! So when you want to communicate with another program over the Internet you’re gonna do it through a file descriptor, you’d better believe it. **TL;DR** **sockets act just like file descriptors between two endpoints of a network connection** - you can read data from the socket (like you would read data from a file descriptor), and you can write data to the socket (like you would write data to a file descriptor). In our project, a socket can also be thought of as a pipe between a client and the server that either endpoint can send data through. ### What is an IP address? What is a port? Roughly, - An **IP address** is the identifier for a network connection endpoint that the **network layer protocol** uses (e.g. IP) - A **port** is the identifier for a network connection endpoint that the **transport layer protocol** uses (e.g. TCP). A "network connection endpoint" here refers to one end of a network connection; for example, if your computer is connected to a remote server, then each of your computer and the remote server are both connection endpoints. Beej's Guide has a good analogy for how IP addresses and ports are used to deliver network packets: > Think of the IP address as the street address of a hotel, and the port number as the room number. In other words, the IP address is used for forwarding a packet to the destination endpoint, while the port is used for sending the packet to the *application using the port* at that endpoint. The kernel is responsible for knowing which applications map to which ports currently running on the system. ## Setting up a server-client connection with TCP The next few sections will guide you through building a client-server program and sending the first few messages of your Snowcast protocol. :::info **Note**: Much of the content here mirrors the sockets demo in Lectures 2 and 3, which implement a different protocol using the same mechanics described here. While you shouldn't just copy the lecture code into your repository, you're welcome to follow along with the lecture example and use it as a reference to get started! ::: ### Creating a listening socket #### Picking a port number Our first step is to create a socket for the server to “listen” for new connections. From there, clients can send your server a request to start a connection, and then your server can set up a connection to each client. To do this, we need to pick a port number on which the server will listen. In general, port numbers can be any 16-bit number (ie, 0-65535); port numbers less than 1024 can only be used by root, so you the port you use must be higher. Otherwise, you can pick any port number you like.[^1] For example, some port numbers you could pick from are 1234, 6666, 7777, 8888, 9999, etc. [^1]: You can also look at this [list of port numbers](https://en.wikipedia.org/wiki/List_of_TCP_and_UDP_port_numbers) and what each one is typically used for, if you're interested. For now, you can hard-code the port number you choose (i.e. define it as a constant in your server program), or you could also pass it in as an argument. #### Listening for new connections The next step is to create the listening socket, which requires telling the kernel some information about the type of socket we want. The kernel has many types of sockets, not all of which involve IP, or even networking. Right now, since we want to establish TCP connections between clients and the server, we need to create TCP socket. The API to create sockets varies by language. The diagram and sections below describe the most relevant functions you need in Go, C/C++, and Rust. ![](https://hackmd.io/_uploads/HyazzSTej.png) Regardless of the language you use, there are a few steps that neeed to happen to set up a listening socket. Expand the steps below for your language, which outlines what the process should look like. (Hint: Click the names of the functions for links to relevant documentation!) :::info <details markdown="1"><summary markdown="span"><b><u>Instructions for Go</b></u></summary> In Go, you can accomplish this in two steps: 1. First, you can use `ResolveTCPAddr` from Go's `net` package ([`net.ResolveTCPAddr`](https://pkg.go.dev/net#ResolveTCPAddr)) to create the TCP socket and bind it to your chosen listen port. This returns a [`TCPAddr`](https://pkg.go.dev/net#TCPAddr) that represents the new socket. 2. Next, we need to tell the kernel that we want the socket to listen for new connections. You can do this with [`net.ListenTCP`](https://pkg.go.dev/net#ListenTCP), which takes in the [`TCPAddr`](https://pkg.go.dev/net#TCPAddr) from the previous call. </details> ::: :::info <details markdown="1"><summary markdown="span"><b><u>Instructions for C/C++</b></u></summary> In C/C++, this process requires a few steps: 1. First, use [`getaddrinfo`](https://beej.us/guide/bgnet/html/#getaddrinfoprepare-to-launch) here to tell the kernel what kind of socket you want. Note that [`getaddrinfo`](https://beej.us/guide/bgnet/html/#getaddrinfoprepare-to-launch) essentially gives you back a struct you then need to use to make a socket. 2. Next, we need to create the socket itself. Here, you should pass in parameters from the struct returned from [`getaddrinfo`](https://beej.us/guide/bgnet/html/#getaddrinfoprepare-to-launch) into the system call [`socket`](https://beej.us/guide/bgnet/html/#socket). This returns the file descriptor for the listen socket. 3. Use [`bind`](https://beej.us/guide/bgnet/html/#bind) to bind the socket to the port you chose as the server's listening port. 4. Finally (for now), we need to tell the kernel that we want the socket to listen for new connections. For this part, we use [`listen`](https://beej.us/guide/bgnet/html/#listen); the second argument to `listen` specify the maximum number of waiting connections to clients your server can have at any given time (called the backlog, more on this later). > **Why are there so many steps for C/C++ compared to the other languages?!** The C API most directly mirrors the Linux kernel API. The other languages are all doing the same steps, they just hide some of them from you! In addition, since the C API predates all of the other languages, certain aspects are a bit more complex in order to maintain compatibility with older programs. </details> ::: :::info <details markdown="1"><summary markdown="span"><b><u>Instructions for Rust</b></u></summary> Use [`TcpListener.bind`](https://doc.rust-lang.org/std/net/struct.TcpListener.html#method.bind). This function creates a new [`TcpListener`](https://doc.rust-lang.org/std/net/struct.TcpListener.html), which, as the name suggests, is a TCP socket that is set to listen for incoming connections on the specified address/port. </details> ::: :::info <details markdown="1"><summary markdown="span">I found some other examples or documentation that show different ways of creating TCP sockets, are they the same?</summary> For any language, socket APIs can have many different features for convenience or historical reasons--all interact with the same low-level kernel API to perform essentially the same functions. We have done our best to outline a series of function calls that will get the job done, but there are certainly many ways to solve the same problem--you are free to use whichever version you like, so long as it meets the assignment requirements. If you have questions, feel free to ask! </details> ::: :::success **Task:** Start writing your Snowcast server program! Use the functions mentioned so far in the language of your choice to set up a listening socket for your server (in your server program). ::: To use the above functions, you may need to provide some additional parameters to specify how the socket is created. For these, keep in mind the following guidelines, which are good "default values" to use for this lab, and the class in general (so just skim this list over and return here if you need it): - **IPv4** sockets only: For our projects, we don't need to worry about IPv6 sockets, or dealing with IPv4/IPv6 compatibility, so we use IPv4 only - **Bind to any address**: Sockets can be bound to one or more *interfaces* by specifying a "bind address." Network interfaces are different network connections available to the system (eg. Ethernet, Wireless, etc.)--we'll learn more about these later. When you create sockets, they should be bound to *all* interfaces on your system, which usually equates to leaving the "bind address" blank. :::warning **Hint:** A big part of this task, and systems programming in general, will be figuring out how to structure your programs well (since we don't give you stencil code, hehehe). To do this, reading up on function documentation and looking at some of the examples given in documentation can be really useful for creating a basic structure. Also, don't be afraid to come to office hours if you're struggling with the question "so where should I even be calling this function?" or similar ones! This is a skill we want you to learn, and it's okay to ask for help! ::: :::danger **Warning**: Don't forget error handling code! You should check if any of these functions (and any networking functions you use in general) return an error and handle them accordingly. For this part, if any functions encounter an error, you should just print out the error and exit the program. You can usually do this in one step with a function like `log.Fatal` in Go or `perror` in C/C++. Error handling is an essential practice in systems code, and it will save you a lot of debugging time to add this now! ::: Once you've written an implementation (even if you're not fully confident in it yet), continue onto the next step. After this, we'll show you a way you can test your program. ### Waiting for a client to connect If you run your program right now, it will just create the socket and then exit! To actually handle clients, we need to tell the server how to "accept" incoming connections. Each of Go, C/C++, and Rust have a single function call (named "accept", or something similar) to handle a client's connection request, as listed below in the diagram. ![](https://hackmd.io/_uploads/Sksmfragj.png) Either way, the principle of accepting new connections is the same: calling "accept" will normally *block* until a client connects. Once a client connects, "accepting" the connection creates a **new socket** specifically for the connection between the server and the new client, while the listen socket remains unchanged. This is important, because your server should now use the new socket file descriptor to communicate with the now-connected client. However, our server also needs to handle connections from multiple clients, so it should also continue listening on the original listening socket that you made in the previous steps. In other words, *your server should not stop listening for other clients once a new client connects*. Conceptually, you'll want your code to behave something like this (in pesudocode): ``` . . . listen_sock = create_listen_socket() while true { client_sock = do_accept(listen_sock, ...) // . . . Do something with client_sock (later) . . . } ``` To do this for real, consult the following instructions for your language: :::info <details markdown="1"><summary markdown="span"><b><u>Instructions for Go</b></u></summary> We recommend using [`net.TCPListener.AcceptTCP`](https://pkg.go.dev/net#TCPListener.AcceptTCP), which behaves similar to C/C++'s [`accept`](https://beej.us/guide/bgnet/html/#acceptthank-you-for-calling-port-3490.) in C/C++ has, except it is specific to incoming TCP connections. [`net.TCPListener.AcceptTCP`](https://pkg.go.dev/net#TCPListener.AcceptTCP) will *block* until the next incoming TCP connection request and return a new [`TCPConn`](https://pkg.go.dev/net#TCPConn), which represents the TCP socket for the connection between your server and the newly connected client. </details> ::: :::info <details markdown="1"><summary markdown="span"><b><u>Instructions for C/C++</b></u></summary> In C/C++, you can use [`accept`](https://beej.us/guide/bgnet/html/#acceptthank-you-for-calling-port-3490.). A key thing to remember about [`accept`](https://beej.us/guide/bgnet/html/#acceptthank-you-for-calling-port-3490.) is that this function will *block* until a new client connects, and will return a *new socket descriptor* for the connection from your server to the client your server has just accepted. </details> ::: :::info <details markdown="1"><summary markdown="span"><b><u>Instructions for Rust</b></u></summary> In Rust, [`TcpListener.incoming`](https://doc.rust-lang.org/std/net/struct.TcpListener.html#method.incoming) works a little bit differently. [`TcpListener.incoming`](https://doc.rust-lang.org/std/net/struct.TcpListener.html#method.incoming) "returns an iterator over the connections being received on this listener." In essence, this method calls [`TcpListener.accept`](https://doc.rust-lang.org/std/net/struct.TcpListener.html#method.accept) in a loop for you, so then all you have to handle is the [`TcpStream`](https://doc.rust-lang.org/std/net/struct.TcpStream.html) returned by [`TcpListener.accept`](https://doc.rust-lang.org/std/net/struct.TcpListener.html#method.accept). This [`TcpStream`](https://doc.rust-lang.org/std/net/struct.TcpStream.html) represents the new socket made for the new client connection, and you will later use it (as a stream) to communicate with the client. The example given in the documentation for [`TcpListener.incoming`](https://doc.rust-lang.org/std/net/struct.TcpListener.html#method.incoming) is pretty good and will help you understand how to use the method. </details> ::: :::success **Task:** Extend your Snowcast server program so that it can accept new client connections, while continuing to listen for other connection requests! ::: When you run your program now, it should just hang. This is good, it means the server is waiting for new connections! ##### Testing your server (so far) The main command you would use to do this is To see if your server is actually listening on the port you chose, we can use the command [`ss`](https://www.man7.org/linux/man-pages/man8/ss.8.html), which stands for "socket statistics." `ss` can view the kernel's table of open sockets to tell us if our server port is open. :::success While your server is running, open up another terminal and run the following: ``` ss -ln | grep <server port number> ``` ::: You should see something like: ```sh tcp LISTEN 0 20 0.0.0.0:8888 0.0.0.0:* ``` This means that one of the sockets on your machine is listening on port 8888 (or whichever port you used) for TCP connections! In other words, if you can see this output, then your server is running and listening for client connections! :::info <details markdown="1"><summary markdown="span">Want to know more about listening sockets on your system?</summary> For information on other sockets that are listening, run: ```sh ss -lnp | grep LISTEN | less ``` You should be able to see a bunch of the sockets being used for various processes on your system. If you run it with `sudo`, `ss` will also output the names of the processes as well. </details> ::: Now that your server is listening for clients and can accept new connections, all that's left to set up is your client program! ### Starting up the client On start up, your client program should create a new TCP socket and connect to your server. To connect to the server, you'll need to provide its IP address and port so that the client knows how to reach it. Since our client and server are running on the same system, we will connect using Linux's *loopback interface*. This is a special, *virtual* network interface that exists only within the local system. This is useful so that programs on the same system can still communicate using networking protocols, even when there's no network! When making connections, the loopback interface always has the same IP address: `127.0.0.1`. We will use this IP when connecting to the server--for now, you can either pass it into your client program as an argument, or define it as a constant. :::info **Note**: We can also refer to this address using the *hostname* `localhost`. Hostnames like `localhost` (or `google.com` or `cs.brown.edu`) are translated into IP addresses by the OS (often using protocols like DNS, which we'll learn about later). If you've been following the functions in this lab, your code should be set up to also translate hostnames automatically, so you can also pass in the name `localhost` instead of `127.0.0.1`. ::: The server's port should be the one you originally chose for your server. To build your client, you will create a socket in a similar manner to creating the server. However, instead of creating a *listening* socket, we will be creating one to connect to a specific destination, the server. To do this, the relevant function calls are outlined in the diagram below and in the following sections: ![](https://hackmd.io/_uploads/BkKVMHags.png) :::info <details markdown="1"><summary markdown="span"><b><u>Instructions for C/C++</b></u></summary> For C/C++, you again need to do a few steps: First, call [`getaddrinfo`](https://beej.us/guide/bgnet/html/#getaddrinfoprepare-to-launch) once again to tell the kernel what type of socket you want. Here however, you will need to pass both the _server_’s address and port in as part of the specifications for the socket (along with TCP, IPv4 etc). `getaddrinfo` actually returns a list of possible socket options for making the connection. You will need to iterate over the results to find one that allows you to connect--take a look at [`Beej's guide`](https://beej.us/guide/bgnet/html/#getaddrinfoprepare-to-launch) or [this old class example](https://github.com/brown-csci1680/lecture-examples/blob/main/sockets-demo/guessing-game/client.c) for details. In the loop over [`getaddrinfo`](https://beej.us/guide/bgnet/html/#getaddrinfoprepare-to-launch)'s results, you should attempt to create a socket using the current result (by calling [`socket`](https://beej.us/guide/bgnet/html/#socket)) and then attempt to connect to the server using the new socket. To connect to the server, you will need to use the system call [`connect`](https://beej.us/guide/bgnet/html/#connect), which takes in a socket file descriptor (i.e. the one returned from [`socket`](https://beej.us/guide/bgnet/html/#socket)) along with the result from [`getaddrinfo`](https://beej.us/guide/bgnet/html/#getaddrinfoprepare-to-launch). Once [`connect`](https://beej.us/guide/bgnet/html/#connect) successfully returns, you have connected to the server! You can then exit out of your loop, and begin sending data to the server. </details> ::: :::info <details markdown="1"><summary markdown="span"><b><u>Instructions for Go</b></u></summary> In Go, use [`net.Dial`](https://pkg.go.dev/net#Dial), which takes in a network type (i.e. TCP) and an IP address/port number, and attempts to connect to the specified address. The address/port string should be of the form `address:port`---ie. `127.0.0.1:9999` or `localhost:9999`, to connect to your system's loopback address on port 9999. </details> ::: :::info <details markdown="1"><summary markdown="span"><b><u>Instructions for Rust</b></u></summary> In Rust, use [`TcpStream.connect`](https://doc.rust-lang.org/std/net/struct.TcpStream.html#method.connect), which takes in an address (see the examples in the documentation for the structure of the input address) and attempts to connect to it. </details> ::: :::success **Task:** Start writing your Snowcast client program. Use the functions mentioned in the language of your choice to create a TCP socket for your client control program and connect the new socket to the server using the server's IP address and port number. For the purpose of testing, print out something when the connection is established (ie, after `Dial`/`connect` on the client, and after `accept` on the server). If you see your message printed (and nothing returned with an error), your connection was established! ::: Now your client should be able to connect to your server! The two arrows in the diagram below represent the initial TCP packets that should be sent between your client and server when your client attempts to connect to your server. Don't worry too much about understanding these just yet! We will cover what these packets mean closer to the TCP project. ![](https://hackmd.io/_uploads/B1UBGS6xj.png) ## Handling a client (server side) At this point, your server program can accept a new client connection, but we haven't sent any data yet! In this section, you will add to your server's current functionality so that it can handle communication with multiple clients concurrently. You could implement this in many ways, but we recommend you start by making a thread per new client connection, which can then do any client-specific tasks. The important thing here is to make sure you store enough data about the client somewhere, perhaps by making a new data structure. When you create the new thread, you will need to pass this information to the thread so that it can communicate with the client. For now, the most important information to pass to the thread is the client's socket. For this, see the instructions based on your language: :::info <details markdown="1"><summary markdown="span">Instructions for Go</summary> All of the information about your client's socket is contained in the `net.Conn` object returned by `net.AcceptTCP`. This object has various helper methods for sending/receiving on the socket, getting the client's IP address, and more. Consider creating a goroutine for each new client, which takes the client's `net.Conn` as an argument. Later, you may wish to pass a custom `struct` instead that also includes more information. </details> ::: :::info <details markdown="1"><summary markdown="span"> Instructions for C/C++</summary> Right now, you should probably store: - The socket file descriptor the server uses to communicate with the client - The IP address of the client's socket (returned in the argument from `accept`) - The thread ID of the client thread We recommend making a `struct` to hold these and passing them to the client thread. Take a look at [this old class example](https://github.com/brown-csci1680/lecture-examples/blob/main/sockets-demo/guessing-game/server.c) for details. </details> ::: :::info <details markdown="1"><summary markdown="span">Instructions for Rust</summary> All of the information about your client's socket is contained in the `TcpListener` returned when accepting the client's connection This object has various helper methods for sending/receiving on the socket, getting the client's IP address, and more. </details> ::: As you build your snowcast project, it will be important to consider what *state* you need to keep track of for each client. For example, when you start sending data from radio stations to client listener programs, you may also want to store the UDP port of the client's listener. Keep this in mind as you think about how you'll implement the protocol. :::success **Task:** In your server program, for each new client connection, store the new client's data and initialize a thread to handle communication with the new client. For now, just make your client thread print out something like "Client connected!" and then exit. If you see your message being printed when a client connects, your threads are being created! ::: ## `Hello` and `Welcome` Now that your server can handle communication with multiple clients concurrently, it's time to send something useful over the connection. As seen in the diagram below, the protocol you are asked to implement in Snowcast requires your client to first send a `Hello` message, and once your server receive's the client's `Hello`, it should respond with a `Welcome`. Other message types (such as `Announce`, `SetStation`, and `InvalidCommand`) should be sent for other parts of the protocol, but you don't need to worry about them for this lab. ![](https://hackmd.io/_uploads/ryW8fBTlj.png) As a reminder, a client's `Hello` message should look like: ```go Hello: uint8 commandType = 0; uint16 udpPort; ``` And this is what a server's `Welcome` message should look like: ```go Welcome: uint8 replyType = 2; uint16 numStations; ``` Therefore, to send a message, you should send a series of bytes corresponding to one of these messages. Each of C/C++, Go, and Rust have function calls for sending and receiving data over a socket, as summarised in the table below: | Function | C/C++ | Go | Rust | | -------- | -------- | -------- | -------- | | Send | [`send`](https://beej.us/guide/bgnet/html/#sendrecv) | [`(net.Conn).Write`](https://pkg.go.dev/net#Conn) | [`TcpStream.write`](https://doc.rust-lang.org/std/net/struct.TcpStream.html) | | Receive | [`recv`](https://beej.us/guide/bgnet/html/#sendrecv) | [`(net.Conn).Read`](https://pkg.go.dev/net#Conn) | [`TcpStream.read`](https://doc.rust-lang.org/std/net/struct.TcpStream.html) | There are several ways to "compose" the data to send: for example, you can create a data-type (such as a `struct`) to represent the message, and then "serialize" or "marshal" it into a byte array. Alternatively, you can call your language's `send` function once for each message field, after converting each field into the appropriate size. There are many ways to do these operations in each language. For examples, we recommend starting by taking a look at the notes for Lectures 2 and 3, and then consulting your language's resources for the method you like best. :::success **Task:** In your client program, once your client has successfully connected to your server, send a `Hello` message to the server and wait for a `Welcome` in response. In your server program, once your server receives a client `Hello`, send a `Welcome` response back to the client. For both of these, you can start by hard-coding any value for `udpPort` and `numStations`, in the `Hello` and `Welcome` messages, respectively. **Note**: On receiving a Welcome message, please print a message **to stdout** in **this exact format**: ``` Welcome to Snowcast! The server has N stations. ``` where `N` is the `NumStations` received in the Welcome message. This will ensure that your code is compatible with the autograder. ::: :::warning **Hint:** Make sure you are using [network byte order](https://beej.us/guide/bgnet/html/#byte-order) to send packets, and that you are converting data back to host byte order once you have received a packet. We'd recommend taking a peek at the [snowcast dissector section](https://hackmd.io/FFKBYQ1HR2KydxmxnqrNPA?both#Snowcast-Dissector) of part 2 of this lab to ensure that your snowcast messages are being properly formatted and sent! ::: Congratulations, you have your Snowcast server and client programs exchanging messages! ## Preparing your code for Snowcast The client and server programs you created here can serve as your starting point for Snowcast: your TCP client is the start of the Snowcast control client (ie `snowcast_control`), and your server implements (you guessed it!) `snowcast_server`! Before we can check off your work, we ask that you refactor your code a bit so that your programs have the same names and command-line arguments as the Snowcast programs. While this may seem annoying now, we want to make sure that you can interact with the autograder so that you can test your work--and so we can test our autograder! More concretely: :::success 1. As described in the assignment, create a `Makefile` such that `make` compiles your client and server and gives them the names `snowcast_control` and `snowcast_server`, respectively. 2. Update your client and server program to accept the same command-line arguments as `snowcast_control` and `snowcast_server`. For example, your server should be able to run as: ``` $ ./snowcast_server <listen port> <file0> [file 1] [file 2] ... ``` Your client program should run as: ``` $ ./snowcast_control <server IP> <server port> <listener port> ``` 3. (If you have not done so already) Update your programs to take in the client and server's address and port numbers as arguments, as shown in the spec above 4. Update your server's Welcome message with the number of files passed in as arguments. You don't need to handle opening/reading from files right now, but make sure that the number of stations aligns with the command-line arguments (i.e. `NumStations == len(args[2:])`). 5. Update your client's Hello message such that value for `udpPort` is the value passsed as `<listener port>` on the command line. For the final version, the value you will pass for `<listener port>` will be the port on which you run the listener client--for now, you can just pass in any number. ::: ### Getting checked off Head over to [the grading server](https://cs1680.cs.brown.edu/grading/). You will receive email from Nick shortly with the subject `[CS 1680] New account information`. **An announcement will be posted, and this document updated, when you should have received your credentials.** :::danger We're still rolling out account invitations to those who have filled out HW0. If you have not received an invitation yet, please fill out **[HW0](https://forms.gle/yGL2vWstMekpjzKr8)** as soon as possible and wait until you receive your credentials. ::: **Step 1**: Log into the grading server with the username and password you received from the email. Once you're logged in, enter your GitHub username (at the top). **Step 2**: Add your Snowcast repository URL (e.g. `brown-csci1680/snowcast-<GitHub username>` under the "Project 1: Snowcast" section. Click on the "Project 1: Snowcast" button to initiate a fetch of your repository from GitHub. > **Note**: If your GitHub account is not linked to your Brown email address, the grading server will give you a command to run to verify your repository. ![](https://hackmd.io/_uploads/SkW3hAyZj.png) **Step 3**: You should see your commit listed on the Snowcast page, as shown above. Next to this, you should see a button labeled **"Compile and locate binaries"**. This will attempt to compile your code using your makefile (ie, using `make clean && make`), and then check for the presence of `snowcast_control` and `snowcast_server`. (It will also check for `snowcast_listener`, but ignore this for now.) For example, your output from the "Compile and locate binaries" chec should look like this: ``` Cleaning repository with 'make clean'... rm -f snowcast_client snowcast_control snowcast_server Building repository with 'make'... go build cmd/snowcast_server/snowcast_server.go go build cmd/snowcast_control/snowcast_control.go Checking for executable snowcast_control... OK Checking for executable snowcast_server... OK Checking for executable snowcast_listener... FAIL Note: It's okay if you don't have snowcast_listener for the milestone! ``` **Step 4**: Next, click the button labeled **“Checkoff lab milestone”.** Click this button to have your lab submission checked off and graded. > We're currently finalizing grading infrastructure, so you may not immediately see output or failing tests. So long as you've made a commit that before the checkoff deadline that roughly implements these components, you'll be fine. As we finalize our tests, we'll update these instructions for more information. If all your tests pass, they should look something like this: ![](https://hackmd.io/_uploads/ryAQ4hvbs.png) :::warning <details markdown="1" id="autograder-notes"><summary markdown="span"> <b>Tests failing on the grading server?</b></summary> - On the control side, make sure your client prints to **`stdout`** the Welcome message in the format specified above. - Additionally, make sure that you flush your `stdout`! This can be done by appending a new line (`\n`) to your print statements. - On the server side, make sure your server stays alive after completing the handshake. You can do this by making your Accept loop hang, or if you’re spawning it in a thread/goroutine, simply add an infinite loop for now (you’ll have a REPL later). - Ensure that your executables are compatible with the format described above; that is, your server must be able to run as `./snowcast_server <listen port> <file0> [file 1] [file 2] ...`, and your control must be able to run as `./snowcast_control <server IP> <server port> <listener port>`. </details> ::: :::danger **Note**: At present, the autograder will spuriously fail on certain tests (for reasons you’ll learn in IP/TCP!); we’ve added messages to the error output when this is the case. If you’re confused about the error output or think it may be a grading server issue, please comment on the grading server megathread and we’ll respond to it as soon as possible. ::: We thank you all for the continued patience as we shift towards a new autograding system! Keep in mind that our goal is to make this easier to test and check that your program is working as expected: **your final grade will always be determined by a human looking at your code, and the test output**. Good luck! :smile: # Part 2: Wireshark From [Wireshark](https://www.wireshark.org/)'s website: "Wireshark is the world’s foremost and widely-used network protocol analyzer. It lets you see what’s happening on your network at a microscopic level and is the de facto (and often de jure) standard across many commercial and non-profit enterprises, government agencies, and educational institutions." Today we will be using Wireshark to analyze the packets that come over the wire when you fetch a web page. ## Running Wireshark Start up Wireshark either from within the container as described in the [setup guide](https://hackmd.io/@csci1680/SkPoVy3Jj) (under the Running Wireshark section). ## Starting a Capture Once you have started up Wireshark, you should be presented with a window like the one below: ![](https://hackmd.io/_uploads/ByB2oHRJs.png) ### Interfaces Packet captures are performed by attaching to one of the system's network interfaces. In this case, we will be capturing on one of the virtual network interfaces inside the container, so we will only see network traffic from container processes. To start a capture, select **Capture** > **Options** from the menus at the top. Each of these options represents an interface on which packets can be received. In general the three interfaces you will care about are **Loopback**, **Ethernet**, and **Wi-Fi**: - `lo`, the loopback interface: This contains traffic to and from `localhost`--it lives exclusively inside the container. Your own system has its own loopback network, which is local to your own machine. **You should capture on this interface if you are debugging a program that connects to `localhost`, like your assignments.** - `eth0`: This is the container's link to the outside world (i.e., your host system, and the Internet). **You should capture on this interface to see traffic that leaves the container.** Not all machines will have the same naming for these devices, and in some cases they may not all be available. For example, your host system may show a wired and wireless interface. :::info **Note:** for the rest of the lab, please make sure to run any commands we mention within the course container! ::: :::success **Task:** Select the `lo` interface and choose **Start** to start capturing packets. ::: :::danger **Warning:** We are aware of some issues where Wireshark crashes with a segfault when starting the capture. If this occurs, simply try again--this seems to work for us. We are looking into the issue and may post an update in the future. ::: ## Snowcast Dissector We have created a custom dissector for snowcast that will display the snowcast command messages as their own protocol type! Follow the instructions in the [starter repository's util README](https://github.com/brown-csci1680/snowcast-starter/tree/snowcast_dissector/util) to get the dissector set up. If using the reference binaries, or if your server and client are sending the HELLO and WELCOME messages, you should see something similar to the following on Wireshark when a new client connects: ![](https://hackmd.io/_uploads/H1W-uokA3.png) ::: warning Wireshark should display the protocol as cs168snowcast. Notice that the messageType value is important for the dissector to recognize the command type. ::: ## Analyzing packets Once we have Wireshark open on the appropriate interface, it automatically begins capturing packets. :::success **Task:** Stop the `lo` packet capture and start a packet capture on interface `eth0` In your container, run the following commands: ``` $ mkdir tmp $ cd tmp $ wget -r -l 1 http://cs.brown.edu ``` This pulls the CS website's data onto your local machine in the directory `tmp`. After the lab is over, you can delete this directory. ::: You should see a large dump of data in Wireshark. If at any time you want to clear your current log of packets you can stop the current capture by clicking the big red box, then starting another capture by clicking the blue shark fin. Note that you have the option to save the old capture when you click the fin to start a new capture (which may be useful for debugging on later projects). ### Fetch and start a capture :::success **Task:** When the [`wget` command](https://www.geeksforgeeks.org/wget-command-in-linux-unix/) finishes, click the red box to stop the capture. Once the capture stops, Wireshark will no longer log additional packets. ::: Look under the `protocol` column. As you scroll down the log of packets, you should see packets from at least the following protocols: DNS, TLS, TCP, and HTTP. ### Filtering packets Looking at network traffic can leave you faced with an overwhelming amount of information. Fortunately, wireshark provides support for *filtering* traffic so that you can focus on only what you need. For the first section, we'll take a look at ARP packets. Address Resolution Protocol (ARP) is a protocol for use on local networks to learn about IP addresses present on a local network--we'll discuss it in a future lecture. You don't need to worry about the details of the ARP protocol now, but for now can look over the packets! :::success **Task:** In the filter bar at the top of Wireshark's window, enter "arp" and click the right facing arrow. ::: There are many different types of filters that can discriminate between packets based on more than just protocol; if you would like to see and select from a wide range of filters click the "Expression..." button next to the filter bar and browse through the filter options. Later in this lab we will try some more sophisticated filters. :::info <details markdown="1"><summary markdown="span"> Not seeing ARP packets after applying the filter? </summary> If you do not find any ARP packets when you apply the filter it is likely because the ARP information is cached on your system. To get around this, first clear the log of packets then start a new capture (red stop button then blue shark fin). Then, run `arp -n` to see your ARP table, `sudo ip -s -s neigh flush all` to clear your ARP table, then `arp -n` again to confirm the table has been cleared. Now run the command `wget -r -l1 http://cs.brown.edu` again to fetch the webpage. As before, click the red box to stop the capture once `wget` finishes. </details> ::: Now, look at your ARP packets - you should see at least two. Under the info column Wireshark tells you exactly what the ARP packets are doing. We can see a request ARP packet that under `Info` says, "Who has \<requested IP\>? Tell \<requesting IP\>." and a response ARP packet that under info says, "\<requested IP\> is at \<destination Ethernet address\>". If you click on a single ARP packet you will see in the box below the packet log that Wireshark will actually dissect the packet for you (in hex), and in the third box you can see the raw bytes of the packet. If you click on certain bytes Wireshark will let you know what part of the packet those bytes belong to. Conversely if you click on a dissected field of the packet Wireshark will tell you which raw bytes it grabbed the data from to attain that field of data. You should see something similar to the below screenshot when the Ethernet and ARP (Address Resolution Protocol) fields are expanded. ![](https://hackmd.io/_uploads/SkPXLKAxs.png) ### Dissect To dive deeper into dissecting, let's examine some DNS traffic. You will learn about DNS later in the course, but for now know that DNS is the primary way that a computer gets the IP address for a web domain (in our case, `cs.brown.edu`). For our purposes, DNS messages will appear in two parts, a *query* which asks a DNS server for information about a domain (eg. `cs.brown.edu`) and a *response* which contains an *answer* (usually an IP address). #### DNS Query Our machine will emit a DNS query, and receive a DNS response. :::success **Task:** Filter on "dns" and click on the first packet. ::: Collapse all the dissected fields, and note that each top-level field is a header for a different protocol. The top field is the outermost header, in the sense that for anything parsing the header, the parser would find the top most header, then the next header down the list, then so on until the parser reads all the bytes in the packet. Also note our DNS packet contains a header for each layer of the OSI model. #### OSI Model within the DNS Query The first field represents the entire packet of raw bytes that Wireshark received over the wire (from the Physical layer) this is the only field not constructed from a protocol header. The next field is constructed from the Ethernet header that Wireshark parsed (Link layer), within the Ethernet frame is the IP packet (Network layer), within the IP packet is the UDP datagram (Transport layer), and the lowest header is for the DNS query (Application Layer). ![](https://hackmd.io/_uploads/rkr21OR1i.png) #### Encapsulation within the DNS Query If you expand any of these dissected fields, you can see the parsed data of the header plus the payload for the protocol. It is important to note how encapsulation is used here. The Ethernet frame consists of an Ethernet header plus a payload, where the payload is an IP packet. The IP packet has an IP header and a payload, which is the UDP datagram. The UDP datagram consists of a UDP header and a payload, the DNS query. The DNS query, the last level of encapsulation, contains its header and a payload, which is the query itself. This image may help you visualize it: ![](https://hackmd.io/_uploads/H1h01u0Js.png) #### Reading Protocol Headers Now, let's explore these protocols a bit more deeply. You can explore the headers by expanding the respective protocol field. :::success **Task:** Select a DNS packet where the Info section starts with `Standard Query...`. Expand all the fields corresponding to the different levels of the OSI model. Add a text file to your repository called `wireshark_lab.md` in which you will take notes. (This file will be checked by a member of the course staff, so don't worry about the format.) Pick one DNS packet and write down the following: - The packet's *frame number*, which is in the leftmost column of Wireshark's packet display. (This isn't a real packet field, it just gives the order in which Wireshark received the packet) - The source and destination IP addresses - The source and destination ports (you will learn how these ports are used later in the course) ::: For each of these protocols, you can put your cursor over the addresses/ports and see which raw bytes from which these fields were extracted from the packet. Wireshark should highlight these bytes as you hover over the address/port. Wireshark shows more than the source and destination of a packet within a network layer, you can also see protocol-related information. Next, we'll look at the content of a DNS packet, ie. the application-layer information. :::success **Task:** Expand the DNS field of the packet you selected earlier, and completely expand the `Queries` subfield. In this section, you can see the domain being queried (`cs.brown.edu`) and the type of record is wants to find. For this example, the type will either be `A` (for an IPv4 address) or `AAAA` (for an IPv6 address). On expanding the query field, there should be blue text saying `[Response in N]`, where N is another packet number. Click on this text and it should select another packet--this is the DNS server's response to this query! ::: #### DNS Response Next, let's look a bit deeper at the response. Expand the DNS fields in the response, including one of the `Answers` subfields. You sould see an entry for `Name: cs.brown.edu` and some metadata about the answer. Later in the course you will learn exactly what this response means--for now, it's just important to see how the packet bytes can break down into different fields that have meaning for the protocol. Wireshark knows about a huge list of common protocols (like DNS, HTTP, ARP, etc.) and can know how to parse these fields so you can see them! :::success **Task:** Look at the answer fields in the DNS response packet for `cs.brown.edu`. One of the subfields should be "Address", which lists an IP address. In your notes file, list the IP address associated with the response--it should be in the form of an IPv4 address (eg. 10.1.2.3) or an IPv6 addresss (2001:47f::212). ::: If you remove the filter and now look at the subsequent traffic, you should see a TCP connection to the IP address listed in the DNS response. Thus, you can see how `wget` looked up the address for `cs.brown.edu` and then established a connection to it to fetch the web page. ## Closing Tips Use Wireshark! It is extremely helpful in dissecting and understanding the packets that are flying to and from a network interface. For IP you can use it to verify your nodes are sending packets and the packets have the form and fields that you expect. For example, you can use Wireshark to check that within a packet a number is sent with either big endian or little endian, which is much harder to do at the sending, intermediate, or receiving nodes (for example, you may need this functionality to check packet checksums). Wireshark will be very helpful for TCP to check that your nodes are following the TCP protocol. To examine TCP with Wireshark, there will be a separate Wireshark lab during the TCP assignment.