The Three Way Handshake and Connection Teardown

Transport Layer · 30 min

Core CS · Computer Networks

Three segments to open it, four to close it

A TCP connection is not a wire. It is two numbering agreements, one per direction, held in two machines’ memory. Every plus one in the handshake, and every one of the four segments in the teardown, follows from that.

Step both endpoints through the whole lifecycle, segment by segment
The client sends SYN, seq = x. The server replies SYN, ACK with seq = y and ack = x + 1. The client answers ACK with seq = x + 1 and ack = y + 1, and both ends are open. Every + 1 in that line is there because a SYN consumes a sequence number even though it carries no data.

01 The idea

You cannot acknowledge a byte until you know what it is called

TCP promises a reliable, ordered byte stream: what you write at one end comes out at the other end, once, in order, or the connection fails and tells you. It is built on IP, which promises none of that. IP packets can be lost, duplicated, delayed, and delivered out of order, and there is exactly one mechanism that repairs all four of those at once. Number every byte, and acknowledge by number. A receiver that says I have everything up to 1004 has simultaneously reported what arrived, what did not, and in what order.

That mechanism has a prerequisite. Before a single byte moves, both ends must agree where the numbering starts, and they must agree in both directions, because a TCP connection is full duplex: two independent byte streams running at the same time, each with its own numbering. The client’s bytes are numbered on one line and the server’s on a completely separate one. Neither line has any relationship to the other, and neither side gets to choose the other’s starting value.

It is worth being precise about what a connection actually is, because it is nothing physical. No wire is reserved, no bandwidth is booked, and no router between the two machines is told anything at all. A connection is a pair of matching records, one in each machine’s memory, called the transmission control block. Each record holds the four values that name the connection, the state it is in, and the two sequence numbers. Opening a connection means creating those two records and getting their contents to agree. Closing one means agreeing that it is now safe to delete them. That is the entire job, and every segment in this lesson exists to move one of those two records from one state to the next.

So the handshake has two things to settle at once. First, reachability and willingness: is anything actually listening at that address and port, and does it want this connection? Second, numbering: here is my starting number, tell me yours. Three segments settle both, and it is three rather than two for a reason you will be asked to give out loud: after two segments only one side has had its starting number confirmed. Agreement that only one party has is not agreement.

The close is different, and the difference comes straight from full duplex. The open can set up both directions in one exchange because neither side has sent anything yet, so there is nothing to finish. The close cannot, because the two applications stop talking at different times. A browser finishes sending its request long before the server finishes sending the page. So TCP closes one direction at a time, and each direction costs a FIN and an ACK. Two directions, two FINs, two ACKs, four segments.

A connection is two numbering agreements, one per direction, kept in two machines’ memory. Three segments create both at once, because the third segment is the only thing that makes the agreement mutual. Four segments take them down, because each direction is closed by the side that owns it, and the two sides finish at different times.
Sequence number and acknowledgement numberThe seq field names the first byte carried in this segment, not the segment itself. The ack field names the next byte expected, so ack = 1005 means everything up to and including 1004 arrived. It is cumulative, and it is only meaningful when the ACK flag is set.
Initial sequence numberThe value seq starts at, written x for the client and y for the server. Each side picks its own, freshly and unpredictably, for every connection. The two values are unrelated and never have to be compared with each other.
The phantom byteA SYN and a FIN carry no data, but each one consumes one sequence number. That is what lets them be acknowledged, retransmitted and ordered exactly like data. It is also why every acknowledgement in the handshake and the teardown is one higher than the number you first expect.

02 Worked example

The three segments, with the numbers written on them

One connection, worked end to end, and it is the same connection for the rest of the lesson: the console in section 04 opens on it, the teardown table in section 03 uses it, and every number in the cheat sheet is checked against it. The client’s initial sequence number is x = 1000 and the server’s is y = 5000. Both were chosen at random; small round values are used here only so you can check the arithmetic in your head. Read left to right.

