**[&laquo; Back to the main CSCI1680 website](https://cs.brown.edu/courses/csci1680/f22/)** # Project 3: TCP :telephone_receiver: #### Key deadlines **Milestone 1**: Arrange meeting on/before Monday, November 7 **Milestone 2**: Arrange meeting on/before Thursday, November 17 **Final deadline**: **Tuesday, November 22 by 11:59pm EST** ## Introduction In this project, you will implement an RFC-compliant version TCP on top of your virtual IP layer from the previous assignment. In doing so, you will extend your virtual network stack to implement *sockets*, the key networking abstraction at the transport layer that allows hosts to keep track of multiple, simultaneous connections. Thus, you will not only learn about how TCP works, but you'll learn about the OS infrastructure involved to support a networking stack. Students report that this project is very challenging, but also very rewarding. When you are done here, you will really understand TCP, and the challenges involved in building a real-world network protocol. You will work on this project in the same group you used for the IP project, and continuing from your existing codebase and repository. If this would be problematic, please contact Nick ASAP. :::warning **Warning**: You have roughly 4 weeks to complete this assignment, with two intermediate milestone deadlines for you to implement part of the functionality and check in with the course staff. We *strongly* recommend that you **start early** and take full advantage of this time, as you will need it! During this time, we remind you that **we are here to help at all stages of the process**! If you have questions at the design or implementation stage, we *implore* you to ask for help early so that we can make sure you are on the right track. ::: ## The Pieces In the IP project, you implemented handling to deliver virtual IP packets between nodes. To implement TCP, you will build on top of these services to provide TCP as a reliable transport layer to send data between nodes. Your TCP implementation will have four major components: * An interface to create and map packets to *sockets*, with an API which allows "applications" to use sockets to interact with the network * An implementation of the TCP state machine that implements connection setup and teardown * The sliding window protocol that determines what data you are allowed to send and receive at any point * A driver program that will allow you (and us) to test your code :::warning **Note:** Additionally, capstone students must also implement a congestion control algorithm and document its performance. ::: The first three items make up what we call the "TCP stack," which is another "layer" in your node that implements TCP. You can think about how your TCP stack fits into your existing IP implementation based on the figure below: ![](https://hackmd.io/_uploads/SJ0AqjFNi.png) Fundamentally, your TCP stack will be another component in your node that receives packets from the IP layer, similar to how you handled RIP and test packets in the IP project. We will add several new driver commands to create and read from/write to sockets, which act as the "applications" that will use our new transport layer. The ultimate test will be commands to send a file between nodes--your goal is to use your TCP to send a file between nodes and have it received completely and accurately on the other side! In keeping with our emphasis on building good abstractions, you will build a socket API that provides the interface for your driver commands to interact with your TCP stack. This API should be similar to the socket API for the language you are using. Your first step will involve building a high-level for your TCP stack and how to implement sockets, after which you can begin implementing the actual mechanics of TCP. As a guide for your TCP implementation, we will refer you to various IETF RFCs that provide the de-facto standard for TCP implementations. **For more details on how to find information on specific components inside the RFCs, see [Relevant RFCs](https://hackmd.io/Vd75vsE9RCiyXvryvT7YVg?view#Relevant-RFCs).** You are also encouraged to consult our class notes and textbooks, as well as any other online resources---so long as the code you write is your own work. ### Sockets and a Socket API To interface with your TCP stack, you will create a representation for sockets within your node and an API to interact with them. Just like in a real OS, our virtual sockets will allow your node to maintain multiple simultaneous connections with other nodes on which you can send and receive data. To do this, you will build an API for creating and operating on virtual sockets, similar to the socket functions you have been using all semester, such as `Listen`, `Dial`/`Connect`, `Read`, `Write`, `Close`). You will add commands in your driver program that will interact with your sockets using your API, similar to how applications interact with the OS. The API you design should look similar to the socket API in the language you're using and have the basic features for creating listen sockets, connecting to ports, and reading/writing to a socket, and closing the connection. While the API will differ slightly for each language, the required elements described here should be essentially the same. ### Example Socket API To show you what we mean, this section contains an example socket API similar to the API provided by Go. *You do not need to follow this exact specification*, so long as you provide some kind of API that implements the features described here. In this example, we create two structs that represent virtual sockets: - `VTCPListener` represents a listener socket (similar to Go's [net.TCPListener](http://golang.org/pkg/net/#TCPListener)) - `VTCPConn` represents a "normal" socket for a TCP connection between two endpoints (similar to Go's [net.TCPConn](http://golang.org/pkg/net/#TCPConn)) The example below describes the basic socket functions you need to implement, and how they might look when implemented for these types. This API is designed to represent the most basic socket opreations--you are NOT required to implement other extra features that your language's socket API may support, such as `SetReadDeadline` or similar. The main idea here is that an independent thread in your program (ie, outside your TCP stack) should be able to use your socket API in a similar way to how you would use the normal API in your language. In Go, for example, your functions should return an error value that is `nil` on success, or contains an [error](https://pkg.go.dev/builtin#error) with a reasonably descriptive message describing what happened. For more details, see [this example](https://hackmd.io/Vd75vsE9RCiyXvryvT7YVg?view#Error-example). >**Note**: For clarity, we prefix each function's name with `V` to ensure it's clear when we're talking about the virtual socket API in this document--you do not need to adhere to this requirement, we only use it to ensure consistent notation across languages. ```go /* *** FUNCTIONS FOR LISTENING SOCKETS (VTCPListener) *** */ /* * Create a new listening socket bound to the specified port on any * of this node's interfaces. * After binding, this socket moves into the LISTEN state (passive * open in the RFC) * * Returns a TCPListener on success. On failure, returns an * appropriate error in the "error" value */ func VListen(port uint16) (*VTCPListener, error) /* * Waits for new TCP connections on this listening socket. If no new * clients have connected, this function MUST block until a new * connection occurs. * Returns a new VTCPConn for the new connection, non-nil error on failure. */ func (*VTCPListener) VAccept() (*VTCPConn, error) /* * Closes the listening socket, removing it * from the socket table. No new connections may be made * on this socket--any pending requests to establish * connections on this listen socket are deleted. */ func (*VTCPListener) VClose() error /* **** FUNCTIONS FOR NORMAL SOCKETS (VTCPConn) **** */ /* * Creates a new socket and connects to an * address:port (active OPEN in the RFC). * Returns a VTCPConn on success or non-nil error on * failure. * VConnect MUST block until the connection is * established, or an error occurs. */ func VConnect(addr net.IP, port int16) (VTCPConn, error) /* * Reads data from the TCP connection (RECEIVE in RFC) * Data is read into slice passed as argument. * VRead MUST block when there is no available data. All reads should * return at least one byte unless failure or EOF occurs. * Returns the number of bytes read into the buffer. Returned error * is nil on success, io.EOF if other side of connection was done sending, or other error describing other failure cases. */ func (*VTCPConn) VRead([]byte) (int, error) /* * Write data to the TCP connection (SEND in RFC) * * Data written from byte slice passed as argument. This function * MUST block until all bytes are in the send buffer. * Returns number of bytes written to the connection, error if socket * is closed or on other failures. */ func (*VTCPConn) VWrite([]byte) (int, error) /* * Shut down the connection * - If type is 1, close the writing part of the socket (CLOSE in * RFC). This should send a FIN, and all subsequent writes to * this socket return an error. Any data not yet ACKed should * still be retransmitted. * - If type is 2, close the reading part of the socket (no RFC * equivalent); all further reads on this socket should return 0, * and the advertised window size should not increase any further. * - If type is 3, do both. * Reuturns nil on success, error if socket already shutdown or for * other failures. * * NOTE: When a socket is shut down, it is NOT immediately * invalidated--that is, it remains in the socket table until * it reaches the CLOSED state. */ func (*VTCPConn) VShutdown(sdType int) error /* * Invalidate this socket, making the underlying connection * inaccessible to ANY of these API functions. If the writing part of * the socket has not been shutdown yet (ie, CLOSE in the RFC), then do * so. Note that the connection shouldn't be terminated, and the socket * should not be removed from the socket table, until the connection * reaches the CLOSED state. For example, after VClose() any data not yet * ACKed should still be retransmitted. */ func (*VTCPConn) VClose() error ``` :::info <details markdown="1"><summary markdown="span"><b>What is this syntax?</b></u></summary> Go defines method signatures like this: ``` func [(Receiver)] FuncName([arg 0, arg 1, ...]) [(return arg 0, return arg 1, ...)] ``` `Receiver` is used to declare functions as methods of a certain object. For example: ```go type Thing struct { A int } func (t *Thing) AddSomething(x int) { t.A += x } func main() { // Make a thing and call a method on it tt := Thing{A: 41} tt.AddSomething(1) } ``` ::: ### TCP State machine You will implement a state machine that keeps track of the fundamental states of each connection. Here is one representation of the state diagram (courtesy of [Wikipedia](https://commons.wikimedia.org/wiki/File:Tcp_state_diagram_fixed_new.svg)): ![TCP state machine](https://upload.wikimedia.org/wikipedia/commons/thumb/f/f6/Tcp_state_diagram_fixed_new.svg/1280px-Tcp_state_diagram_fixed_new.svg.png) :::info <details markdown="1"><summary markdown="span"><b>What do the labels on the arrows mean?</b></u></summary> State transitions are labeled as `Event/Action`, where `Event` is what triggers the state transition, and `Action` indicates a packet that gets sent in response. For example, in the `SYN_SENT` state, the transition `SYN+ACK/ACK` indicates "When a `SYN+ACK` is received, send an `ACK` and go to the `ESTABLISHED` state." `Events` in **bold** indicate an operation performed by the user (ie, the socket API) using the event terms listed in the RFC (see [Section 3.9.1](https://datatracker.ietf.org/doc/html/rfc9293#section-3.9.1)). For example, in the `SYN_RECEIVED` state, the transition `CLOSE/FIN` means, "when the user performed a `CLOSE`, send a `FIN` and go to the `FIN_WAIT_1` state". ::: The state machine is not as complicated as it may seem at first. To begin, we recommend that you start coding by just using the diagram and getting connections to set up and close under ideal conditions. For the purposes of the assignment, we are asking you to implement a minimal form of TCP that implements *most* of the features that the RFC considers, with a few exceptions to help reduce the complexity. In general, you are expected to follow the state machine and features described by RFC9293, *except* for the parts of referring to: - Simultaneous OPEN - Silly window syndrome (SWS) avoidance - `PSH` flags - Error-related edge cases involving `RST` packets - Urgent data (`URG` flags) - Any TCP options - Precedence - Congestion control (*except* capstone students) - Security considerations Once you have the rest of your implementation working, there are some edge cases to consider: for example, what happens when, after a call to `connect`, you've sent a SYN, but you receive a packet that has an incorrect ACK in it? Once your basic state diagram is working, we recommend that you look at the RFC for answers to questions such as these. In particular, [Section 3.10 of RFC9293](https://datatracker.ietf.org/doc/html/rfc9293#section-3.10) contains info on exactly what you should do in such scenarios for each state. :::success **Tip**: As with the IP project, you don't need to build/parse the TCP header yourself. See the [Implementation Notes](#Implementation-Notes) for details. ::: :::info Not sure if something is required? Ask on Edstem! We are making an effort to expand documentation here, so questions are encouraged! ::: ### Sliding Window Protocol Your sliding window protocol controls how you send and receive data---this is the "heart" of your TCP stack. **Be sure that you can accept out-of-order packets.** That is, a packet's sequence number doesn't have to be exactly the sequence number of the start of the window. It can be fully contained within the window, somewhere in the middle. The easiest way to handle such packets is to place them on a queue of potentially valid packets, and then deal with them once the window has caught up to the beginning of that segment's sequence number. You are not required to implement slow start, but you should detect dropped or un'acked packets and adjust your flow accordingly. You should strictly adhere to the flow control window as specified in the RFC, e.g. do not send packets outside of your window, etc. Similarly, you should implement zero window probing to ensure your sender can recover when the receiver's window is full. Overall, your goal is to ensure reliability---all data must get to its destination in order, uncorrupted. As you implement your protocol, keep in mind how sliding windows will interact with the rest of TCP. For example, a call to CLOSE only closes data flow in one direction. Because data will still be flowing in the other direction, the closed side will need to send acknowledgments and window updates until both sides have closed. ### Driver Your driver should support the following commands to create and work with sockets. <!---As part of your driver, each socket you create should be assigned a number starting from 0, which we call the socket ID. We will use these numbers to refer to sockets in commands. ---> | Command | Description | | ---------------- | -------- | |`h`|Print this list of commands.| |`li`|Print information about each interface, one per line.| |`lr`|Print information about the route to each known destination, one per line.| |`ls`| List all sockets, along with the state the TCP connection associated with them is in, and their window sizes (one should be the socket's receiving window size, and the other should be the peer's receiving window size). | |`a <port>`| Open a socket, bind it to the given `port` on any interface, and start accepting connections on that port. **Your driver must continue to accept other commands.**| |`c <ip> <port>`| Attempt to connect to the given IP address, in dot notation, on the given port. Example: &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; `c 10.13.15.24 1056`. If the connection is established successfully, this command should print out an ID number that is used to refer to the socket for other commands (0, 1, 2, \...).| |`s <socket ID> <data>`|Send a string on a socket. This should block until `VWrite()` returns.| |`r <socket ID> <numbytes> <y\|N>` |Try to read data from a given socket. If the last argument is `y`, then you should block until numbytes is received, or the connection closes. If `n`, then don't block; return whenever and whatever `VRead()` returns. Default is `n`.| |`sd <socket ID> <read\|write\|both>` &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; |`VShutdown` on the given socket. If `read` or `r` is given, close only the reading side. If `write` or `w` is given, close only the writing side. If `both` is given, close both sides. Default is `write`.| |`cl <socket ID>`|`VClose()` on the given socket.| |`sf <filename> <ip> <port>`| Connect to the given `ip` and `port`, send the entirety of the specified file, and close the connection. **Your driver must continue to accept other commands.**| |`rf <filename> <port>`| Listen for a connection on the given `port`. Once established, write everything you can read from the socket to the given file. Once the other side closes the connection, close the connection as well. **Your driver must continue to accept other commands.** Hint: give `/dev/stdout` as the filename to print to the screen.| |`q`|Quit cleanly, closing all open sockets. | :::info **Note**: This list does not include the \"up\" or \"down\" commands (as TCP sockets are rarely well defined when interfaces are changed), but we recommend keeping the code for it. ::: ### Congestion Control (Capstone only) Each student taking this course for capstone is responsible for implementing one of the following congestion control algorithms. Your TCP design should be able to selectively enable and disable any congestion control module that is available, and only 1 congestion control algorithm can be enabled per tcp socket at any given time. If you would like to implement a different congestion control algorithm than the two provided below (since there are many more out there), first seek approval from the staff. The algorithms we recommend are: - **TCP Tahoe:** Slow Start, Congestion Avoidance, Fast Retransmit - **TCP Reno:** TCP Tahoe + Fast Recovery If both students on a team are taking the course for capstone credit, they may not share parts of their code for the congestion control algorithm with each other. All code for each congestion control algorithm must be written individually. Your TCP driver must implement the following commands to demonstrate your congestion control algorithm: | Command | Description | | ------ | ----------- | | `lc` | Prints the available congestion control algorithm names, eg. `reno`, `tahoe`, ... | | `sc <socket ID> <string>` &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; | Sets the congestion control algorithm for the given socket. To disable congestion control, use the string: `none`| |`*s`| You should modify your `ls` command to also list the congestion control algorithm (if any) each socket is using, and the congestion window size as well.| |`*sf`|You should modify your `sf` command to optionally take in a congestion control algorithm, with the options being: `reno`, `tahoe`, .... The default for no argument is `none`.| Lastly, you will be required to provide Wireshark capture files as well as a summary of how your congestion control algorithm fared against your implementation without congestion control. ## Implementation Notes A few notes (skim them now, then return here later): - You must use the TCP packet format, exactly as-is. Like with IP, you are welcome to use a library serialize/deserialize the TCP header for you. One example library in Go is the [netstack's package `TCPFields` struct](https://pkg.go.dev/github.com/google/netstack@v0.0.0-20191123085552-55fcc16cd0eb/tcpip/header#TCPFields)--though you are free to use other libraries. [**Take a look at the TCP-in-IP example for a demo of how to use this library to build/parse TCP packets.**](https://github.com/brown-csci1680/lecture-examples/tree/main/tcp-demo) In C, you should use the header found in `netinet/tcp.h` ([demo here](https://github.com/brown-csci1680/lecture-examples/tree/main/tcp-checksum)). - There are several places in the RFCs that leave room for flexibility in implementation. We extend the same flexibility to your projects, as long as you can justify your design decisions (in a `README`). A good rule of thumb is to be liberal in what you accept but conservative in what you output. For example, your program shouldn't crash if, say, a packet has the URG flag set, even if you don't support it (instead, just ignore the flag, and set it to 0 for packets you send). - TCP uses a "pseudo-header" in its checksum calculation. Make sure you understand how TCP checksumming works to ensure interoperability with the reference implementation. Section 3.1 of RFC9293 or [this link](http://www.tcpipguide.com/free/t_TCPChecksumCalculationandtheTCPPseudoHeader-2.htm) may be helpful. **A code example about this will be posted soon.** - You MUST NOT use arbitrary sleeps or busy-waiting to handle sending/receiving packets. For example, you might have a thread which takes care of sending out packets for a particular socket. You MUST NOT have this thread check whether there is something to be sent every, say, 1 ms--this may unnecessarily wake up the thread and waste a lot of CPU cycles! Instead, your thread should send whenever data is available, or or when a retransmission timer expires. Channels, mutexes, and condition variables are your friends. - As in the IP assignment, never send packets greater than the MTU. For our link layer, the maximum MTU is 1400 bytes: any TCP segments you send must be no larger than the MTU--therefore, **the maximum TCP payload size is**:<br /> `1400 bytes - (size of IP header) - (size of TCP header)` - **You don't have to handle any TCP options.** You should ignore any options that you see in incoming packets, but your program shouldn't crash if you encounter them. - **When should `VConnect()` timeout?** A good metric is after 3 re-transmitted SYNs fail to be ACKed. The idea is that if your connection is so faulty that 4 packets get dropped in a row, you wouldn't do very well anyway. How long should you wait in between sending SYNs? You can have a constant multi-second timeout, e.g. 3 seconds. Or, you can start off at 2 seconds, and double the time with each SYN you retransmit. - The RFC states that a lower bound for your RTO should be 1 second. This is way too long! A common RTT is 350 microseconds for two nodes running on the same computer. Use 1 millisecond as the lower bound, instead. By a similar principle, you do not need to be overzealous in precisely measuring RTT; it is reasonable to tolerate small processing delays (1-10ms). - Debugging TCP can be very difficult---we strongly recommend using Wireshark to observe your TCP connections. Wireshark has many tools to help analyze TCP connection state, which may prove useful---we will discuss a few of these in class. If you have questions how to use wireshark to accomplish a particular task, please feel free to ask! - Log as much as you can, and make it possible to filter out what you care about. For example, you may only want to log information related to a specific connection, or you may only want to see logs from TCP, and not IP. ## Grading ### Milestone I -- 5% (by November 7) Similar to the IP milestone, you will complete this part by scheduling a meeting with the course staff (preferably your mentor TA) on or before **Monday, November 7**. For this meeting, your implementation should be able to demonstrate the following: - Establishing new connections by properly following the TCP state diagram under *ideal conditions*. Connection teardown is **NOT** required for this milestone. - When creating new connections, you should allocate a data structure pertaining to the socket---be prepared to discuss what you need to include in this data structure for the rest of your implementation - To test establishing new connections, you should implement the **`a`**, **`c`**, and (partially) `ls` commands in your TCP driver to listen for, create, and list connections, respectively. For the `ls` command, you need not list the window sizes for the milestone. - You should be able to view your TCP traffic in Wireshark to confirm you are using the packet header correctly. However, **you do not need to compute the TCP checksum yet**. You can run the reference node with checksum validation disabled by passing the flag `--disable-checksum`. - Correct operation even with another node in between the two endpoints. This should already be possible if your IP implementation is implementing forwarding properly---but you should check this at this stage to help rule out any lingering bugs. In addition, try to consider how you will tackle these problems, which we will discuss: - What does a \"SYN\" packet or a \"FIN\" packet do to the receiving socket (in general)? - What data structures/state variables would you need to represent each TCP socket? - How will you map incoming packets to sockets? - What types of *events* do you need to consider that would affect each socket? - How will you implement retransmissions? - In what circumstances would a socket allocation be deleted? What could be hindering when doing so? Note that the state CLOSED would not be equivalent as being deleted. ### Milestone II -- 20% (by November 17) You should schedule a subsequent milestone checkin with the course staff on or before **Thursday, November 17**. For this meeting, students should have the send and receive commands working over non-lossy links. That is, send and receive should each be utilizing the sliding window and ACKing the data received to progress the window. This also means that sequence numbers, circular buffers, etc. should be in place and working. Retransmission, connection teardown, packet logging and the ability to send and receive at the same time are not yet required. ### Basic Functionality -- 55% As usual, most of your grade depends on how well your implementation adheres to these specifications. Some key points: - Properly follow the state diagram. - Adhere to the flow control window. - Re-transmit based on a computed RTO interval (based on SRTT) - Send data reliably--files sent across your network should arrive at the other end identical to how they were sent, even if the links in between the two nodes are lossy. - Follow the RFC in corner cases. You may ignore error-related edge cases that normally require RST packets, as well as any of the features listed in the ["TCP state machine"](#tcp-state-machine) section. If you have questions on whether you need to handle a certain edge case, just ask! Overall, we don't want you to be bogged down handling more edge cases than necessary to have a working implementation that can tolerate lossy links. The idea is that having full basic functionality means that any existing valid TCP implementation should be able to talk with yours and eventually get data across, regardless of how faulty the link is. ### Documentation and Performance Measurement -- 20% We want you to understand how your design decisions affect your TCP's behavior. In your `README`, you should document your major design decisions and your reasoning for using them. In addition, we ask that you also include in your `README` a brief analysis of your TCP traffic, as follows: **Measuring performance**: When you submit, we also ask that you investigate your implementation's performance. However, we are **NOT** asking you to highly optimize your implementation and meet a certain performance goal. While performance is key in all systems design, spending many iterations on optimizing your design is beyond the scope of this project. Instead, we only ask that you *measure* your implementation's performance relative to the reference node and comment on it in your `README`. Since our implementation runs on a single host's loopback network, performance is based entirely on CPU speed. To get a baseline for performance, run two reference nodes connected directly to each other with no packet loss and compare the time to send a file of a few megabytes in size (you can also directly measure the throughput in Wireshark). Your implementation should have performance on the same order of magnitude as the reference under the same test conditions. **Packet capture**: Finally, you should submit a packet capture of a 1 megabyte file transmission between two of your nodes. To do this, run two of your nodes in the ABC network with the lossy node in the middle, configured with a 2% drop rate. After filtering your packet capture to show only one side of the transmission, you should "annotate" the following items in the capture file: - The 3-way handshake - One example segment sent and acknowledged - One segment that is retransmitted - Connection teardown To do this, list the frame numbers for each item in your `README` with a description. For each annotation, you should evaluate if your implementation is responding appropriately per the specification. If you notice any issues, you should document them accordingly. An example packet capture will be demonstrated in class before the deadline. Capstone students will also need to provide packet captures for their congestion control implementation. For these you should run your congestion control algorithm in a drop-free network, and also run it with the faulty node in the middle. Similarly, you should run your algorithm with no other competition, as well as run multiple instances of your algorithm intermixed with simple flow control TCP streams simultaneously. In your write up you should explain the behaviour of your node in all these situations, and try to explain the strengths and weaknesses of your algorithm. ## Getting Started Since this project is a continuation of your work from the IP assignment, you should continue development in your IP repository. ### Development Environment Similar to IP, you should work on this project using the [course container environment](https://cs.brown.edu/courses/csci1680/f22/pages/container_setup/). :::success **Note**: **Wireshark will be extremely helpful for debugging your work in this project. See [this section](#using-wireshark-for-this-project) for some important instructions on how to use it for TCP.** If you have issues running the container environment, or Wireshark, (ie, if it's slow, hard to use, etc.)--**please talk to us and let us know!** This is a relatively new element of the course: we are happy to help debug issues or work on improving your workflow, but we'll only know about issues if you come to us! ::: ### Reference Implementations We have provided a few additional reference binaries for this assignment, which are available here: <https://github.com/brown-csci1680/ip-tcp-starter/tree/main/tcp_tools>. Please copy these files into your IP repository---there is no need to start a new repository for this assignment. :::warning **M1 mac users**: Please use the binaries located in the `arm64` directory instead. ::: #### Lossy Network Node (`ip_node_lossy`) The starter repository contains an IP node called `ip_node_lossy` that can be configured to drop a fraction of outgoing packets. This will be useful when testing your retransmission and timeout logic. You can specify the drop rate with the command "lossy". The drop rate should be a value between 0.0 and 1.0, where 1.0 means every packet will be dropped by the node. #### TCP Reference Implementation (`ref_tcp_node`) The starter repository also contains a reference TCP implementation. We must emphasize that your node **MUST** be able to operate with the reference node, so please test using this node frequently! In addition, you should fix any lingering issues in IP preventing your node from working with the reference IP node. If you have questions on how to do this, please contact the course staff--we can help! Note that the reference implementation does not implement congestion control :::warning **Warning**: Our current reference implementation has a few quirks where it deviates from our current spec. See [this section](https://hackmd.io/Vd75vsE9RCiyXvryvT7YVg?view#Reference-node-known-issues) for a list. ::: #### IP Reference implementation (by request only) If you do not feel confident in extending your work from the previous assignment to support TCP, please talk to us. We can advise you on critical areas of your IP ipmlementation to prioritize, or we may be able to provide a reference implementation that you can use instead. If you are interested in this, please contact the course staff. ### Relevant RFCs The original standard for TCP (RFC793) was released in 1981, and has been amended and extended over the past four decades by many others. Accordingly, in the past, this project has required sifting through a lot of RFCs. **However**: this year, there is finally a better way. As of August 2022, a *new* RFC has been forged that consolidates four decades of updates into a new document: [RFC9293](https://tools.ietf.org/html/rfc9293). This should be a definitive source for most information about most parts of your implementation. Otherwise, the list below lists how to find the most important parts of the other RFCs--**this list is now obsolete, you can probably get away with only RFC9293 now. We will be checking this in the next couple of days and will update this soon!** Links: - ✨✨ **[RFC9293](https://tools.ietf.org/html/rfc9293)** ✨✨ is the most definitive source - [RFC 793](https://tools.ietf.org/html/rfc793) - [RFC 1122](https://tools.ietf.org/html/rfc1122) - [RFC 5681](https://tools.ietf.org/html/rfc5681) - [Beej's Guide to Network Programming](http://beej.us/guide/bgnet/) #### Introduction RFC 793: pp. 4-5, 2.6, 2.7 and 2.10. #### State Machine RFC 793: pp. 27-28 (Initial Sequence Number Selection), 3.4, 3.5, 3.8, 3.9 and RFC 1122: section 4.2.2.9, 4.2.2.10 #### Sliding Window Protocol RFC 793: section 3.2, 3.3, 3.7, 3.9 and RFC 1122: section 4.2.2.16, 4.2.2.17, 4.2.2.20, 4.2.2.21 #### Congestion Control RFC 5681 #### API RFC 793: section 3.8; Beej's Guide to Network Programming: Ch. 5 #### TCP Header format RFC 793: section 3.1 and RFC 1122: section 4.2.2.3 #### RTT and Re-transmission Timeout RFC 793: p. 41 and RFC 1122: section 4.2.3.1 ## Handing In and Interactive Grading Before each milestone and before the final deadline, once you have completed the requirements for that part of the project, you should commit and push your Git repository. Your mentor TA will arrange to meet with you for each interactive grading session (milestones and final demo) to demonstrate the functionality of your program and grade the majority of it. This meeting will take place at some point shortly after the project deadline. Between the time you've handed in and the final demo meeting, you can continue to make minor tweaks and bug fixes. However, the version you've handed in should be nearly complete since it could be referenced for portions of the grading. ## Final Thoughts Once again, we highly recommend getting started on this assignment soon---don't wait to start until a few days before the deadlines! If you have questions, or need help with your implementation, always feel free ask for help or clarification or debugging. This is a hard assignment, but we are here to help you and provide resources, and we will be most effective at doing so if you ask early! Although we expect compatibility between your TCP implementation and our own, do not get bogged down in the RFC from the start. It is much more important that you understand how TCP works on an algorithmic/abstract level and design the interface to your buffers from your TCP stack and from the virtual socket layer. For any corner cases or small details, the RFC will be your best friend, and our reference implementation should come in handy. Always feel free to consult the course staff if you have any questions about what you are required to do, or how to handle corner cases. It is **not OK** to just make assumptions as to how things will work, because we will be testing your code for interoperability with the reference node and other groups in the class. [^1]: <http://ttcplinux.sourceforge.net/documents/one/tcpstate/tcpstate.html> [^2]: <https://datatracker.ietf.org/doc/html/rfc793> [^3]: <https://datatracker.ietf.org/doc/html/rfc2525> [^4]: <http://www.tcpipguide.com/free/t_TCPChecksumCalculationandtheTCPPseudoHeader-2.htm> # Version history Any updates to the assignment after release will be listed here: * 29 October 2022: Added link to [TCP-in-IP example](https://github.com/brown-csci1680/lecture-examples/tree/main/tcp-demo) * 28 October 2022: Initial release # Appendix This section contains various notes for certain project components. ## Using Wireshark for this project For instructions on using Wireshark with this project, please see [Lab 2](https://hackmd.io/@csci1680/lab2)--all of the instructions on using Wireshark apply here (such as capture filters, "Decode As" rules, etc.). In addition, there are two more small items you will need to configure, as described below: ### Watching a TCP connection Wireshark has a LOT of great tools for monitoring TCP connection state. However, Wireshark can get a bit confused by our virtual IP network: since we have multiple nodes running on the same machine, and Wireshark by default sees all packets from all nodes--which confuses its TCP analyses. To fix this, we can [**use a capture filter when you start Wireshark**](https://hackmd.io/@csci1680/lab2#Viewing-virtual-links-in-Wireshark) to restrict it to see only traffic for only one node. For example: * **To look at traffic only entering/exiting node A, you would apply the filter `udp port 5000`**. * To look at node B, use the capture filter `udp port 5001`, etc. In most cases, it should be sufficient for debugging to only look at traffic for one node. **[For instructions on configuring capture filters, see here.](https://hackmd.io/@csci1680/lab2#Viewing-virtual-links-in-Wireshark)** ### Validating the TCP checksum Wireshark can validate the TCP checksum, similar to how it validates the IP checksum. To configure this, right-click on a TCP header and select **Protocol Preferences...** > **Validate TCP checksum if possible**. For visual instructions, please see **this section** of lab 2--just start by clicking on a TCP header instead. ## Reference node known issues There are a few places where our current reference node deviates from the specification described in the assignment. Any known issues will be listed here. :::danger **Note**: **if the reference does something listed here that doesn't agree with this document, you should follow the document**. If you find any other issues, please post on Edstem and we will review them! ::: * In the reference, the `c` command is non-blocking: that is, when establishing a TCP connection, it doesn't wait for the other side to respond (or an error to occur) before returning. **This behavior is not required**: your implementation for `VConnect` (and the `c` command) should block until the connection is `ESTABLISHED`, or an error occurs. * The `cl` command immediately removes a socket from the socket table, regardless of the current connection state. Instead, `cl` should trigger a shutdown on both the reading and writing side of the socket, if this has not yet been done so already, and then return. Then, the socket should only be removed from the socket table when it enters the `CLOSED` state. ## Error example Here's an example function that returns a value and an optional custom error message. For more details on creating errors, see the [errors](https://pkg.go.dev/errors) library. Another good way is to look at go's own source code for function in the `net` package. ```go func SqrtPositiveOnly(num int) (int, error) { if num < 0 { // Error return nil, errors.New("Value cannot be negative") } else { // Success return math.Sqrt(num), nil } } ``` ## Example socket API for C This section describes a virtual socket API similar to C's socket API. In C, you should create new connections as virtual sockets using your own socket table and virtual file descriptors to allow connecting and listening, reading and writing into buffers, etc. An independent thread in your program should be able to use this your socket API in a similar way to how you would use the normal API in your language. These functions, on error, should return appropriate error codes (such as negative values in C, error values in Go, etc.). For C, you should use standard error codes (such as `EBADF`). ```c /* creates a new socket, binds the socket to an address/port If addr is nil/0, bind to any available interface After binding, moves socket into LISTEN state (passive OPEN in the RFC) returns socket number on success or negative number on failure Some possible failures: ENOMEM, EADDRINUSE, EADDRNOTAVAIL (Note that a listening socket is used for "accepting new connections") */ int v_listen(struct in_addr *addr, uint16_t port); /* creates a new socket and connects to an address (active OPEN in the RFC) returns the socket number on success or a negative number on failure You may choose to implement a blocking connect or non-blocking connect Some possible failures: EAGAIN, ECONNREFUSED, ENETUNREACH, ETIMEDOUT */ int v_connect(struct in_addr *addr, uint16_t port); /* accept a requested connection from the listening socket's connection queue returns new socket handle on success or error on failure. if node is not null, it should fill node with the new connection's address accept is REQUIRED to block when there is no awaiting connection Some possible failures: EBADF, EINVAL, ENOMEM */ int v_accept(int socket, struct in_addr *node); /* read on an open socket (RECEIVE in the RFC) return num bytes read or negative number on failure or 0 on eof and shutdown_read nbyte = 0 should return 0 as well read is REQUIRED to block when there is no available data All reads should return at least one data byte unless failure or eof occurs Some possible failures : EBADF, EINVAL */ int v_read(int socket, void *buf, size_t nbyte); /* write on an open socket (SEND in the RFC) return num bytes written or negative number on failure nbyte = 0 should return 0 as well write is REQUIRED to block until all bytes are in the send buffer Some possible failures : EBADF, EINVAL, EPIPE */ int v_write(int socket, const void *buf, size_t nbyte); /* shutdown an connection. If type is 1, close the writing part of the socket (CLOSE call in the RFC. This should send a FIN, etc.) If 2 is specified, close the reading part (no equivalent in the RFC; v_read calls should return 0, and the window size should not grow any more). If 3 is specified, do both. The socket is NOT invalidated. returns 0 on success, or negative number on failure If the writing part is closed, any data not yet ACKed should still be retransmitted. Some possible failures : EBAF, EINVAL, ENOTCONN */ int v_shutdown(int socket, int type); /* Invalidate this socket, making the underlying connection inaccessible to ANY of these API functions. If the writing part of the socket has not been shutdown yet, then do so. The connection shouldn't be terminated, though; any data not yet ACKed should still be retransmitted. Some possible failures : EBADF */ int v_close(int socket); } ```