A minimal TCP-like reliable protocol built on top of UDP, implemented in Rust. Implements slow start, congestion avoidance, retransmissions, and simulated packet/ACK loss for educational purposes.
It is necessary to have installed the Rust programming language to run this repository, If you do not have Rust yet installed, simply run:
curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh
Run the server in one terminal:
make serverRun the client in another terminal:
make clientThe protocol uses a simplified 8-byte TCP header over UDP:
| Field | Size | Description |
|---|---|---|
| Sequence number | 16 bits | First byte of data in this segment (or ISN when SYN is set) |
| Acknowledgment number | 16 bits | Next sequence number the sender expects to receive |
| Receive buffer | 16 bits | Not used |
| Data length | 13 bits | Number of payload bytes in this segment |
| ACK flag | 1 bit | Acknowledgment number is valid |
| SYN flag | 1 bit | Synchronise sequence numbers (connection establishment) |
| FIN flag | 1 bit | No more data from sender (connection teardown) |
Constants:
- MSS = 1024 bytes (payload only; full packet = 1032 bytes with header)
- RTO = 500 ms
- Initial ssthresh = 15 360 bytes
- Three-way handshake — client sends SYN, server replies with SYN-ACK (carrying the server's ISN), client replies with ACK. Both sides are then
Established. - Data transfer — client sends data using a sliding window controlled by CWND.
- Teardown — client sends FIN, server replies with FIN-ACK, client sends final ACK.
The client implements TCP Tahoe:
Active when cwnd < ssthresh. On every ACK received:
cwnd += 1 MSS
CWND doubles each RTT (exponential growth) until it reaches ssthresh.
Active when cwnd >= ssthresh. On every ACK received:
cwnd += MSS² / cwnd
This grows CWND by approximately 1 MSS per RTT (linear growth).
When the RTO expires with unacknowledged segments in flight:
ssthresh = cwnd / 2 (floor: 2 × MSS)
cwnd = 1 MSS
The sender retransmits the oldest unacknowledged segment and re-enters slow start.
Both sides independently simulate network loss to exercise retransmission logic:
| Constant | Location | Default | Effect |
|---|---|---|---|
SIM_LOSS_RATE |
client/src/client.rs |
0.1 (10 %) |
Silently drops initial sends; retransmissions always go through |
SIM_ACK_LOSS_RATE |
server/src/server.rs |
0.05 (5 %) |
Silently drops ACKs sent by the server |
Set either constant to 0.0 to disable that side's simulation.
The client emits structured log fields at every significant event:
| Event | Key fields |
|---|---|
| Transfer start | cwnd, ssthresh, total_segments, total_bytes |
| Segment sent | seq, ack, data_len |
| Simulated drop | seq, loss_rate |
| ACK received | ack_num, acked_bytes, in_flight, cwnd, ssthresh, rtt_ms, throughput_kbps |
| CWND growth (slow start) | old_cwnd, cwnd, ssthresh, mode |
| CWND growth (cong. avoid.) | old_cwnd, cwnd, ssthresh, increment, mode |
| Mode transition | old_cwnd, cwnd, ssthresh |
| Timeout / retransmit | old_cwnd, old_ssthresh, cwnd, ssthresh, retransmit_count |
| Transfer complete | cwnd, ssthresh, retransmissions, elapsed_ms, throughput_kbps |
The server logs in-order ACKs, duplicate ACKs, and simulated ACK drops.
Log verbosity is controlled by the RUST_LOG environment variable (e.g. RUST_LOG=debug).