§labChapter 3 Labs, the rdt Assignment and Interview

Transport layer Kurose & Ross pp. 300–302 · ~17 min read

  • fast retransmit
  • duplicate acknowledgement
  • internet checksum
  • go-back-n

Where you are

  • Application layer
  • Transport layer you are here
  • Network layer
  • Link layer
  • Physical layer

Two Wireshark labs rebuilt as captures you can click through, one of them containing a real lost segment and its recovery. Then the reliable-transfer programming assignment in both its versions, and four questions to the man who worked out why TCP (Transmission Control Protocol) was collapsing.

What is on this page

  • Wireshark Lab: Exploring TCP — a file uploaded, a segment lost, three duplicate acknowledgements and a fast retransmit, all in 28 packets.
  • Wireshark Lab: Exploring UDP (User Datagram Protocol) — the four header fields, and the checksum arithmetic done word by word so you can check it.
  • The rdt programming assignment — working Python for both versions the book offers: alternating-bit and Go-Back-N.
  • An interview with Van Jacobson, who found TCP’s congestion control by asking a different question.

Why this matters

Everything in this chapter was a mechanism described in words and diagrams. A packet capture is where you learn whether you believed it.

The TCP capture below is the useful one. Its middle contains the single most important sequence in the chapter. A segment vanishes, the receiver keeps asking for the same byte, and after the third repeat the sender resends. No timer expires. If you can point at those packets and say which is which, you understand section 3.5.4.

The UDP (User Datagram Protocol) capture is here for the opposite reason. It is four header fields and eighteen bytes, and it takes about a minute. That is the comparison.

Wireshark Lab: Exploring TCP

The book’s lab uploads a file to a Web server with an HTTP (HyperText Transfer Protocol) POST. It then asks you to trace TCP’s window behaviour, and to infer loss, retransmission, flow control and congestion control from the trace. This capture is that upload: 12,620 bytes in nine segments, captured at the sender.

Capturing at the sender matters. Packet 11 never arrives anywhere. It is in the trace because the client transmitted it, and the client will not learn otherwise for another 120 milliseconds.

