CMP-330 · Lesson 6 of 12 · 30 min

The transport layer I: UDP and reliable data transfer

The network layer moves datagrams between hosts, with no promises: datagrams can be lost, corrupted, duplicated or delivered out of order. The transport layer turns this into a service between processes. At a minimum it multiplexes and demultiplexes data to the right socket, using port numbers. UDP does that and little else. TCP and QUIC go much further and provide reliable, in-order delivery over the unreliable network. This lesson looks first at UDP and its checksum, then builds up the principles of reliable data transfer step by step: acknowledgements, timeouts, sequence numbers and pipelining with Go-Back-N and Selective Repeat. Lesson 7 shows how TCP puts these principles into practice and adds flow control and congestion control.

Multiplexing and demultiplexing

A host may run dozens of processes using the network at once. When a segment arrives, the transport layer must deliver it to the right socket. For UDP, a socket is identified by the destination IP address and destination port alone: datagrams from different senders to the same port all arrive at the same socket, and the application learns each sender's address from recvfrom. For TCP, a connected socket is identified by all four values: source address, source port, destination address and destination port. That is how a web server's port 443 can have thousands of simultaneous connections, each delivered to its own socket. Lesson 5 showed both behaviours in running code.

UDP: the minimal transport

UDP, defined in RFC 768 in 1980 and only three pages long, adds an 8-byte header to the application's data: source port, destination port, length and checksum. It offers no connection setup, no reliability, no ordering, no flow control and no congestion control. That sounds like a list of defects, but it is often exactly what an application wants. There is no handshake delay, so a DNS query and reply take one round trip. There is no connection state, so a server can handle many more clients. Header overhead is small. And the application controls exactly when data is sent: a voice call would rather skip a late packet than wait for its retransmission.

The UDP header
FieldBitsPurpose
Source port16Port of the sending socket, for replies (may be zero)
Destination port16Port of the receiving socket, for demultiplexing
Length16Length of header plus data, in bytes
Checksum16Detects errors in the header, data and a pseudo-header (optional in IPv4, mandatory in IPv6)

Applications built on UDP include DNS, the Network Time Protocol, the DHCP protocol that assigns addresses (lesson 8), streaming media and games, SNMP network management and, increasingly, QUIC, which builds its own reliability on top of UDP so that it can evolve in user space. When an application needs reliability over UDP, it must implement it itself, using the principles in the rest of this lesson.

The internet checksum

UDP, TCP and the IPv4 header all use the same error-detection code, the internet checksum (RFC 1071). The sender treats the data as a sequence of 16-bit integers, adds them using one's complement arithmetic (wrapping any carry out of the top bit back into the bottom), and sends the complement of the sum. The receiver adds everything including the checksum; if no bits were corrupted, the result is all ones, so its complement is zero. For UDP and TCP, the sum also covers a pseudo-header containing the source and destination IP addresses, the protocol number and the length, which catches datagrams delivered to the wrong host.

Computing and verifying a UDP checksum, including the pseudo-header
import struct
def internet_checksum(data: bytes) -> int:
    """16-bit one's complement of the one's complement sum (RFC 1071)."""
    if len(data) % 2:
        data += b"\x00"                       # pad odd-length data with a zero byte
    total = 0
    for (word,) in struct.iter_unpack("!H", data):
        total += word
        total = (total & 0xFFFF) + (total >> 16)   # wrap the carry around
    return ~total & 0xFFFF

