Computer Networks

The Transport Layer

UDP

PORTS so the packet reaches a program

JrCodex·7 min read

Jr Codex Computer Networks Notes

Level: Intermediate Prerequisites: Chapter 1: Ports, Sockets and Multiplexing Time to complete: ~15 minutes


Table of Contents

  1. The Minimal Transport
  2. The Header
  3. What UDP Does Not Do
  4. When Less Is More
  5. Writing UDP Code
  6. Building Reliability on UDP
  7. Summary & Next Steps

1. The Minimal Transport

What UDP Adds to IP
─────────────────────────────────────────
  PORTS       so the packet reaches a program
              (Chapter 1)
  A CHECKSUM  optional in IPv4, mandatory in IPv6
  A LENGTH    field

  That is all. Four fields, eight bytes.

  UDP is IP with process addressing bolted on. It
  inherits every one of IP's non-guarantees.
─────────────────────────────────────────
The Design Philosophy
─────────────────────────────────────────
  UDP is not "TCP without the good parts". It is a
  deliberate choice to give the application FULL
  CONTROL.

  TCP's guarantees cost latency and impose
  behaviour. For some applications those costs
  exceed the benefit, and the application can do
  better with its own logic.

  UDP exists so that those applications are not
  forced to pay.
─────────────────────────────────────────

2. The Header

Eight Bytes
─────────────────────────────────────────
   0                16                 31
  ┌─────────────────┬──────────────────┐
  │   Source Port   │ Destination Port │
  ├─────────────────┼──────────────────┤
  │     Length      │     Checksum     │
  └─────────────────┴──────────────────┘

  Compare TCP's 20-byte minimum header, with
  sequence numbers, acknowledgement numbers, flags
  and a window (Chapter 3).

  Every one of those fields exists to support a
  guarantee UDP does not make.
─────────────────────────────────────────
The Checksum's Pseudo-Header
─────────────────────────────────────────
  UDP's checksum covers the data, the UDP header,
  AND a PSEUDO-HEADER containing the source and
  destination IP addresses from the IP layer.

  WHY: to detect a packet misdelivered to the wrong
  host. Without it, a corrupted IP address could
  route a packet to the wrong machine, which would
  accept it as valid.

  It is a deliberate small violation of layering
  (Module 1, Chapter 1), for a good reason. TCP
  does the same.
─────────────────────────────────────────

3. What UDP Does Not Do

The Missing Guarantees
─────────────────────────────────────────
  NO DELIVERY GUARANTEE
    A datagram may be lost. Nothing notices, and
    nothing retransmits.

  NO ORDERING
    Datagrams may arrive in any order — a later one
    may take a faster route.

  NO DUPLICATE DETECTION
    A datagram may arrive twice.

  NO CONNECTION
    No handshake, no state, no teardown.

  NO FLOW CONTROL
    A fast sender will overwhelm a slow receiver.

  NO CONGESTION CONTROL
    UDP does not slow down when the network is
    congested. If it did, it would not be UDP.
─────────────────────────────────────────
THE CONGESTION WARNING
─────────────────────────────────────────
  This last one is a genuine responsibility, not a
  footnote.

  TCP's congestion control (Chapter 4) is what
  prevents the internet collapsing under load. It
  works because nearly all traffic participates.

  A high-volume UDP application that does not
  implement its own rate limiting is a FREE RIDER:
  it takes bandwidth from every well-behaved TCP
  flow sharing the path, and TCP will back off
  while UDP does not.

  If you build a UDP protocol that moves real
  volume, implementing congestion control is part
  of the job.
─────────────────────────────────────────

4. When Less Is More

Real Uses, and the Reason
─────────────────────────────────────────
  DNS (Module 5, Chapter 1)
    A query and a reply, both small. Setting up a
    TCP connection would triple the latency of a
    lookup that takes one round trip. Lost query?
    Ask again.

  VOICE AND VIDEO CALLS
    A retransmitted audio packet arrives after its
    moment has passed and is USELESS. Better to
    conceal the gap and keep playing. Latency
    matters; completeness does not.

  ONLINE GAMES
    Position updates are superseded ten times a
    second. An old one is worthless.

  DHCP (Module 3, Chapter 5)
    The client has no IP address yet, so it cannot
    establish a TCP connection.

  NTP, SNMP, syslog
    Small, frequent, individually unimportant.

  QUIC / HTTP/3 (Chapter 5)
    Uses UDP to build a BETTER transport in
    userspace — the most interesting case.
─────────────────────────────────────────
THE PRINCIPLE
─────────────────────────────────────────
  Use UDP when a LATE packet is worth LESS than a
  MISSING one.

  For a file, every byte matters and time does not.
  ──► TCP.

  For a live voice call, a packet that arrives
  200ms late is worse than silence — it cannot be
  played, and waiting for it delays everything
  behind it.
  ──► UDP.

  That single question answers the choice almost
  every time.
