TCP's 3-way handshake... why not 2-way?

Started by
2 comments, last by hplus0603 10 years, 11 months ago

Hi everybody.

I'm writting a simple reliability layer on top of UDP. Not that I want to reinvent the wheel, just most of the data I'll be sending can be sent unreliable but there are bits of data every now and then that need to be sent reliably; and mixing tcp & udp gets even more troublesome.

For establishing a connection, I'm well aware of the 3-way handshake that TCP uses "SYN; SYN-ACK; ACK"
However, what I don't get, is why the third ACK is needed. Yes, I know it's the ultimate proof that the connection has been acknoledged by both parties; but I don't believe it's absolutely necessary.

This question has already been raised in StackOverflow, however the answers there don't satisfy me:

Let's see a communication without the 3rd ack:

Case: Client didn't get SYN-ACK
In this case, Server thinks the connection is established, Client think it's not. Just send another SYN from client until the SYN-ACK is received

Server may start sending data to client because it doesn't know the SYN-ACK got through. Client won't ACK that data because his connection isn't yet established, so server will continue to keep sending data over & over again until timeout. When the Client successfully gets the syn-ack; it will ack that data, hopefully before the timeout. If timed out, server-side it will just look like one connection timed out, and another came in. It's important that server doesn't use the SYNs from client as proof of heartbeat.

Typical view on this: The server needs to allocate resources (for tcp). More SYNs received -> more resources. However I send a random ID generated client-side with the SYN. That way the server identifies the SYN & IP with associated resources using that ID (and deals with client reconnecting and starting a new session as they'll change their ID; or with another client getting the same IP)

ACKs from normal messages always send the ID along the sequence number. So that if client reconnects (or new client got the same IP & port old client had) the server won't think an ack received from an old session is acknowledging packets sent from the current session.

A connection could be hijacked only if a machine gets the same IP address (and port), happens to use the same ID the other client has been using, and all happens before timeout (or there is a man in the middle that can see all data, spoof the IP, and still read the answers from server because he can see all data going to that spoofed IP).

But it's not like TCP is foolproof to hijacking either. Granted, this method is a lot easier to hijack because the ID, IP & port is repeated in every ack. Furthermore I'm interested in preventing "accidental" hijacking, not directed attacks.

The only disadvantage I can see: Potentially much higher bandwidth consumption (because the server may start sending data while client won't acknoledge it), while TCP needs to account for congestion control (which I don't care).
In TCP, everything is silent until SYN-SYN-ACK-ACK has been performed.

Bigger ACKs as a disadvantage could also be mentioned, but TCP overhead is already much bigger than UDP, and again, most of the data I'll be sending is unreliable (no need for ack), while every now and then I send some reliable data (needs guaranteed delivery, guaranteed to arrive in order)

Am I missing something? Why is the third ACK needed?

Thanks!

Advertisement

The ACK is accompanied by sequence numbers.

The RFC is pretty clear on the handshake:

1) A --> B SYN my sequence number is X
2) A <-- B ACK your sequence number is X
3) A <-- B SYN my sequence number is Y
4) A --> B ACK your sequence number is Y

Steps 2 and 3 are combined in a single packet.

The thing about it is that step 4 is also what is used to move the TCP window. By including that step you are using the regular protocol in the initialization process. It is basically just zeroing out the transmission windows.

You aren't blocked from sending data along with step 4.

You could send data with your first ACK if you wanted to, essentially saying "I received packets up to Y, here is some data".

Ahhhhhh thanks a lot. That explains everything.

In my simplified layer both A & B start their sequence at 0. I knew tcp randomized the start, but I didn't realize both A and B (not just B) need to send their start.

Yeah, that answers my question. I knew I was missing something obvious.

Thank you very much!

In my simplified layer both A & B start their sequence at 0.

By doing that, you run a greater risk that replays may confuse the protocol state, and the risk that an attacker may be able to inject his/her own packets because the state is "known." That may or may not actually matter for your case, but it did matter for the TCP designers back in a day where packets would be duplicated and a copy delivered 10 seconds late.
enum Bool { True, False, FileNotFound };

This topic is closed to new replies.

Advertisement