CLOSED and LISTENThe server has bound a port and called listen. It sits in LISTEN holding no state for any particular client. The client is in CLOSED, which means no connection record exists at all.
1. SYN, seq = 1000Client to server. The SYN flag is set, there is no data, and there is no ACK flag because there is nothing yet to acknowledge. Client goes CLOSED to SYN_SENT; the server allocates a record and goes LISTEN to SYN_RCVD.
2. SYN, ACK · seq = 5000, ack = 1001Server to client, doing two jobs in one segment. Its SYN carries the server’s own number 5000. Its ack = 1001 says I have 1000, send me 1001 next. The client now knows the server is there, so the client reaches ESTABLISHED.
3. ACK · seq = 1001, ack = 5001Client to server. seq = 1001 because the client’s SYN used 1000; ack = 5001 because the server’s SYN used 5000. Only now does the server reach ESTABLISHED. This is the segment that is worth justifying, and the one interviewers push on.
Open, and already numberedBoth ends are in ESTABLISHED and the next byte in each direction is already agreed: 1001 from the client, 5001 from the server. No data has moved, and two sequence numbers have already been spent.

Three segments, five numbers: 1000, then 5000 and 1001, then 1001 and 5001. The first segment writes only a seq, because with no ACK flag the acknowledgement field carries nothing. Two of those five values were freely chosen, 1000 and 5000; the other three are forced. Check them the way an examiner does. The client has sent no data, so how is its next byte 1001 and not 1000? Because the SYN occupied 1000. If you write ack = 1000 on the second segment you have said I still want the SYN, and the client would retransmit it forever.

Now the highlighted node, because why three and not two is the follow-up you will get and the one that is answered badly. Suppose we stopped after two segments and let the server call itself open the moment it had sent its SYN and ACK. Two things break, and you should name both. The first is that the server would be opening a connection on the strength of a single arriving SYN, and a single SYN is easy to fake: the source address in an IP header is not verified by anything, so any machine can send a SYN claiming to be any address. The third segment is the only thing that proves the sender can actually receive at the address it claimed, because the number it has to echo, y + 1, was never on the wire in that direction. It is the closest thing TCP has to a proof of address.

The second reason is the one people forget, and it is more fundamental. After two segments the client has had its number confirmed and the server has not. The server sent y = 5000 into the network and has heard nothing back about it. If that SYN and ACK were lost, the server would sit in SYN_RCVD believing 5000 was agreed while the client was still in SYN_SENT with no connection open at all, and the very first data segment the server sent would be numbered from a value the client had never seen. The third segment is not politeness. It is the acknowledgement of y, and without it only one of the two numbering agreements exists. There is also a duplicate problem: a SYN delayed for a long time in the network can arrive after the connection it belonged to is long gone, and a two-segment scheme would open a connection on it. The third segment kills that too, because the server’s SYN and ACK goes to the real host, which has no such connection and answers with an RST rather than the expected y + 1, and the server tears the attempt down.

Two smaller points that carry marks. First, why the SYN consumes a sequence number at all when it carries no data. Because a SYN can be lost like anything else, and the only machinery TCP has for detecting a loss and retransmitting is this number has not been acknowledged yet. Give the SYN a number and it inherits the whole reliability mechanism for free, with no special case anywhere. The same argument applies word for word to the FIN. Second, why the initial sequence number is random rather than starting at zero. Two reasons. A predictable ISN lets an off-path attacker guess the numbers a connection will use and inject a segment into it without ever seeing the traffic, which was a real and widely exploited attack; RFC 6528 specifies generating the ISN from a secret hash of the four values that name the connection. And even with no attacker, a fresh random value makes it very unlikely that a straggling segment from an earlier connection between the same two ports lands inside the current connection’s window and is accepted as real data.

03 Mechanics

The states, the four segments of a close, and the fields they ride in

Three tables, in the order the questions come. First the states. A TCP endpoint is a state machine, and an interview question about this topic is almost always which state is this end in now or what moves it out of that state. The last column is the one to read carefully, because one of these rows is left only when a timer expires and one is left only when your own program acts, and confusing those two is a production incident rather than a lost mark. They are the two rows marked in amber.