src, dst = bytes([192, 168, 1, 23]), bytes([10, 0, 0, 53])
payload = b"temp=21.5"
length = 8 + len(payload)
pseudo = src + dst + struct.pack("!BBH", 0, 17, length)        # pseudo-header: addresses, protocol, length
udp = struct.pack("!HHHH", 40000, 9000, length, 0) + payload   # checksum field zero while computing
csum = internet_checksum(pseudo + udp)
print(f"UDP checksum = 0x{csum:04x}")
segment = udp[:6] + struct.pack("!H", csum) + udp[8:]
print("receiver check (should be 0):", hex(internet_checksum(pseudo + segment)))
corrupt = bytearray(segment); corrupt[-1] ^= 0x01               # flip one bit in the payload
print("after a 1-bit error:", hex(internet_checksum(pseudo + bytes(corrupt))))
swapped = segment[:8] + segment[10:12] + segment[8:10] + segment[12:]   # exchange two 16-bit words
print(f"after swapping two words (payload now {swapped[8:].decode()!r}):", hex(internet_checksum(pseudo + swapped)))
Output
UDP checksum = 0xef38
receiver check (should be 0): 0x0
after a 1-bit error: 0x100
after swapping two words (payload now 'mpte=21.5'): 0x0

The checksum catches the single-bit error but not the swapped words, because addition is commutative: exchanging two 16-bit words leaves the sum unchanged, so the corrupted reading passes the check. The internet checksum is cheap to compute in software but weak; link layers use stronger cyclic redundancy checks (lesson 10), and TLS and QUIC add cryptographic integrity checks that catch deliberate tampering as well as accidents. Why check at the transport layer at all, if links already check? Because not every link checks, errors can occur inside routers' memory, and the end-to-end principle says that only the endpoints can verify end-to-end correctness.

Building a reliable protocol step by step

Imagine designing a protocol to deliver data reliably over a channel that can corrupt and lose packets. Following the classic presentation by Kurose and Ross, we can build it in stages, each adding one mechanism to handle one new problem.

Mechanisms of reliable data transfer and the problems they solve
ProblemMechanismWhy it is needed
Bit errorsChecksum, plus positive and negative acknowledgements (ACK and NAK)The receiver must detect corruption and tell the sender to retransmit
Corrupted acknowledgementsSequence numbers on data packetsThe receiver must recognize a retransmitted duplicate it has already received
Avoiding NAKsACKs that carry the sequence number being acknowledgedA duplicate ACK for the previous packet means the current one was not received
Lost packetsA countdown timer and retransmission on timeoutWithout a timer, a lost packet or ACK would leave the sender waiting forever
Low throughputPipelining with a window of unacknowledged packetsWaiting for each ACK wastes most of a high bandwidth–delay path

The result of the first four stages is the alternating-bit protocol, a stop-and-wait protocol: the sender transmits a packet with sequence number 0 or 1, starts a timer and waits. If the ACK arrives intact with the right number, it sends the next packet with the other number. If the timer expires, it retransmits. The receiver delivers each new packet to the application, discards duplicates (but still acknowledges them, since the earlier ACK may have been lost) and always acknowledges the last correctly received packet. Choosing the timeout is a balancing act: too short causes needless retransmissions, too long makes recovery from loss slow. Lesson 7 shows how TCP estimates it from measured round-trip times.

The problem with stop-and-wait

Stop-and-wait is correct but slow. The sender transmits one packet, taking \(L/R\) seconds, and then sits idle for a whole round trip waiting for the acknowledgement. Its utilization, the fraction of time the sender is actually sending, is

\[U_{\text{sender}} = \frac{L/R}{RTT + L/R}\]

For a 1,500-byte packet on a 1 Gbit/s link with a 30 ms round trip, that is 12 microseconds of sending per 30 milliseconds: a utilization of 0.04%, or 400 kbit/s of a 1 Gbit/s link. The solution is pipelining: let the sender have many packets in flight at once. With a window of \(N\) unacknowledged packets, utilization rises by a factor of \(N\) until the pipe is full. The window needed to fill it is the bandwidth–delay product from lesson 1 divided by the packet size.

Utilization of a 1 Gbit/s, 30 ms path as the window grows
# Sender utilization of stop-and-wait versus pipelining with window N
L, R, RTT = 1500 * 8, 1e9, 0.030          # 1,500-byte packets, 1 Gbit/s, 30 ms RTT
t = L / R
for N in (1, 10, 100, 1000, 2500, 5000):
    U = min(1.0, N * t / (RTT + t))
    print(f"window {N:5d} packets: utilization {U:8.4%}, throughput {U * R / 1e6:8.1f} Mbit/s")