─────────────────────────────────────────

5. Writing UDP Code

import socket
 
def udp_server(host="0.0.0.0", port=9999):
    s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
    s.bind((host, port))
    print(f"UDP listening on {host}:{port}")
 
    while True:
        data, addr = s.recvfrom(65535)        # ONE datagram, WHOLE — boundaries preserved
        print(f"{len(data)} bytes from {addr}")
        s.sendto(data.upper(), addr)          # no connection; every send names a target
def udp_client(host, port, message: bytes, timeout=2.0):
    s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
    s.settimeout(timeout)                     # ESSENTIAL — nothing else detects loss
    try:
        s.sendto(message, (host, port))       # no handshake; this is the first packet
        data, _ = s.recvfrom(65535)
        return data
    except socket.timeout:
        return None                           # lost request, lost reply — indistinguishable
    finally:
        s.close()
Three Things to Notice
─────────────────────────────────────────
  NO accept(), NO connect() REQUIRED
    There is no connection to establish. The first
    packet is the request.

  recvfrom RETURNS ONE WHOLE DATAGRAM
    Message boundaries are preserved (Chapter 1),
    so no framing protocol is needed.

  A TIMEOUT IS MANDATORY
    Without it, a lost packet means waiting
    forever. UDP will never tell you.

  SIZE LIMIT: keep datagrams under ~1472 bytes on
  a standard Ethernet path, so IP does not have to
  fragment them (Module 1, Chapter 3). A fragmented
  datagram is lost entirely if ANY fragment is
  lost.
─────────────────────────────────────────

6. Building Reliability on UDP

Why Anyone Would
─────────────────────────────────────────
  To choose which guarantees to pay for.

  A game might want: retransmit critical events
  (a player died), never retransmit position
  updates, and never block one on the other.

  TCP cannot express that. It reliably delivers
  everything, in order, which means one lost packet
  stalls everything behind it — HEAD-OF-LINE
  BLOCKING (Chapter 5).
─────────────────────────────────────────
import struct, time
 
class ReliableChannel:
    """Selective reliability over UDP: some messages are retried, others are not."""
 
    def __init__(self, sock, addr, rto=0.2, max_retries=5):
        self.sock, self.addr = sock, addr
        self.rto, self.max_retries = rto, max_retries
        self.seq = 0
        self.pending = {}                     # seq -> (payload, sent_at, tries)
 
    def send(self, payload: bytes, reliable: bool):
        self.seq += 1
        header = struct.pack("!IB", self.seq, 1 if reliable else 0)
        self.sock.sendto(header + payload, self.addr)
        if reliable:
            self.pending[self.seq] = (header + payload, time.time(), 0)   # track for retry
 
    def on_ack(self, seq: int):
        self.pending.pop(seq, None)           # acknowledged ──► stop retrying
 
    def tick(self):
        """Call periodically. Retransmit anything unacknowledged past its timeout."""
        now = time.time()
        for seq, (data, sent, tries) in list(self.pending.items()):
            if now - sent < self.rto * (2 ** tries):      # exponential backoff
                continue
            if tries >= self.max_retries:
                del self.pending[seq]                     # give up
                continue
            self.sock.sendto(data, self.addr)
            self.pending[seq] = (data, now, tries + 1)
The Honest Caution
─────────────────────────────────────────
  TCP is the product of forty years of refinement
  against real networks. Reimplementing it badly is
  easy and common.

  Build on UDP when you need something TCP
  genuinely cannot express — selective reliability,
  no head-of-line blocking, connection migration.

  And prefer using QUIC (Chapter 5), which already
  did this work carefully, over writing your own.
─────────────────────────────────────────

7. Summary & Next Steps

Key Takeaways

  • UDP adds only ports, a length and a checksum to IP; every field TCP has beyond that exists to support a guarantee UDP does not make.
  • UDP does not implement congestion control, which makes a high-volume UDP application a free rider on well-behaved TCP flows unless it limits itself.
  • Use UDP when a late packet is worth less than a missing one — the question that decides voice, video and games versus file transfer.
  • Message boundaries are preserved, no framing is needed, and a receive timeout is mandatory because nothing else will ever report loss.

Concept Check

  1. Why does UDP's checksum deliberately include IP addresses from the layer below?
  2. Why is retransmitting a lost audio packet in a live call worse than concealing the gap?
  3. What responsibility does a high-volume UDP application take on that a TCP application does not?

Next Chapter

Chapter 3: TCP Connections and Reliability


Jr Codex — 1-on-1 Personalized Coaching | Back to Module Index | Back to Computer Networks Index