StateWhoseYou arrive here byYou leave when
CLOSEDeitherThe start and the end. No connection record exists.The application connects, or binds and listens.
LISTENpassiveThe application called listen() on a bound port.A SYN arrives for that port.
SYN_SENTactiveYou sent a SYN.The SYN and ACK arrives, or you give up and time out.
SYN_RCVDpassiveA SYN arrived and you replied with SYN and ACK.The third segment, the ACK, arrives.
ESTABLISHEDbothThe handshake finished.Either application calls close(), or a FIN arrives.
FIN_WAIT_1active closerYou sent your FIN.Your FIN is acknowledged.
FIN_WAIT_2active closerYour FIN was acknowledged. You are now half closed.The peer’s FIN arrives.
CLOSE_WAITpassive closerThe peer’s FIN arrived and you acknowledged it.your own application calls close() — no timer will ever do it for you
CLOSINGboth, rareYou and the peer sent FINs at the same instant.Your FIN is acknowledged. Then you go to TIME_WAIT too.
LAST_ACKpassive closerYou sent your own FIN from CLOSE_WAIT.The ACK of that FIN arrives. Then you are CLOSED.
TIME_WAITactive closerYou acknowledged the peer’s FIN.2 x MSL of wall clock passes — nothing else ends it

The row that becomes a support ticket. CLOSE_WAIT is the only state on that list that a machine can be stuck in indefinitely with nothing wrong on the network. It means the kernel has been told the peer is finished sending, has acknowledged that, and is now waiting for your program to call close() on the socket. A server that leaks file descriptors, or catches an exception on a path that skips the close, accumulates CLOSE_WAIT sockets until it runs out of descriptors and stops accepting new connections. The give-away is that the count only ever goes up. Contrast TIME_WAIT, which also accumulates in thousands on a busy machine but drains on its own and is usually not a fault at all.

Now the teardown, on the same connection as section 02. The client sent 4 bytes of data after the handshake, so it used numbers 1001 to 1004 and its next unused number is 1005. The server sent no data, so its next unused number is still 5001.

#SegmentSenderOn our connectionSender is thenReceiver is then
1FIN, ACKactive closer (client)seq=1005 ack=5001FIN_WAIT_1CLOSE_WAIT
2ACKpassive closer (server)seq=5001 ack=1006CLOSE_WAITFIN_WAIT_2
Half closed. One direction is shut and the other is not. The client will send no more data but must keep reading; the server may keep sending for as long as its application wants. Nothing here is an error and there is no timer on it.
3FIN, ACKpassive closer (server)seq=5001 ack=1006LAST_ACKTIME_WAIT
4ACKactive closer (client)seq=1006 ack=5002TIME_WAIT for 2 x MSLCLOSED

Read the four ack numbers, because they are the whole check. The client’s FIN sits at 1005, so the server acknowledges with 1006: the FIN consumed 1005 exactly as the SYN consumed 1000. The server’s FIN sits at 5001, the number that has been waiting unused since the handshake, so the client acknowledges with 5002. Notice that the server’s seq is 5001 in both of its segments, and that is correct rather than a typo: segment 2 is a pure ACK carrying no data, and a segment that carries nothing consumes no sequence number, so the server’s numbering did not advance between them.

Why four and not three, and the honest real-world clause. The open needed three because both directions could be set up in one exchange, with the server’s SYN riding along on the ACK of the client’s SYN. The close cannot do that in general, because the server’s ACK is due immediately (it has to confirm the client’s FIN) while the server’s FIN is not due until its application has finished writing, which could be minutes later. Two events with different timing cannot always share a segment. In practice, when the server’s application closes straight away, the stack does combine them and you see a single FIN, ACK where the table shows segments 2 and 3 — a three-segment close on the wire. Give four as the answer, then add that clause: it shows you know the reason for the four rather than the count.

Half close is a feature, not a leftover. Calling shutdown(fd, SHUT_WR) instead of close(fd) sends the FIN for your sending direction only and leaves you able to read. The classic use is a client that streams a request of unknown length: it shuts down its write side so the server sees end-of-input and can start replying, while the client stays open to read that reply. This is exactly the state between segments 2 and 3 in the table, and it can last as long as the application wants. If you are asked whether a connection where one side has sent a FIN and the other has not is broken, the answer is no, that is a legitimate half-closed connection.

