DEV Community

Cover image for UDP Can Lose Your Packets. So I Made It Reliable.
Akash Santra
Akash Santra

Posted on

UDP Can Lose Your Packets. So I Made It Reliable.

UDP Can Lose Your Packets. So I Made It Reliable.

UDP is fast and simple.

But it also makes very few promises:

  • No delivery guarantee
  • No ordering guarantee
  • No retransmission
  • No duplicate protection

So I started wondering:

What would it actually take to make UDP reliable?

Instead of only reading about how TCP handles reliability, I decided to build a small reliable transport layer on top of UDP.

This isn't a replacement for TCP.

It isn't production-ready either.

It's a small experiment to understand what actually happens underneath reliable communication.


The Problem

When data is sent over UDP, the sender doesn't know whether a packet actually reached the receiver.

A packet can be lost somewhere in the network.

It can also arrive later than another packet, arrive more than once, or never arrive at all.

That creates several problems:

  • How do we identify each packet?
  • How do we know whether a packet arrived?
  • How do we detect a missing packet?
  • How do we resend it?
  • How do we handle packets arriving out of order?
  • How do we handle duplicate packets?
  • How do we send multiple packets efficiently?

These are the problems I wanted to solve.


1. Sequence Numbers

The first requirement is being able to identify individual packets.

Each packet gets a sequence number.

This gives the receiver a way to determine the position of each packet and detect missing or duplicated data.

For example, if packets 1, 3, and 4 arrive, the receiver can determine that packet 2 hasn't arrived yet.

Sequence numbers are therefore useful for:

  • Detecting missing packets
  • Detecting duplicates
  • Detecting out-of-order packets
  • Maintaining the correct order of data

2. Acknowledgements

The sender also needs feedback from the receiver.

That's where acknowledgements, or ACKs, come in.

When the receiver successfully receives a packet, it sends an acknowledgement back to the sender.

The sender can then mark that packet as successfully delivered.

Without acknowledgements, the sender has no direct way to know whether a packet reached its destination.

But acknowledgements introduce another problem.

What happens if the packet arrives, but the ACK itself gets lost?

That's where timeouts become important.


3. Timeouts

The sender cannot wait forever for an acknowledgement.

So after sending a packet, it starts a timer.

If the expected ACK arrives before the timer expires, the packet is considered successfully delivered.

If the timer expires without receiving the expected ACK, the sender assumes that the packet or its acknowledgement may have been lost.

This triggers the next mechanism:

retransmission.


4. Retransmission

When a packet is considered lost, the sender sends it again.

This is the basic idea behind retransmission.

The sender keeps track of packets that haven't been acknowledged.

When a timeout occurs, the corresponding packet can be retransmitted.

This means that even though UDP itself doesn't guarantee delivery, our additional layer can attempt to recover from packet loss.

Of course, real transport protocols need much more sophisticated loss detection and recovery mechanisms.

But this simple mechanism makes the idea much easier to understand.


5. Handling Out-of-Order Packets

Packet loss isn't the only problem.

Packets can also arrive in a different order from the order in which they were sent.

For example, packet 3 might arrive before packet 2.

If the application receives the data immediately, it could process the information in the wrong order.

Sequence numbers allow the receiver to recognize this situation.

Instead of immediately delivering every packet, the receiver can temporarily buffer packets that arrive early and wait for the missing data.

Once the required packets are available, they can be reordered and delivered to the application in the correct sequence.


6. Handling Duplicate Packets

Retransmission creates another interesting situation.

A packet may have successfully reached the receiver, but its ACK might have been lost.

The sender doesn't know that the packet already arrived.

Eventually, the timeout expires and the sender retransmits the same packet.

Now the receiver gets the same packet again.

This is a duplicate.

Sequence numbers allow the receiver to recognize that it has already processed that packet.

Instead of delivering the same data twice, it can ignore the duplicate while still handling the acknowledgement appropriately.


7. Sliding Window

There is still another problem.

What if we send one packet, wait for its ACK, then send the next packet?

That would work, but it would waste a lot of time waiting for the network.

A sliding window allows multiple packets to be in flight at the same time.

Instead of waiting for every individual acknowledgement, the sender can transmit several packets and keep track of which ones have been acknowledged.

As acknowledgements arrive, the window moves forward and new packets can be sent.