print("window needed to fill the pipe:", round((RTT + t) / t), "packets")
Output
window     1 packets: utilization  0.0400%, throughput      0.4 Mbit/s
window    10 packets: utilization  0.3998%, throughput      4.0 Mbit/s
window   100 packets: utilization  3.9984%, throughput     40.0 Mbit/s
window  1000 packets: utilization 39.9840%, throughput    399.8 Mbit/s
window  2500 packets: utilization 99.9600%, throughput    999.6 Mbit/s
window  5000 packets: utilization 100.0000%, throughput   1000.0 Mbit/s
window needed to fill the pipe: 2501 packets

Pipelining has consequences. The range of sequence numbers must grow, since many packets are outstanding. Both sides may need to buffer packets. And when a packet is lost, the protocol must decide what to retransmit. Two classic answers are Go-Back-N and Selective Repeat.

Two sequence-number lines. The Go-Back-N sender line shows packets already acknowledged in green, a window of N packets sent but not acknowledged in gold starting at base, usable but not yet sent numbers in white, and unusable numbers in grey. The Selective Repeat line is similar, but inside the window some packets are individually marked as acknowledged. Below, the Selective Repeat receiver shows a window with some packets received out of order and buffered.
Sender windows in Go-Back-N and Selective Repeat; Selective Repeat acknowledges and buffers packets individually

Go-Back-N

In Go-Back-N (GBN), the sender may have up to \(N\) unacknowledged packets in flight. The window begins at base, the oldest unacknowledged sequence number. The receiver is simple: it accepts only the next packet in order, discards anything that arrives out of order, and sends a cumulative acknowledgement for the highest in-order sequence number received, meaning all packets up to and including it have arrived. The sender keeps a single timer for the oldest unacknowledged packet. If it expires, the sender retransmits every packet in the window, going back to base. GBN is simple and needs no receiver buffering, but a single loss on a path with a large window causes many needless retransmissions of packets that had arrived intact.

Selective Repeat

Selective Repeat (SR) avoids those needless retransmissions. The receiver acknowledges each correctly received packet individually, buffering packets that arrive out of order and delivering them to the application once the gap is filled. The sender keeps a timer for each outstanding packet and retransmits only the ones whose timers expire. The cost is more state at both ends, and a subtle constraint on the window size: the window must be no more than half the size of the sequence number space. Otherwise the receiver cannot tell whether a packet carrying a given number is a retransmission of an old packet whose acknowledgement was lost or a new packet from the next window.

With 2-bit sequence numbers, a window of 3 is ambiguous and a window of 2 is safe
# Why Selective Repeat needs window <= half the sequence number space
k = 2                                  # 2-bit sequence numbers: 0..3
space = 2 ** k
for window in (3, 2):
    first = list(range(window))                        # packets the sender sends first
    receiver_expects = [(s + window) % space for s in range(window)]   # after acking all of them
    retransmit = first                                 # if every ACK is lost, sender resends these
    ambiguous = sorted(set(retransmit) & set(receiver_expects))
    print(f"window {window}: receiver now accepts {receiver_expects}, "
          f"old retransmissions carry {retransmit} -> confused on {ambiguous or 'nothing'}")
Output
window 3: receiver now accepts [3, 0, 1], old retransmissions carry [0, 1, 2] -> confused on [0, 1]
window 2: receiver now accepts [2, 3], old retransmissions carry [0, 1] -> confused on nothing

The simulation below compares the three protocols delivering 2,000 packets over a channel that loses 5% of packets, with a window of 8 and a round trip of 10 ms. Each simulated round sends a window of packets and receives their acknowledgements. Go-Back-N retransmits more than Selective Repeat because it resends everything after a loss; stop-and-wait sends the fewest packets but takes far longer, because it never has more than one in flight.