TIME_WAIT, and the two jobs it is actually doing. Only the side that closed first enters it, and it stays there for 2 x MSL, twice the maximum segment lifetime — the longest a segment is allowed to survive in the network before being discarded. RFC 793 puts MSL at 2 minutes, so the textbook TIME_WAIT is 4 minutes; Linux fixes it at 60 seconds, effectively an MSL of 30 seconds. Job one: if the final ACK is lost, the peer sitting in LAST_ACK will retransmit its FIN, and somebody has to still be there to answer it. Close immediately and that retransmitted FIN meets a machine with no matching connection, which replies with an RST, and the peer’s clean shutdown turns into an error. Job two: the connection is named by a four tuple of source address, source port, destination address and destination port, and that tuple can be reused. Holding it for 2 x MSL guarantees every straggling segment from this connection has expired before a new connection can adopt the same four values, so no new connection can ever accept an old segment as its own data.

What TIME_WAIT costs, and what people wrongly do about it. The socket is gone but the four tuple is reserved, and on a machine that opens huge numbers of short outbound connections to one destination, the only part of that tuple free to vary is the source port. Ports are a 16-bit field; the IANA ranges are well known 0 to 1023, registered 1024 to 49151 and dynamic or ephemeral 49152 to 65535, and Linux’s own default range is 32768 to 60999, which is 28,232 ports. At a few thousand connections per second, held for 60 seconds each, that is exhausted in seconds. The right fixes are to reuse connections rather than reopen them, to widen the port range, or to have the other side close first so it carries the TIME_WAIT. SO_REUSEADDR lets a listener bind a port that still has connections in TIME_WAIT, and net.ipv4.tcp_tw_reuse lets outgoing connections safely reuse them; tcp_tw_recycle did something similar and broke badly behind NAT, and was removed from Linux in 4.12.

Half open, SYN floods and SYN cookies. A half-open connection is one where one side has vanished — crashed, rebooted, or been unplugged — while the other still believes the connection is ESTABLISHED. Nothing detects it until either side tries to send, at which point the survivor gets an RST, or until a keepalive probe fires; the Linux keepalive idle default is 7200 seconds, which is why a dead peer can go unnoticed for two hours. A SYN flood weaponises the gap between segments 1 and 3: the attacker sends SYNs from forged source addresses, the server allocates a record and enters SYN_RCVD for each, sends a SYN and ACK to an address that will never reply, and the third segment never comes. The half-open queue fills and real clients are refused. The defence is SYN cookies: instead of allocating anything, the server encodes the connection state into the initial sequence number it puts in the SYN and ACK, using a keyed hash it can verify later. It keeps no state at all until the third segment comes back carrying y + 1, at which point it recomputes the cookie and rebuilds the connection. The cost is that not every TCP option fits in 32 bits, so some are lost or smuggled elsewhere, which is why cookies are typically switched on only when the queue is actually under pressure.

Simultaneous open and simultaneous close, which exist and are occasionally asked. If both hosts send a SYN to each other before either receives one, neither is ever in LISTEN. Each is in SYN_SENT, receives a SYN rather than a SYN and ACK, moves to SYN_RCVD and replies with its own SYN and ACK, and both reach ESTABLISHED after four segments with no server side at all. Simultaneously closing works the same way: both send FINs, both move FIN_WAIT_1 to CLOSING rather than FIN_WAIT_2, both then acknowledge, and both ends sit in TIME_WAIT. Neither case is common on a real network, but both follow from the state table above with no new rules, which is what makes them a fair question.

Finally, the fields all of this rides in. You are expected to know the size of a TCP header and what the handshake writes into it, because the follow-up to describe the handshake is often and what does that segment actually look like.

