Case study · Systems programming

microTCP

A reliable transport protocol written in C on top of UDP. Handshake, ordering, integrity, flow control and congestion control, implemented rather than imported.

The microTCP transport protocol project
A transport stack small enough to read end to end, and complete enough to move a file reliably over a lossy link.
100/100 Course grade
C Over raw UDP sockets
CRC32 Per-segment integrity

The problem

UDP gives you exactly one guarantee: none. Datagrams arrive out of order, arrive twice, arrive corrupted, or never arrive at all, and the socket will not tell you which happened.

Everything a developer takes for granted when they open a TCP connection — that bytes come out in the order they went in, once each, unmodified — is machinery someone had to build. The exercise here was to build it.

The goal

A working transport layer with the shape of TCP: connection setup and teardown, reliable in-order delivery, detection of corruption, a sender that respects what the receiver can absorb, and a sender that backs off when the network is congested.

Small enough to read in an afternoon, complete enough that a file transferred over it arrives byte-identical on a link that is deliberately dropping packets.

What I implemented

  • Connection handshake and teardown — the state machine, including the states that only exist to handle the message that gets lost.
  • Reliable delivery with sequence and acknowledgement numbers, retransmission, and duplicate detection.
  • CRC32 integrity per segment, so corruption is detected rather than silently delivered.
  • Segmentation of the application's byte stream into datagrams and reassembly on the far side.
  • Flow control — a receive window the sender must respect.
  • Congestion control — slow start and congestion avoidance, so loss reduces the sending rate instead of amplifying it.

What the project actually teaches

Three things that are obvious in a textbook and not at all obvious in an implementation.

Flow control and congestion control are different problems

They both slow the sender down, which makes them easy to conflate. One protects the receiver from being overrun; the other protects the network between them. They have separate windows, separate signals, and the effective send rate is bounded by both.

A checksum is not error correction

CRC32 tells you a segment is wrong. It does not tell you which bit, and it cannot recover the data. Integrity checking only has value because it is wired into retransmission: detection without a recovery path just turns silent corruption into a loud failure.

Most of the state machine exists for failure

The happy path through connection setup and teardown is a handful of transitions. Nearly everything else is there because the last message might not arrive, and both sides have to reach a consistent conclusion anyway.

What was hard

Debugging a protocol is harder than debugging a program, because the bug is not in one place. It is in the disagreement between two processes about what has already happened, and reproducing it means reproducing a timing and loss pattern rather than an input.

The part that took longest was retransmission timing. Wait too long and throughput collapses; retransmit too eagerly and you add traffic to a link that is already dropping packets, which is precisely the wrong response.

The result

Graded 100/100, with the full C source and build on GitHub.

It is the project I would point to if someone wanted to know whether I understand what is happening underneath an HTTP request, rather than only how to make one.

Have a project in mind?

I take on client websites, web apps, and product builds across Cyprus and Greece.

Start a conversation