This improves throughput because the sender doesn't have to stop after every packet.


8. Putting Everything Together

At this point, the reliable layer has several responsibilities:

  • Sequence numbers identify packets.
  • ACKs confirm successful delivery.
  • Timeouts detect packets that may have been lost.
  • Retransmission attempts to recover lost packets.
  • Reordering handles packets that arrive in the wrong order.
  • Duplicate detection prevents the same data from being processed twice.
  • Sliding windows allow multiple packets to be in flight.

UDP remains underneath all of this.

The network is still capable of losing, delaying, duplicating, or reordering packets.

We're simply adding another layer that knows how to deal with those problems.


Why Not Just Use TCP?

This was never about replacing TCP.

TCP already provides reliable and ordered delivery, along with many other mechanisms for controlling how data moves across a network.

The purpose of this project was different.

I wanted to understand what the word "reliable" actually means at the protocol level.

It's easy to remember:

TCP = reliable

UDP = unreliable

But that doesn't explain much.

Once you start implementing the mechanisms yourself, the abstraction becomes much clearer.

Reliability isn't one feature.

It's a collection of mechanisms working together.


Then QUIC Started Making More Sense

This experiment also made QUIC much easier to understand.

HTTP/3 uses QUIC, and QUIC runs over UDP.

At first, that can sound strange.

If UDP doesn't guarantee delivery, why would a modern web protocol use it?

The important point is that UDP itself doesn't suddenly become reliable.

QUIC provides its own transport functionality above UDP, including reliable delivery, streams, flow control, congestion control, and TLS integration.

That's why the simplified HTTP/3 stack looks different from the traditional HTTP stack.

HTTP traditionally sits on top of TCP.

HTTP/3 sits on top of QUIC, which runs over UDP.

The reliability hasn't disappeared.

It's handled by QUIC.

That distinction became much easier to understand after building a small version of the underlying idea myself.


TCP vs My Reliable UDP

My implementation is obviously nowhere near the complexity or maturity of TCP.

But the comparison is useful for understanding the basic responsibilities involved.

Feature UDP My Implementation TCP
Delivery guarantee No Yes Yes
Ordered delivery No Yes Yes
Sequence numbers No Yes Yes
ACKs No Yes Yes
Retransmission No Yes Yes
Duplicate handling No Yes Yes
Sliding window No Yes Yes
Flow control No Limited Yes
Congestion control No Limited Yes

The important takeaway is that reliable communication requires much more than simply sending data from one machine to another.


What I Built

The project focuses on implementing the core mechanisms needed for reliable communication over UDP.

The main components are:

  • Sequence numbers
  • ACK handling
  • Timeout detection
  • Retransmission
  • Packet reordering
  • Duplicate handling
  • Sliding-window transmission

The goal wasn't to recreate TCP.

The goal was to make the abstraction visible and understand the ideas behind reliable transport.


What I Learned

Before working on this, it was easy to think about networking in terms of definitions:

  • TCP is reliable.
  • UDP is fast.
  • TCP uses acknowledgements.
  • UDP doesn't guarantee delivery.

After implementing the mechanisms, those definitions started to feel much less abstract.

I could see the problem each mechanism was trying to solve.

Sequence numbers solve identification and ordering.

ACKs provide feedback.

Timeouts help detect missing communication.

Retransmission provides recovery.

Buffering handles out-of-order delivery.

Duplicate detection prevents repeated processing.

Sliding windows improve efficiency.

Each mechanism exists because the network can fail in a different way.


The Bigger Lesson

The most useful thing I got from this project wasn't another definition of TCP or UDP.

It was learning to ask:

Why does this mechanism exist?

Instead of memorizing that a protocol uses sequence numbers, I can now think about the problem sequence numbers are solving.

Instead of memorizing that retransmission exists, I can think about what happens when data disappears.

That shift from remembering features to understanding the problems behind them made networking much more interesting for me.


Final Thought

One of the best ways I've found to understand a protocol is to remove some of the abstraction and rebuild a small part of it.

Start with something simple:

Send data.

Then ask:

What if the packet is lost?

What if the ACK is lost?

What if the packet arrives twice?

What if packets arrive out of order?

What if the network becomes congested?

Each problem leads to another mechanism.

And eventually, the protocol starts to make sense.

Don't just learn what a protocol does. Try rebuilding the reason it exists.

Top comments (0)