FieldWidthWhat the handshake and teardown put in it
Source port, destination port16 bits eachWith the two IP addresses these form the four tuple that names the connection. Nothing else identifies it.
Sequence number32 bitsOn a SYN it is the ISN. Afterwards it is the number of the first data byte in this segment; on a FIN it is the number the FIN itself occupies.
Acknowledgement number32 bitsThe next byte expected, cumulative. Only meaningful when the ACK flag is set, which is why segment 1 of the handshake leaves it unused.
Data offset4 bitsHeader length in 32-bit words, so 5 means the 20-byte minimum and 15 means the 60-byte maximum. That is where the options live.
Control bits6 in RFC 793URG, ACK, PSH, RST, SYN, FIN. The handshake uses SYN and ACK; the teardown uses FIN and ACK; a refusal or an abort uses RST. ECN later added two more bits.
Window16 bitsHow much the sender is willing to receive. Both sides advertise it from their very first segment, so flow control is running before any data moves.
Checksum16 bitsCovers the header, the data, and a pseudo-header containing the two IP addresses, which is what binds a segment to the addresses it was sent between.
Totals. A TCP header is 20 bytes with no options and at most 60 bytes with them. A UDP header is 8 bytes, fixed, and has no sequence number and no acknowledgement number — which is precisely why UDP has no handshake to describe.

05 Cheat sheet

The answers that get asked, and the wrong ones that get given

Every row is something you can be asked to state in under ten seconds. The right-hand column is the specific wrong answer that gets written down, not a general caution.

What they askThe answerThe trap
The three segments, in fullSYN seq=x · SYN,ACK seq=y ack=x+1 · ACK seq=x+1 ack=y+1ack=x on the second segment — the SYN already consumed x
Why three and not twothe third segment is the only acknowledgement of ySaying "because TCP is reliable". Name what the third segment proves and to whom.
Sequence cost of a SYN or a FINone number each, though neither carries dataacking a FIN with the FIN’s own number — it is one higher
Sequence cost of a pure ACKzero — the sender’s numbering does not advanceAdvancing the server’s seq after it sends a bare ACK.
Why the ISN is randomblind injection by an off-path attacker, and old duplicatesSaying "for security" with nothing after it.
Segments to closefour: FIN, ACK, FIN, ACKthree — that is the open; the close is one FIN per direction
Why fourTCP is full duplex and each direction closes on its own scheduleNot naming full duplex. It is the whole reason.
Half closed vs half openhalf closed is legal and one-directional; half open is one side goneusing them interchangeably — one is a feature, one is a fault
Which side gets TIME_WAITwhichever side closed first, the active closerAnswering "the server". It depends only on who called close first.
How long TIME_WAIT lasts, and why2 x MSL · RFC 793 MSL is 2 min, so 4 minForgetting the two jobs: answer a retransmitted FIN, and outlive old segments. Linux fixes it at 60 s.
Sockets piling up in CLOSE_WAITthe application never called close()blaming the network — no timer clears CLOSE_WAIT
SYN flood, and the defencefill the half-open queue · defend with SYN cookiesSaying "a firewall". Cookies keep no state until the third segment returns.
Header sizes and port rangesTCP 20 to 60 bytes, UDP 8 · ports 0-1023, 1024-49151, 49152-65535quoting 20 bytes as fixed — options take it to 60
Two numbering lines, never oneThe client’s bytes and the server’s bytes are numbered independently, and x and y have no relationship. Every ack you write refers to the other side’s line, and every seq refers to your own. Most wrong answers come from mixing the two lines together.
SYN and FIN are phantom bytesEach consumes one sequence number while carrying nothing, so both inherit retransmission and ordering with no special case. That single design decision is the source of every + 1 in the handshake and every + 1 in the teardown.
Four segments, because two directionsThe open configures both directions at once because neither has sent anything yet. The close cannot, because the two applications stop at different times, so each direction is shut by the side that owns it. Full duplex is the answer to "why four", not "because closing is harder".

06 Where & why

These state names are printed by tools you will run

None of this is a teaching abstraction. The eleven state names in section 03 are literal strings that real commands print, and three of the four systems below differ from the textbook in a way worth naming out loud in an interview.

Linux · ss and netstat
The state column is the state machine

ss -tan prints one line per socket with a State column that reads LISTEN, SYN-SENT, SYN-RECV, ESTAB, FIN-WAIT-1, FIN-WAIT-2, CLOSE-WAIT, LAST-ACK and TIME-WAIT. Note that ss abbreviates ESTABLISHED to ESTAB while the older netstat spells it out, which trips people reading two outputs side by side. A count by state, ss -tan | awk '{print $1}' | sort | uniq -c, is the first command to run on a server that has stopped accepting connections.

