**[« 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: `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>` |`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>` | 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);
}
```