Wireshark Lab: Exploring TCP — a file uploaded, a segment lost, and the recovery
No.TimeSourceDestinationProtocolLengthInfo
10.000000192.168.1.24198.51.100.7TCP5852310 → 80 [SYN] Seq=0 Win=64240 Len=0 MSS=1460
20.060200198.51.100.7192.168.1.24TCP5880 → 52310 [SYN, ACK] Seq=0 Ack=1 Win=64240 Len=0 MSS=1460
30.060250192.168.1.24198.51.100.7TCP5452310 → 80 [ACK] Seq=1 Ack=1 Win=64240 Len=0
40.060330192.168.1.24198.51.100.7TCP151452310 → 80 [PSH, ACK] Seq=1 Ack=1 Win=64240 Len=1460
50.120530198.51.100.7192.168.1.24TCP5480 → 52310 [ACK] Seq=1 Ack=1461 Win=64240 Len=0
60.120580192.168.1.24198.51.100.7TCP151452310 → 80 [ACK] Seq=1461 Ack=1 Win=64240 Len=1460
70.121790192.168.1.24198.51.100.7TCP151452310 → 80 [ACK] Seq=2921 Ack=1 Win=64240 Len=1460
80.180780198.51.100.7192.168.1.24TCP5480 → 52310 [ACK] Seq=1 Ack=2921 Win=64240 Len=0
90.180830198.51.100.7192.168.1.24TCP5480 → 52310 [ACK] Seq=1 Ack=4381 Win=64240 Len=0
100.180880192.168.1.24198.51.100.7TCP151452310 → 80 [ACK] Seq=4381 Ack=1 Win=64240 Len=1460
110.182090192.168.1.24198.51.100.7TCP151452310 → 80 [ACK] Seq=5841 Ack=1 Win=64240 Len=1460 ← lost in the network
120.183300192.168.1.24198.51.100.7TCP151452310 → 80 [ACK] Seq=7301 Ack=1 Win=64240 Len=1460
130.184510192.168.1.24198.51.100.7TCP151452310 → 80 [ACK] Seq=8761 Ack=1 Win=64240 Len=1460
140.241080198.51.100.7192.168.1.24TCP5480 → 52310 [ACK] Seq=1 Ack=5841 Win=64240 Len=0
150.241130192.168.1.24198.51.100.7TCP151452310 → 80 [ACK] Seq=10221 Ack=1 Win=64240 Len=1460
160.241180198.51.100.7192.168.1.24TCP5480 → 52310 [ACK] Seq=1 Ack=5841 Win=62780 Len=0 [TCP Dup ACK 1]
170.241230198.51.100.7192.168.1.24TCP5480 → 52310 [ACK] Seq=1 Ack=5841 Win=61320 Len=0 [TCP Dup ACK 2]
180.301330198.51.100.7192.168.1.24TCP5480 → 52310 [ACK] Seq=1 Ack=5841 Win=59860 Len=0 [TCP Dup ACK 3]
190.301390192.168.1.24198.51.100.7TCP151452310 → 80 [PSH, ACK] Seq=5841 Ack=1 Win=64240 Len=1460 [TCP Fast Retransmission]
200.361590198.51.100.7192.168.1.24TCP5480 → 52310 [ACK] Seq=1 Ack=11681 Win=64240 Len=0
210.361640192.168.1.24198.51.100.7TCP99452310 → 80 [PSH, ACK] Seq=11681 Ack=1 Win=64240 Len=940
220.421840198.51.100.7192.168.1.24TCP5480 → 52310 [ACK] Seq=1 Ack=12621 Win=64240 Len=0
230.423940198.51.100.7192.168.1.24HTTP22680 → 52310 [PSH, ACK] Seq=1 Ack=12621 Win=64240 Len=172
240.484140192.168.1.24198.51.100.7TCP5452310 → 80 [ACK] Seq=12621 Ack=173 Win=64240 Len=0
250.484540192.168.1.24198.51.100.7TCP5452310 → 80 [FIN, ACK] Seq=12621 Ack=173 Win=64240 Len=0
260.544740198.51.100.7192.168.1.24TCP5480 → 52310 [ACK] Seq=173 Ack=12622 Win=64240 Len=0
270.545640198.51.100.7192.168.1.24TCP5480 → 52310 [FIN, ACK] Seq=173 Ack=12622 Win=64240 Len=0
280.605840192.168.1.24198.51.100.7TCP5452310 → 80 [ACK] Seq=12622 Ack=174 Win=64240 Len=0

Packet 1 Packet 1 of 28. The connection begins. SYN = 1, no data, and the sequence number field carries the client’s initial sequence number — the random value §3.5.6 insists on. Wireshark shows it as 0 because it renumbers relative to the start of the connection; the real value is in the bytes below.

Protocol tree — click a field

The actual bytes

