0

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. Wireshark capture 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.

2
  • Err, a bug? It should resend the last ACK it sent, obviously. NB The state is CLOSE_WAIT, not CLOSED_WAIT. The local port isn't closed at all, that's the point, it is waiting to be closed.
    – user207421
    Commented Apr 15, 2014 at 0:45
  • My mistake, it is indeed CLOSE_WAIT and yes, the port is not closed. I will update my post accordingly. And I agree with you, the behavior I expected was the re-sending of the previous ACK. I wonder if Wireshark is missing/not seeing those ACK packets. I got the same behavior from both Ubuntu 12.04 LTS and Ubuntu 13.10 .
    – Paul
    Commented Apr 15, 2014 at 8:58

2 Answers 2

2

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().

2
  • Sorry for the late reply, by calling close I would trigger a FIN+ACK from the server side. The point of this experiment was that you would have the server be completely reactive. I believe, but I am defo no expert, that the garbage collector does not exert any influence, since we are talking about OS level implementation. A connection, unless properly terminated, will stay on for much longer than the respective handle on the java server side. (in this instance, the handle was nothing, since on the server side we only accept)
    – Paul
    Commented May 1, 2014 at 9:54
  • 1
    Looking back, I have to apologize and say that you made a valid point. Garbage collection does clear out sockets (remove file descriptors) left opened and un-referenced.
    – Paul
    Commented Jul 10, 2014 at 13:48
1

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;

Not the answer you're looking for? Browse other questions tagged or ask your own question.