nginx
Two queues sit between segment 1 and your code

A SYN arriving at listen 80; does not reach nginx. It joins the kernel’s SYN queue while the connection is in SYN_RCVD, and only on the third segment does it move to the accept queue, where accept() collects it. The backlog= parameter on the listen directive sizes the second queue, capped by net.core.somaxconn, and net.ipv4.tcp_max_syn_backlog sizes the first. Overflow of the SYN queue is exactly what a SYN flood aims at, which is why net.ipv4.tcp_syncookies ships enabled on modern distributions and engages only when that queue overflows.

Cloudflare
The handshake terminates at the edge, on purpose

When a site is behind Cloudflare, the three segments complete against a Cloudflare machine near the client, not against the origin server, and a separate connection carries the request onward. That is what lets a SYN flood be absorbed: the forged SYNs never reach the origin’s SYN queue at all, and the edge answers them statelessly with cookies. It also explains a real operational consequence, that the origin sees Cloudflare’s addresses as the peer of every connection and has to read the client’s real address out of a header instead of out of the four tuple.

Wireshark
It hides the ISN from you by default

The filter tcp.flags.syn == 1 && tcp.flags.ack == 0 isolates every connection attempt in a capture, and adding tcp.analysis.retransmission shows the SYNs that had to be sent twice. The catch worth knowing: Wireshark displays relative sequence numbers by default, so every connection appears to start at 0 and the client’s ACK appears to carry seq=1. That is a display convenience, not the wire. Turn relative sequence numbers off in the TCP protocol preferences and the real random ISN from section 02 appears.

Everything in this lesson is a rule about when it is safe to forget a connection. The handshake is a rule about not creating one too easily; the four-segment close is a rule about not deleting one while the other side is still talking; TIME_WAIT is a rule about not deleting one while its old segments could still be alive. Read that way, the whole lifecycle is a single idea seen from three angles.

07 Interview questions

What they ask, and what they follow up with

This is the single most asked question in the transport layer, and almost nobody is asked it only once. The reliable pattern is that you describe the three segments, and then they ask why there are three, or what happens if one is lost, or which side waits at the end. Say the sequence numbers out loud while you draw; a description with no numbers on it reads as memorised.