0000 aa bb cc 00 00 01 aa bb cc 00 00 11 08 00 45 00 ..............E.
0010 00 2c 2b 01 40 00 40 06 23 d0 c0 a8 01 18 c6 33 .,+.@.@.#......3
0020 64 07 cc 56 00 50 6b 2f 01 00 00 00 00 00 60 02 d..V.Pk/......`.
0030 fa f0 78 64 00 00 02 04 05 b4 ..xd......

An HTTP POST of a 12,620-byte file, captured at the sender. Packet 11 never reaches the server. Watch what happens between packets 15 and 21 — that is fast retransmit, and it is the whole reason this capture exists.

The lab’s questions, answered from this trace

What are the port numbers, and the initial sequence numbers? The client uses ephemeral port 52310 and the server the well-known port 80. The client’s initial sequence number is 1,798,242,560 and the server’s is 2,753,396,224. They are unrelated, and each side chose its own — section 3.5.6 explains why they must be hard to guess. Wireshark renumbers both to start at 0, which is why packet 1 reads Seq=0.

What is the maximum segment size? 1,460 bytes, announced by both sides as an option on their SYN (synchronize) . It is 1,500 − 20 − 20: an Ethernet frame’s payload, less the IP (Internet Protocol) header and the TCP header.

What is the estimated round-trip time? Read it off any pair. Packet 4 goes out at 0.0603 and its acknowledgement, packet 5, arrives at 0.1205. The difference is 60.2 ms, and it is the same for every other pair in the trace.

Where is congestion control? In the shape of the sending. One segment (packet 4), then two (packets 6 and 7), then four (packets 10 to 13). That is slow start doubling the congestion window each round trip, exactly as in section 3.7.1. Nothing in any header says so — the congestion window is never transmitted. You are inferring it from the timing, which is what the lab is training you to do.

Where is flow control? In the receive-window field. It sits at 64,240 bytes until packet 16, then falls to 62,780, 61,320 and 59,860 — down exactly 1,460 each time — and returns to 64,240 at packet 20. Section 3.5.5 says the receive window is the free space in the receive buffer, and here you can watch it being consumed.

Where is retransmission? Packet 19, and the four packets before it explain why.

Count duplicates, not acknowledgements

Four packets carry Ack=5841: packets 14, 16, 17 and 18. Only three of them are duplicates.

Packet 14 is the ordinary cumulative acknowledgement for packet 10, which carried bytes 4381 to 5840. It is the first time 5841 has ever been requested, so it is not a repeat of anything.

Packets 16, 17 and 18 are the duplicate acknowledgements . Each one was produced by a segment arriving past the gap — packets 12, 13 and 15. Each says the same thing: I am still waiting for byte 5841.

Section 3.5.4’s rule counts duplicates, so the trigger is packet 18. A reader who counts acknowledgements instead fires one packet too early, and that is the commonest misreading of fast retransmit.

The recovery, packet by packet

PacketTimeWhat it is
110.1821Bytes 5841–7300 are sent. They never arrive.
12, 130.1833, 0.1845The next two segments are sent, and both arrive — past the hole.
140.2411Ack=5841. The cumulative acknowledgement for packet 10. Not a duplicate.
150.2411Packet 14 slid the sender window, so one more segment goes out. The sender still suspects nothing.
160.2412Ack=5841, duplicate 1 — caused by packet 12 arriving. Window drops 1,460.
170.2412Ack=5841, duplicate 2 — caused by packet 13. Window drops another 1,460.
180.3013Ack=5841, duplicate 3 — caused by packet 15, which is why it comes a round trip later.
190.3014 Fast retransmit . Bytes 5841–7300 again, one tenth of a millisecond after duplicate 3.
200.3616Ack=11681. One acknowledgement jumps 5,840 bytes.

Two things are worth sitting with.

Packet 19 follows packet 18 immediately. No timer expired. The sender acted on the third duplicate, which is the entire point of the mechanism.

Packet 20 jumps 5,840 bytes. The receiver had bytes 7301 through 11680 the whole time and could not say so, because a cumulative acknowledgement cannot name anything past a gap. The moment the hole was plugged, all four segments became deliverable at once — and the receive window snapped back to 64,240 because those bytes finally left the buffer.

That is section 3.5.4 and section 3.5.5 in the same two numbers.

Every byte here is real

Both checksums in every packet were computed, not invented. You can verify any of them the way a receiver does. Add up the 16-bit words of the header, including the checksum field itself, with end-around carry. The answer must be 0xFFFF.

tools/check-capture-ch03.mjs does exactly that for all 28 packets, and independently re-derives the sequence-number arithmetic, the duplicate count and the window behaviour from the hex alone.

Wireshark Lab: Exploring UDP

The book’s UDP lab is one short paragraph: capture your favourite UDP application, then investigate the header fields and the checksum calculation. This is the two-datagram exchange from section 2.7.1 — eighteen bytes of text sent and shouted back.

The two datagrams UDPClient.py and UDPServer.py actually send
No.TimeSourceDestinationProtocolLengthInfo
10.000000192.168.1.24198.51.100.7UDP6051873 → 12000 Len=18
20.091200198.51.100.7192.168.1.24UDP6012000 → 51873 Len=18

Packet 1 One datagram out. No connection was established first — this is the first packet either side has sent, and it already carries application data. Compare the three-way handshake in the chapter 1 capture.

Protocol tree — click a field

The actual bytes

0000 aa bb cc 00 00 01 aa bb cc 00 00 11 08 00 45 00 ..............E.
0010 00 2e 4f 21 40 00 40 11 ff a2 c0 a8 01 18 c6 33 ..O!@.@........3
0020 64 07 ca a1 2e e0 00 1a a7 c3 68 65 6c 6c 6f 20 d.........hello
0030 66 72 6f 6d 20 62 61 6e 67 6b 6f 6b from bangkok

A real capture of the book’s example, generated with correct checksums. Click a field to highlight its bytes. The whole conversation is two packets.

The four fields, and nothing else

FieldBytesValue in packet 1
Source port251873
Destination port212000
Length226 — the header plus the 18 bytes of data
Checksum20xa7c3

Eight bytes of header in total, against TCP’s twenty. There is no sequence number, no acknowledgement number, no receive window and no flags, because UDP does none of the things those fields exist for.

Two details that catch people out.

The length includes the header. 26, not 18. So the payload is always length − 8.

The source port is optional. UDP allows it to be zero when no reply is wanted. Here it is filled in, because the client wants the shouted sentence back.

The checksum, computed

Section 3.3.2 says the checksum covers the UDP header, the data, and a pseudo-header borrowed from the IP header. Here is the whole sum for packet 1, word by word.

The pseudo-header — source address, destination address, a zero byte, the protocol number and the UDP length:

0xc0a8  192.168 — source address, high half
0x0118  .1.24   — source address, low half
0xc633  198.51  — destination address, high half
0x6407  .100.7  — destination address, low half
0x0011  zero byte, then protocol 17 (UDP)
0x001a  UDP length, 26

The UDP header — with the checksum field taken as zero while computing:

0xcaa1  source port 51873
0x2ee0  destination port 12000
0x001a  length 26

The data — “hello from bangkok”, two characters per word:

0x6865 "he"   0x6c6c "ll"   0x6f20 "o "
0x6672 "fr"   0x6f6d "om"   0x2062 " b"
0x616e "an"   0x676b "gk"   0x6f6b "ok"

Add all of those with end-around carry — every carry out of the top bit is added back at the bottom — and the total is

0x583c

Invert every bit:

~0x583c = 0xa7c3

which is exactly what the checksum field contains.

Now check it the way the receiver does. Add the same words again, this time including the checksum:

0x583c + 0xa7c3 = 0xffff

All ones. That single test is why the sender inverts the sum instead of sending it directly — the receiver looks for one fixed pattern rather than comparing two numbers. Problem P3 asks you to prove that, and problem P4(c) asks you to break it.

Why UDP borrows fields from the layer below

The pseudo-header is a layering violation, and the book says so. The transport layer is reaching into the network layer’s header to include the IP addresses in its own checksum.

It is there to catch a datagram delivered to the wrong host. Without it, a corrupted destination address in the IP header would still be caught by the IP checksum. But a datagram misrouted for any other reason would arrive at the wrong machine with a perfectly valid UDP checksum, and be accepted.

Programming Assignment: implementing a reliable transport protocol

The book offers this lab in two versions, the alternating-bit version and the Go-Back-N version. It supplies a simulated network that can corrupt and lose packets. Both are written out here. They differ in about fifteen lines.

The simulator calls five routines. A_output(message) when the application above A has data. A_input(packet) when a packet arrives for A. A_timerinterrupt() when A’s timer expires. And B_input(packet), B_init() on B’s side. You may call tolayer3(side, packet) to send, tolayer5(side, data) to deliver, and starttimer(side, increment) / stoptimer(side).

Version 1 — the alternating-bit protocol

This is rdt3.0 from section 3.4.1, turned into code. One packet outstanding, one sequence bit, one timer.

TIMEOUT = 20.0

def checksum(pkt):
    """Sum of the two sequence fields and every payload byte. The simulator
    corrupts payloads and sequence numbers, so both must be covered."""
    return pkt['seq'] + pkt['ack'] + sum(pkt['payload'].encode())

def corrupt(pkt):
    return pkt['checksum'] != checksum(pkt)

# ── sender ────────────────────────────────────────────────────────────────
A = {'seq': 0, 'waiting': False, 'last': None}

def A_output(message):
    if A['waiting']:
        return                          # refuse; the layer above must retry
    pkt = {'seq': A['seq'], 'ack': 0, 'payload': message, 'checksum': 0}
    pkt['checksum'] = checksum(pkt)
    A['last'] = pkt
    A['waiting'] = True
    tolayer3('A', pkt)
    starttimer('A', TIMEOUT)

def A_input(pkt):
    if corrupt(pkt) or pkt['ack'] != A['seq']:
        return                          # ignore it — P12 explains why
    stoptimer('A')
    A['seq'] ^= 1                       # 0 -> 1 -> 0
    A['waiting'] = False

def A_timerinterrupt():
    tolayer3('A', A['last'])            # the same packet, unchanged
    starttimer('A', TIMEOUT)

# ── receiver ──────────────────────────────────────────────────────────────
B = {'expected': 0}

def B_input(pkt):
    if corrupt(pkt) or pkt['seq'] != B['expected']:
        ack = {'seq': 0, 'ack': 1 - B['expected'], 'payload': '', 'checksum': 0}
    else:
        tolayer5('B', pkt['payload'])
        ack = {'seq': 0, 'ack': B['expected'], 'payload': '', 'checksum': 0}
        B['expected'] ^= 1
    ack['checksum'] = checksum(ack)
    tolayer3('B', ack)

The four lines that matter

if A['waiting']: return — stop-and-wait, enforced. The layer above is told nothing; the simulator simply retries later. This is the rdt_unable_to_send of problem P17.

if corrupt(pkt) or pkt['ack'] != A['seq']: return — the sender ignores anything unexpected. It does not retransmit. Problem P12 shows what happens if you retransmit here instead: the protocol stays correct and its cost grows without bound.

tolayer3('A', A['last']) — a timeout resends the stored packet, byte for byte. Rebuilding it would be a bug: the sequence number must not change.

The else branch in B_input — a duplicate or corrupted packet still gets an acknowledgement, naming the previous sequence number. Staying silent would deadlock the sender, which is exactly the failure problem P6 asks you to construct.

Version 2 — Go-Back-N

Now the sender may have N packets outstanding. Three things change: a sender window instead of a flag, a buffer of unacknowledged packets, and a timeout that resends all of them.

N = 8                 # window size
SEQSPACE = 12         # must exceed N — see problem P23
TIMEOUT = 25.0

A = {'base': 0, 'nextseq': 0, 'buffer': {}, 'queue': []}

def A_output(message):
    A['queue'].append(message)
    _fill_window()

def _fill_window():
    """Send everything the window allows. This is the only place that decides
    whether a packet may go out."""
    while A['queue'] and (A['nextseq'] - A['base']) % SEQSPACE < N:
        pkt = {'seq': A['nextseq'], 'ack': 0,
               'payload': A['queue'].pop(0), 'checksum': 0}
        pkt['checksum'] = checksum(pkt)
        A['buffer'][A['nextseq']] = pkt
        tolayer3('A', pkt)
        if A['base'] == A['nextseq']:
            starttimer('A', TIMEOUT)    # one timer, for the oldest packet
        A['nextseq'] = (A['nextseq'] + 1) % SEQSPACE

def A_input(pkt):
    if corrupt(pkt):
        return
    # Cumulative: this acknowledges everything up to and including pkt['ack'].
    if (pkt['ack'] - A['base']) % SEQSPACE < N:
        stoptimer('A')
        while A['base'] != (pkt['ack'] + 1) % SEQSPACE:
            A['buffer'].pop(A['base'], None)
            A['base'] = (A['base'] + 1) % SEQSPACE
        if A['base'] != A['nextseq']:
            starttimer('A', TIMEOUT)    # packets still outstanding
        _fill_window()

def A_timerinterrupt():
    starttimer('A', TIMEOUT)
    seq = A['base']
    while seq != A['nextseq']:          # go back N — resend the whole window
        tolayer3('A', A['buffer'][seq])
        seq = (seq + 1) % SEQSPACE

# ── receiver: unchanged in spirit, and still a window of one ──────────────
B = {'expected': 0, 'lastack': SEQSPACE - 1}

def B_input(pkt):
    if not corrupt(pkt) and pkt['seq'] == B['expected']:
        tolayer5('B', pkt['payload'])
        B['lastack'] = B['expected']
        B['expected'] = (B['expected'] + 1) % SEQSPACE
    ack = {'seq': 0, 'ack': B['lastack'], 'payload': '', 'checksum': 0}
    ack['checksum'] = checksum(ack)
    tolayer3('B', ack)                  # always reply, even to a discard

Three traps in the Go-Back-N version

One timer, not N. Go-Back-N has a single timer, and it belongs to the oldest unacknowledged packet. Start it when base == nextseq before sending; restart it whenever base moves and packets remain. Giving each packet its own timer turns this into Selective Repeat, which is a different assignment.

Compare sequence numbers modulo the space, never with <. A['nextseq'] - A['base'] < N is wrong the moment the numbers wrap. Every comparison here is written (x - y) % SEQSPACE, and that is not fussiness — it is the bug that makes this lab take an extra evening.

The receiver still discards out-of-order packets, and still replies. The if has no else that skips the acknowledgement. Every arriving packet gets one, and a discarded packet gets a repeat of the last good one. That repeat is what lets the sender’s window advance when an acknowledgement was lost — review question R12(b) is exactly this case.

What you should see when it runs

Set the simulator’s loss probability to 0.2 and its corruption probability to 0.2, and watch the difference.

The alternating-bit version delivers everything, slowly. Each loss costs one full timeout, and nothing at all is in flight while it waits.

The Go-Back-N version is much faster and much noisier. Each loss costs one timeout too — but the retransmission carries up to eight packets, most of which the receiver already discarded once. At a loss rate of 0.2 you will see the same payload sent three or four times.

That contrast is the argument for Selective Repeat in section 3.4.4. Problem P37 counts it exactly: nine segments for Go-Back-N against six for Selective Repeat, when one packet out of five is lost.

Check yourself

Check yourself — reading a real trace

0 of 7 answered
  1. 1.predictFour packets in the TCP capture carry Ack=5841. How many of them are duplicate acknowledgements?

    What makes an acknowledgement a duplicate?

  2. 2.Packet 11 appears in the capture even though it never reaches the server. Why is it there?

  3. 3.predictThe receive window falls from 64,240 to 62,780 to 61,320 to 59,860, then jumps back to 64,240. What is happening?

  4. 4.Nothing in any TCP header names the congestion window. How can you see slow start in this capture?

  5. 5.Adding all the 16-bit words of the UDP segment — including the checksum field — gives 0xFFFF. What does that tell the receiver?

  6. 6.predictIn the Go-Back-N assignment, how many timers should the sender run?

  7. 7.Van Jacobson describes finding "ack clocking". What is it?

An interview with Van Jacobson

Voices from the field

Van Jacobson works at Google and was previously a Research Fellow at PARC (Palo Alto Research Center) . Before that he was co-founder and Chief Scientist of Packet Design, and Chief Scientist at Cisco. Earlier still he headed the Network Research Group at Lawrence Berkeley National Laboratory and taught at UC Berkeley and Stanford.

He received the ACM SIGCOMM Award in 2001 for outstanding lifetime contribution to the field. He received the IEEE (Institute of Electrical and Electronics Engineers) Kobayashi Award in 2002 for “contributing to the understanding of network congestion and developing congestion control mechanisms that enabled the successful scaling of the Internet”. He was elected to the U.S. National Academy of Engineering in 2004.

Please describe one or two of the most exciting projects you have worked on during your career. What were the biggest challenges?

School teaches us lots of ways to find answers. In every interesting problem I’ve worked on, the challenge has been finding the right question. When Mike Karels and I started looking at TCP congestion, we spent months staring at protocol and packet traces asking “Why is it failing?”. One day in Mike’s office, one of us said “The reason I can’t figure out why it fails is because I don’t understand how it ever worked to begin with.” That turned out to be the right question and it forced us to figure out the “ack clocking” that makes TCP work. After that, the rest was easy.

More generally, where do you see the future of networking and the Internet?

For most people, the Web is the Internet. Networking geeks smile politely since we know the Web is an application running over the Internet but what if they’re right? The Internet is about enabling conversations between pairs of hosts. The Web is about distributed information production and consumption. “Information propagation” is a very general view of communication of which “pairwise conversation” is a tiny subset. We need to move into the larger tent. Networking today deals with broadcast media (radios, PONs, etc.) by pretending it’s a point-to-point wire. That’s massively inefficient. Terabits-per-second of data are being exchanged all over the World via thumb drives or smart phones but we don’t know how to treat that as “networking”. ISPs are busily setting up caches and CDNs to scalably distribute video and audio. Caching is a necessary part of the solution but there’s no part of today’s networking — from Information, Queuing or Traffic Theory down to the Internet protocol specs — that tells us how to engineer and deploy it. I think and hope that over the next few years, networking will evolve to embrace the much larger vision of communication that underlies the Web.

What people inspired you professionally?

When I was in grad school, Richard Feynman visited and gave a colloquium. He talked about a piece of Quantum theory that I’d been struggling with all semester and his explanation was so simple and lucid that what had been incomprehensible gibberish to me became obvious and inevitable. That ability to see and convey the simplicity that underlies our complex world seems to me a rare and wonderful gift.

What are your recommendations for students who want careers in computer science and networking?

It’s a wonderful field — computers and networking have probably had more impact on society than any invention since the book. Networking is fundamentally about connecting stuff, and studying it helps you make intellectual connections: Ant foraging & Bee dances demonstrate protocol design better than RFCs, traffic jams or people leaving a packed stadium are the essence of congestion, and students finding flights back to school in a post-Thanksgiving blizzard are the core of dynamic routing. If you’re interested in lots of stuff and want to have an impact, it’s hard to imagine a better field.

The sentence to take from this page

“The reason I can’t figure out why it fails is because I don’t understand how it ever worked to begin with.”

That is the question that produced congestion control, and it was asked in 1988 about a network that was collapsing. The answer, acknowledgement clocking, is the idea underneath everything in section 3.7. A TCP sender releases a new segment when an acknowledgement arrives. So the acknowledgements returning from the receiver pace the sender at exactly the rate the bottleneck link can carry.

You can see it in the capture at the top of this page. Every burst of data from the client is triggered by an acknowledgement arriving, never by a clock.

What to remember

  • The capture is the only place in this chapter where sequence numbers are byte counts you can add up yourself. Do the arithmetic once and §3.5.2 stops being abstract.
  • A retransmission looks exactly like the original except for its position in the file. That is the receiver’s whole problem, in hex.
  • UDP’s checksum is computed the same way TCP’s is, over a pseudo-header that includes the IP addresses — which is why a transport layer ends up reading network-layer fields.