Stop-and-wait, Go-Back-N and Selective Repeat over a 5% loss channel
# Simulate stop-and-wait, Go-Back-N and Selective Repeat over a lossy channel
import random
def simulate(protocol, n_packets=2000, window=8, loss=0.05, rtt=10, seed=1):
    rnd = random.Random(seed)
    t, sent, base, delivered = 0, 0, 0, set()
    acked = set()
    while base < n_packets:
        w = 1 if protocol == "stop-and-wait" else window
        batch = range(base, min(base + w, n_packets))
        lost = [s for s in batch if s not in acked and rnd.random() < loss]
        for s in batch:
            if protocol == "selective-repeat" and s in acked:
                continue                       # SR resends only what is missing
            sent += 1
        t += rtt                              # one round of sends and acknowledgements
        if protocol == "go-back-n":
            first_lost = min(lost) if lost else None
            good = [s for s in batch if first_lost is None or s < first_lost]
            acked.update(good)                # receiver discards everything after a gap
        else:
            acked.update(s for s in batch if s not in lost)
        while base in acked:
            base += 1
    return t, sent
for proto in ("stop-and-wait", "go-back-n", "selective-repeat"):
    t, sent = simulate(proto)
    print(f"{proto:17s} time {t:6d} ms  transmissions {sent:5d}  ({sent/2000:.2f} per packet)")
Output
stop-and-wait     time  21130 ms  transmissions  2113  (1.06 per packet)
go-back-n         time   3250 ms  transmissions  2593  (1.30 per packet)
selective-repeat  time   3020 ms  transmissions  2113  (1.06 per packet)

Worked problem

Sizing a pipelined protocol

A satellite link has a rate of 50 Mbit/s and a round-trip time of 600 ms. Packets are 1,250 bytes. What window is needed for full utilization? How many bits of sequence number does Selective Repeat need, and how many does Go-Back-N need?

Show solution
  1. Transmission time per packet: \(1250 \times 8 / 5 \times 10^7 = 0.2\) ms.
  2. Window to fill the pipe: \((600 + 0.2) / 0.2 \approx 3001\) packets.
  3. Selective Repeat needs a sequence space at least twice the window, 6,002 numbers, so 13 bits (8,192 numbers).
  4. Go-Back-N needs a space of at least the window plus one, 3,002 numbers, so 12 bits (4,096 numbers).

Answer: About 3,000 packets in flight; 13 bits of sequence number for Selective Repeat and 12 for Go-Back-N.

Worked problem

Throughput of stop-and-wait with loss

A stop-and-wait protocol sends 1,000-byte packets over a 10 Mbit/s link with a 20 ms RTT. The timeout is 40 ms and 10% of packets are lost (acknowledgements are never lost). Estimate the average throughput.

Show solution
  1. A successful attempt takes \(L/R + RTT = 0.8 + 20 = 20.8\) ms. A lost attempt costs the 40 ms timeout plus 0.8 ms of transmission.
  2. Expected failed attempts per packet: \(p/(1-p) = 0.1/0.9 \approx 0.111\), costing \(0.111 \times 40.8 \approx 4.5\) ms on average.
  3. Average time per packet: about 25.3 ms, so throughput is \(8000 / 0.0253 \approx 316\) kbit/s, about 3% of the link.

Answer: About 316 kbit/s: the round-trip wait, not the loss, is the main reason stop-and-wait wastes the link.

Real protocols mix these ideas. TCP uses cumulative acknowledgements like GBN, but buffers out-of-order segments and, with the selective acknowledgement (SACK) option, tells the sender exactly which blocks arrived, so it retransmits only what is missing, much like SR. QUIC never reuses packet numbers, which removes the ambiguity about which transmission an acknowledgement refers to, and acknowledges ranges of packets. Both add congestion control, the subject of lesson 7.