Walk me through the TCP three way handshake, with the sequence numbers.
The client sends a segment with the SYN flag set and seq = x, its randomly chosen initial sequence number, and moves to SYN_SENT. The server replies with SYN and ACK together, carrying seq = y and ack = x + 1, and moves to SYN_RCVD. The client answers with an ACK carrying seq = x + 1 and ack = y + 1, at which point the client is ESTABLISHED, and the server becomes ESTABLISHED when that segment arrives. Both plus ones are there because a SYN consumes one sequence number even though it carries no data.
Why does it take three segments? What would go wrong with two?
Two would leave one side’s number unacknowledged. After the server has sent its SYN and ACK, nothing has confirmed that y arrived, so the server would be sending data numbered from a value the client may never have seen. The third segment is the acknowledgement of y, and only with it does the agreement exist in both directions. It also stops a duplicated or spoofed SYN from opening a connection: source addresses in IP are not verified, so the only proof that the sender can actually receive at the address it claimed is that it echoes back y + 1, a number that was never carried in that direction.
Why does a SYN consume a sequence number when it carries no data?
So that it can be acknowledged, and therefore retransmitted, by the machinery that already exists. TCP detects loss by noticing that a sequence number has not been acknowledged; give the SYN a number and it inherits that whole mechanism with no special case anywhere in the code. It is the same argument for the FIN. The practical consequence is the one that shows up in every exam: an acknowledgement of a SYN or a FIN is always one more than the number the flag occupied.
Why is the initial sequence number random rather than zero?
Two reasons, and interviewers want both. A predictable ISN lets an off-path attacker who cannot see your traffic guess the numbers your connection will use and inject a forged segment into it, which was a real and widely used attack; RFC 6528 specifies deriving the ISN from a secret keyed hash of the four tuple. Second, even with no attacker, a fresh unpredictable value makes it very unlikely that a delayed segment from an earlier connection between the same two ports lands inside the current connection’s window and is accepted as genuine data.
Why does closing take four segments when opening takes three?
Because TCP is full duplex: it is two independent byte streams, one per direction, and each has to be shut down by the side that owns it. The open can set up both directions in one exchange because neither side has sent anything yet, so the server’s SYN rides along on its ACK. The close cannot in general, because the ACK of the peer’s FIN is due immediately while your own FIN is not due until your application has finished writing. So it is FIN, ACK, FIN, ACK. In practice, when the second application closes at once, the stack combines its ACK and its FIN into one segment and you see three on the wire.
What is a half-closed connection, and is it an error?
It is not an error, it is a feature. One side has sent its FIN and had it acknowledged, so it will send no more data, but the other side has not sent its FIN and may keep sending as long as it likes. You get it deliberately by calling shutdown(fd, SHUT_WR) instead of close(fd). The classic use is a client sending a request of unknown length: it shuts down its write side so the server sees end of input and can begin replying, while the client stays open to read that reply. Nothing times it out, so it can last for as long as the application wants.
Which side ends up in TIME_WAIT, how long does it sit there, and why does it wait at all?
Whichever side closed first, the active closer. It is not about client or server; if the server calls close first, the server waits. It sits there for 2 x MSL, twice the maximum segment lifetime, which RFC 793 puts at 2 minutes so the textbook figure is 4 minutes, while Linux fixes it at 60 seconds. It waits because it has just sent an ACK that nothing will ever acknowledge, so it cannot know the ACK arrived, and because the four tuple it is holding could otherwise be reused while old segments from this connection are still alive in the network.
So what actually breaks if a host skips TIME_WAIT and goes straight to CLOSED?
The peer is in LAST_ACK waiting for that final ACK. If the ACK was lost, its retransmission timer fires and it re-sends its FIN. That FIN now arrives at a machine with no matching connection, so the machine replies with an RST, and the peer’s shutdown ends in an error rather than cleanly. The second failure is subtler: with the four tuple free immediately, a new connection can be opened on the same four values while a delayed segment from the old one is still in flight, and if its sequence number happens to fall in the new connection’s window it is accepted as real data on the new stream.
A server has thousands of sockets sitting in CLOSE_WAIT. What do you tell the team?
That it is an application bug, not a network problem. CLOSE_WAIT means the kernel received the peer’s FIN, acknowledged it, and is waiting for your program to call close() on that socket. No timer will ever move it out, so the count only goes up until the process runs out of file descriptors and stops accepting connections. Look for a path that returns or throws without closing the socket, usually an error branch or a missing finally block. Contrast that with TIME_WAIT, which also piles up in thousands on a busy host but drains on its own and is normally not a fault.
What is a half-open connection, and how does it differ from a half-closed one?
Half-open is a fault: one side has gone away without sending a FIN, because it crashed, rebooted or lost power, while the other side still believes the connection is ESTABLISHED. Half-closed is deliberate and legal. Nothing detects a half-open connection until somebody tries to send, at which point the survivor gets an RST from the rebooted peer, or until a keepalive probe fires, and the Linux keepalive idle default is 7200 seconds, so a dead peer can go unnoticed for two hours. Application-level heartbeats exist mainly because that default is far too slow.
What is a SYN flood, and how do SYN cookies stop it?
The attacker sends SYNs with forged source addresses. For each one the server allocates a connection record, moves to SYN_RCVD and sends a SYN and ACK to an address that will never reply, so the third segment never arrives and the entry sits in the half-open queue until it times out. Fill that queue and genuine clients are refused. SYN cookies remove the thing being exhausted: instead of allocating anything, the server encodes the connection parameters into the initial sequence number it sends back, using a keyed hash, and keeps no state at all. When a real third segment arrives carrying y + 1, the server recomputes the cookie, verifies it, and builds the connection then. The cost is that not every option fits in 32 bits, which is why cookies usually engage only when the queue is actually overflowing.
UDP has no handshake at all. What exactly do those three segments buy you?
They buy agreement, and agreement is the prerequisite for everything else TCP does. UDP has an 8-byte header with no sequence number and no acknowledgement number, so there is nothing to agree on and nothing to acknowledge; it sends a datagram and is finished. TCP’s three segments establish two independent numbering lines, confirm that something is listening and willing rather than discovering it after you have already sent data, prove the peer can receive at the address it claimed, and exchange the initial window so flow control is running before the first byte moves. The price is one round trip of latency before any data, plus a second for TLS on top, which is exactly why real systems reuse connections instead of opening a new one per request.

