Reputation: 63
We are trying to analyze the behavior of various TCP implementations (Windows 8, Ubuntu 13.10). For that, we are using Scapy, a Python tool that you can use to craft packets, send them over the network and analyze responses.
In our setup, we have a fake Scapy guided client and a listening server. With the client, we send a sequence of TCP packets to the server and check responses. The server just accepts connections and does nothing with them. The aim is to get a simple yet more concrete model of the server behavior. We leave out from the model/ignore complexities such as retransmits, windows, even data exchange.
When analyzing the behavior of a listening server on Windows 8, we got a pretty nice model.
Experimenting on Ubuntu, however, we encountered non-deterministic behavior which for me at least, is hard to explain. I attached here an image of the wireshark log, which comprises several "runs" of similar input packets. Every run is executed via a port that is incremented with each run.
The strange scenarios follow the pattern below:
client ---- SYN 0 _ ---> server [LISTENING]
client <- SYN+ACK 0 1 -- server [SYN_RCVD]
client -- ACK+FIN 1 1 -> server [SYN_RCVD]
client <--- ACK 1 2 ---- server [CLOSE_WAIT]
client ---- ACK 1 20 --> server [CLOSE_WAIT]
client <--- ACK 1 2 ---- server [CLOSE_WAIT] or no_response [CLOSE_WAIT]
Can anyone explain to me, why on receiving an invalid acknowledgement (ack. of a segment that never existed) does the server behave non deterministically? That is, either by resending the ACK that it sent for the ACK+FIN, or by not sending anything. Is this behavior be caused by a configuration parameter? In our setup we use the default settings.
BTW, the simple server code:
while (true) {
try {
Socket socket = server.accept();
}
catch (IOException e) {}
}
UPDATE
I analyzed the model and for Windows 8, when running the same sequence, I get a timeout. This is not conforming to the rfc793 standard that explicitly specifies that:
If the connection is in a synchronized state (ESTABLISHED, FIN-WAIT-1, FIN-WAIT-2, CLOSE-WAIT, CLOSING, LAST-ACK, TIME-WAIT), any unacceptable segment (out of window sequence number or unacceptible acknowledgment number) must elicit only an empty acknowledgment segment containing the current send-sequence number and an acknowledgment indicating the next sequence number expected to be received, and the connection remains in the same state.
Can some of you shed some light on this? Are protocol implementations meant to conform to the standard or is it common to have a certain amount of noncompliance. I guess some of it is inevitable as the standards sometimes fail to specify time limits, but here we are talking about noncompliance in the control flow.
There is still obviously the possibility that I am doing something wrong. :)
Thanks, Paul.
Upvotes: 0
Views: 738
Reputation: 63
To anyone interested, I reckon I managed to track down the "problem". It is a small non-compliance with the specification that should be fixed. If you check the code for processing ack numbers of segments received (available here), there is a check on the ack numbers' acceptability, as established in rfc 793 and rfc 5961.
Based on the rfc 5961, which builds on 793, an ack number is only acceptable if within ((SND.UNA - MAX.SND.WND) <= SEG.ACK <= SND.NXT). In all other cases, the ack number is deemed not acceptable and an ACK should be issued.
In the code itself, ACKs are only issued for segments that fall in the interval ((SND.UNA-(2^31-1)) <= SEG.ACK < SND.UNA - MAX.SND.WND). If the segment is within ((SND.NXT+1 <= SEG.ACK <= SND.UNA - 2^31), they discard the segment without sending an ACK back, even though in this case the segment also bears an invalid ack number. Posted the snippet of the code below. Cheers.
/* If the ack is older than previous acks
* then we can probably ignore it.
*/
if (before(ack, prior_snd_una)) {
/* RFC 5961 5.2 [Blind Data Injection Attack].[Mitigation] */
if (before(ack, prior_snd_una - tp->max_window)) {
tcp_send_challenge_ack(sk);
return -1;
}
goto old_ack;
}
/* If the ack includes data we haven't sent yet, discard
* this segment (RFC793 Section 3.9).
*/
if (after(ack, tp->snd_nxt))
goto invalid_ack;
Upvotes: 1
Reputation: 8021
Is your server written in Java? I guess the "non-determinism" you observed is due to GC timing and may disappear if you explicitly call Socket#close()
or wait on InputStream#read()
.
Upvotes: 2