Common mistakes

  • MistakeThinking UDP is useless because it is unreliable.

    InsteadUDP's lack of setup and control suits DNS, real-time media and protocols such as QUIC that implement their own reliability.

  • MistakeBelieving the internet checksum detects all errors.

    InsteadIt misses errors that leave the sum unchanged, such as reordered words; stronger CRCs and cryptographic checks complement it.

  • MistakeForgetting that acknowledgements can be lost or corrupted too.

    InsteadSequence numbers let the receiver discard duplicates caused by retransmissions after lost ACKs.

  • MistakeAssuming stop-and-wait is fine on fast links.

    InsteadUtilization is (L/R)/(RTT + L/R), tiny when the bandwidth–delay product is large.

  • MistakeUsing a Selective Repeat window as large as the sequence space.

    InsteadThe window must be at most half the sequence number space to avoid ambiguity.

  • MistakeTreating Go-Back-N's receiver as buffering out-of-order packets.

    InsteadThe GBN receiver discards out-of-order packets; only Selective Repeat buffers them.

Summary and key terms

  • The transport layer multiplexes data between processes using port numbers; TCP sockets are identified by four values, UDP sockets by destination address and port.
  • UDP adds only ports, a length and a checksum, giving low delay and full application control.
  • The internet checksum is a one's complement sum over the segment and a pseudo-header; it is cheap but weak.
  • Reliable transfer needs checksums, acknowledgements, sequence numbers and timers.
  • Stop-and-wait has utilization (L/R)/(RTT + L/R), which is very low on long, fast paths.
  • Pipelining with Go-Back-N uses cumulative ACKs and resends the whole window after a timeout.
  • Selective Repeat acknowledges and buffers packets individually and needs a window of at most half the sequence space.
Demultiplexing
Delivering an arriving segment to the correct socket using header fields.
UDP
The User Datagram Protocol: connectionless, unreliable delivery between ports.
Internet checksum
A 16-bit one's complement sum used by IPv4, UDP and TCP to detect errors.
Pseudo-header
IP addresses, protocol and length included in the transport checksum but not transmitted in the segment.
Acknowledgement (ACK)
A message from the receiver confirming receipt of data.
Sequence number
A number identifying a packet so the receiver can detect duplicates and order data.
Stop-and-wait
A protocol that sends one packet and waits for its acknowledgement before sending the next.
Pipelining
Sending several packets before receiving their acknowledgements.
Go-Back-N
A pipelined protocol with cumulative ACKs that retransmits the whole window after a timeout.
Selective Repeat
A pipelined protocol that acknowledges and retransmits packets individually.

Practice problems

Work each problem on paper first, then reveal the answer to check.

  1. What fields make up the UDP header?

    Show answer

    Source port, destination port, length and checksum, 2 bytes each.

  2. Compute the utilization of stop-and-wait with L/R = 1 ms and RTT = 19 ms.

    Show answer

    1/(19 + 1) = 5%.

  3. Why does the receiver acknowledge a duplicate packet?

    Show answer

    Its previous ACK may have been lost, and the sender needs an ACK to move on.

  4. What does a cumulative acknowledgement for packet n mean?

    Show answer

    All packets up to and including n have been received.

  5. In Go-Back-N, what happens when the timer expires?

    Show answer

    The sender retransmits all unacknowledged packets in the window.

  6. With 3-bit sequence numbers, what is the largest safe Selective Repeat window?

    Show answer

    4.

  7. Why is the internet checksum computed over a pseudo-header?

    Show answer

    To detect segments delivered to the wrong host or protocol.

  8. Name two applications that use UDP.

    Show answer

    DNS and real-time voice or video; also QUIC, NTP and DHCP.

  9. What is the bandwidth–delay product used for in pipelining?

    Show answer

    To find how many packets must be in flight to fill the path.

  10. Why might a timeout that is too short hurt performance?

    Show answer

    It causes unnecessary retransmissions that waste capacity.

Lesson quiz · 5 questions

Score 60% or more to complete this lesson.

Take the quiz