08 Practice problems

Six to work on paper

Draw the two sequence lines for every one of these before you write a single ack number, and mark on them what each transmission occupies. Four of the six turn on something occupying a number without carrying data, and one of them cannot be answered at all without counting two one-way trips rather than one.

Fourteen numbers for twelve bytes

Easy
A client opens a connection with an initial sequence number of 7000, sends 12 bytes of data, and then closes. List every sequence number the client occupies, give the number carried on its FIN, and give the acknowledgement number the peer puts in the ACK of that FIN.
Follow-up
The client transmitted 12 bytes but occupied 14 numbers. Naming the two that carried nothing, and placing them at the right ends of the range, is the whole exercise.
Show the hint
Draw the client’s sequence space as a number line starting at its ISN and place every transmission on it in order before you compute any acknowledgement number.

The capture that never finishes

Easy
A capture of one connection shows, in order: SYN; SYN,ACK; ACK; PSH,ACK carrying 20 bytes; ACK; FIN,ACK; ACK. Minutes later both machines still list the connection. Name the state each machine is in, name the segment that has not been sent, and say which machine has to send it.
Follow-up
Nothing has been lost and no timer has failed. This is a complete and perfectly legal picture of a connection, and one of the two machines is entitled to leave it exactly like this indefinitely.
Show the hint
Count the FINs, then count the directions. Ask what event, and only what event, moves the machine that is waiting.

Both hosts dial at once

Medium
Host P with ISN 300 and host Q with ISN 800 each send a SYN to the other before either receives one. Give every segment that follows, in order, with its flags, its seq and its ack, justify each ack number you write, and give the sequence number of the first data byte each host will send.
Follow-up
Neither host is ever in LISTEN, and there is no client and no server here. Every ack you write refers to the other host’s numbering line and the two lines never touch, so one crossed number ruins the whole answer.
Show the hint
Treat each host separately as a state machine that has just sent a SYN and then receives a SYN rather than a SYN and ACK, and read the state table for what that transition does.

The port range runs out

Medium
A load generator opens and closes 5,000 short connections per second from one machine to one server address and port, and it is always the side that closes first. TIME_WAIT is 60 seconds and the machine is configured with an ephemeral port range of 32768 to 65535 inclusive. Give the number of usable ports, say whether the rate is sustainable, and give the highest rate that is.
Follow-up
Neither the server nor the bandwidth is the bottleneck. The resource that runs out is a 16-bit field, and it runs out on the machine that behaved entirely correctly.
Show the hint
A connection is named by four values and three of them are fixed by this test, so count only the one that is free to vary, then multiply by how long each value stays reserved.

The acknowledgement that vanished

Medium
On the connection from section 02, the server’s ACK of the client’s FIN, seq=5001 ack=1006, is lost while every other segment arrives. Give the state each side is in immediately afterwards, say which side’s timer fires and what it retransmits, and give the total number of segments the teardown ends up costing.
Follow-up
There are two ways this recovers and only one of them costs a retransmission. The cheaper one does not depend on any timer at all, but on what the server’s application happens to do next.
Show the hint
The acknowledgement of a FIN does not have to travel in a segment of its own. Ask what the server’s own FIN would carry in its acknowledgement field, and what state that would put the client in directly.

Why two lifetimes and not one

Hard
TIME_WAIT lasts 2 x MSL, and it is doing two separate jobs. Take each job on its own and argue in units of MSL how much waiting that job alone would need, then construct a concrete failure for a host that waits only 1 x MSL: give the order of events, which segment survives long enough to do damage, and what the peer concludes.
Follow-up
The two jobs do not need the same amount of time. One of them alone fixes the number and the other is then satisfied for free, so most of the marks are in working out which job is actually setting the constant.
Show the hint
For the retransmission job, count two one-way trips rather than one: how long the lost acknowledgement could still have been alive out there, and then how long the peer’s reply takes to reach you after that.