TCP connection in slow motion

I’ve always been curious about the netstat output: what is the meaning of the different TCP connection states? How the connection transit from a state to another? I am also working on a different post on TCP errors, so I need to understand better the different TCP connection phases.

I then decide to “film” a TCP connection between two computers in slow motion. The idea is to show how the state of the TCP connection changes in the two machines and what packets transit between the two hosts.

Setup

To perform the experiment I have used a laptop (thinkpad) and a raspberry pi (raspberrypi) connected through my home’s wifi network.

A server application runs on the thinkpad and listens on port 9090. The server is just a netcat in listening mode:

nc -l 9090

A client application runs on the rapsberrypi and connects to the server on the thinkpad. Again the client is a netcat instance:

nc 192.168.1.108 9090

Note that the server and the client applications run on top of a Linux OS. The operative system manages the TCP connection and offers to the applications primitives to listen for connection, accept incoming connections, create connection, send and receive data and finally close connections. In the post, I will refer to the server and the client both to indicate the application and/or the OS.

The packets between the client and the server are captured and visualized with Wireshark.

The connection status is monitored in both machines with a repeated netstat command with output filtered on port 9090 and visualization of the timers (-o flag):

watch -n 1 "netstat -nao | grep \"9090\|Proto Rec\""

To slow down the packets between the two machines I have used Traffic Control. Here the bash script that setups TC:

#!/bin/bash

interface=wlp5s0
port=9090
delay=3s

# clean all the queuing disciplines
sudo tc qdisc del dev $interface root

# adds a queuing discipline with id 1:0 to the outbound traffic (root/egress)
# the discipline is PRIO https://linux.die.net/man/8/tc-prio 
# the prio class is setup to send all the packets to band 0
sudo tc qdisc add dev $interface root handle 1:0 prio priomap 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0

# adds a queuing discipline to the class 2 of the prio discipline we defined before
# the discipline with id 20:0 will use net emulator to delay packets
sudo tc qdisc add dev $interface parent 1:2 handle 20:0 netem delay $delay

# adds a filter to the class 0 of the prio discipline we defined before
# the classifier (u32) is a generic filter based on packet properties 
# the match is on packet with port $port
# the selected packets are sent to class 2 of the prio discipline
sudo tc filter add dev $interface parent 1:0 protocol ip u32 match ip sport $port 0xffff flowid 1:2
sudo tc filter add dev $interface parent 1:0 protocol ip u32 match ip dport $port 0xffff flowid 1:2

# To remove the delay
# sudo tc -p qdisc ls dev wlp5s0
# sudo tc qdisc del dev wlp5s0 root

Screen layout

Top left: Wireshark, all the packets between the two hosts will be visible there.

Top right: the TCP status diagram for both machines. The blue colour is used for the server, the red colour for the client. The green colour is used to indicate that both server and client are in the same state.

Bottom left – first console: the console that runs the server on the thinkpad machine.

Bottom left – second console: the connection status for the server. A repeated run of the netstat command on the thinkpad machine.

Bottom right – first console: the console that runs the client on the raspberrypi machine.

Bottom right – second console: the connection status for the client. A repeated run of the netstat command on the rapsberrypi machine.

Storyline

Server and client are not running. Both side of the connection are in state CLOSED (there is no connection).

The server application is launched and starts listening on port 9090. The connection on the server side is now in status LISTEN. Netstat shows a process listening on port 9090. No packets exchanged or activity on the client side.

TCP connection establishment

The client application is launched and initiates a connection toward the server. The client sends a SYN packet: the 3-way handshake has begun. The connection status on the client side is now SYN_SENT.

The server receives the SYN packet and moves to status SYN_RECEIVED. The server then sends back a SYN-ACK packet.

The client receives the SYN-ACK packet and moves the connection status to ESTABLISHED. The client sends back an ACK packet.

The server receives the ACK packet and moves the connection status to ESTABLISHED.

The server application has been notified of the presence of the incoming connection and will manage it in a separate thread. At this point, there are two sockets open on the server side: one listing for new connections and another for the established connection to the client.

The 3-way handshake is now completed and the connection established.

In the Wireshark screen, you can notice some packets visualized in light grey, those are packets that have been re-sent because an ack was not received in time (packets delayed by traffic controller).

Data communication

The server sends a “Hello” message to the client. The client receives it, print it in the console and sends back an ACK packet.

The client sends a “Bye” message to the server. The server receives it, print it in the console and sends back an ACK packet.

Notice the flag PSH, it indicates that the received data should be pushed to the application.

TCP connection termination

The connection can be terminated by the server or by the client, in this case, the server initializes the process.

The server application is terminated, this indicates to the OS that the socket can be closed. The server sends a FIN packet to the client and sets its connection status to FIN_WAIT1. The four-way handshake has begun.

The client receives the FIN packet, sets the connection status to CLOSE_WAIT and sends back an ACK packet.

The server receives the ACK packet from the client and moves to FIN_WAIT2.

The client process is terminated and the connection can be closed. The client sends a FIN packet to the server.

The server receives the FIN packet and replies with the last ACK packet moving to TIME_WAIT status. This status is used to receive and discard packets that have been delayed in the network.

The client receives the last ACK and closes the connection.

The server waits for the timer to expire and then closes the connection.

The TCP connection is now terminated.

Video

Finally the video. Please watch it at the highest resolution.

How to make two ESP8266 communicate through WiFi with Espruino and JavaScript

In this article we will make two ESP8266 communicate using the WiFi network. We will use Espruino and some JavaScript code.

Setup the WiFi

Espruino offers a WiFi module that let us to connect to a WiFi network.

The code below will connect our ESP to the WiFi network and will print the information about the obtained IP plus other network information:

var wifi = require("Wifi");
wifi.connect("***WIFI*SSID***", {password:"***WIFI*PASSWORD***"}, function(err) {
  console.log("connected? err = ", err, " info = ", wifi.getIP());
});

The result should be:
 _____                 _
|   __|___ ___ ___ _ _|_|___ ___
|   __|_ -| . |  _| | | |   | . |
|_____|___|  _|_| |___|_|_|_|___|
          |_| http://espruino.com
 1v93 Copyright 2016 G.Williams
Espruino is Open Source. Our work is supported
only by sales of official boards and donations:
http://espruino.com/Donate
Flash map 4MB:512/512, manuf 0xe0 chip 0x4016
>
=undefined
connected? err =  null  info =  {
  "ip": "192.168.1.106",
  "netmask": "255.255.255.0",
  "gw": "192.168.1.1",
  "mac": "5c:cf:7f:f8:53:5a"
 }

Please take note of the IP for both the devices, currently there are no other way to discover each other.

Now both the devices are connected to the WiFi network. Now we can make them talk each other. In this example we elect one board server and the other client. The client board will start a TCP communication toward the server board. Once connected they will able to exchange messages.

Setup the server

The server first setup the WiFi connection, then creates a socket server on port 1234 for receiving connection from the clients. When a connection is established it will print the messages coming from the client and will close the connection saying “I’m a server! Goodbye.”.

function handleClient(socket) {
  console.log('client connected');

  socket.on('error', console.log);

  socket.on('data', function(d) {
    console.log('client says: ', d);
    socket.end("I'm a server! Goodbye.");
  });
}

function setupServer() {
  var net = require("net");
  var server = net.createServer(handleClient);
  server.listen(1234);
}

function connectWiFi() {
  var wifi = require("Wifi");

  wifi.connect("***WIFI*SSID***", {password:"***WIFI*PASSWORD***"}, function(err) {
    console.log("connected? err = ", err, " info = ", wifi.getIP());

    setupServer();
  });
}

function onInit() {
  connectWiFi();
}

save();

Setup the client

The client, after get ready with the WiFi connection, will try to connect to the server. When the connection is established it will send a “Hello I’m a client!” message and will wait for responses from the server.

function talkToServer(socket) {
  console.log('connected to the server');

  socket.on('error', console.log);

  socket.on('data', function(d) {
    console.log('server says: ', d);
  });

  socket.on('end', function() {
    console.log('disconnected from the server');
  });

  socket.write('Hello, I\'m a client!');
}

function connectToServer() {
  var net = require("net");
  net.connect({host: "192.168.1.105", port: 1234}, talkToServer);
}

function connectWiFi() {
  var wifi = require("Wifi");

  wifi.connect("***WIFI*SSID***", {password:"***WIFI*PASSWORD***"}, function(err) {
    console.log("connected? err = ", err, " info = ", wifi.getIP());

    connectToServer();
  });
}

function onInit() {
  connectWiFi();
}

save();

The output

Here the output in the server side:

>
 _____                 _
|   __|___ ___ ___ _ _|_|___ ___
|   __|_ -| . |  _| | | |   | . |
|_____|___|  _|_| |___|_|_|_|___|
          |_| http://espruino.com
 1v93 Copyright 2016 G.Williams
Espruino is Open Source. Our work is supported
only by sales of official boards and donations:
http://espruino.com/Donate
Flash map 4MB:512/512, manuf 0xe0 chip 0x4016
>Erasing Flash..................
Writing.....
Compressed 25600 bytes to 2137
Checking...
DRunning onInit()...
connected? err =  null  info =  {
  "ip": "192.168.1.105",
  "netmask": "255.255.255.0",
  "gw": "192.168.1.1",
  "mac": "5c:cf:7f:0f:7a:2b"
 }
client connected
client says:  Hello, I'm a client!
>

Here the output for the client side:
>
 _____                 _
|   __|___ ___ ___ _ _|_|___ ___
|   __|_ -| . |  _| | | |   | . |
|_____|___|  _|_| |___|_|_|_|___|
          |_| http://espruino.com
 1v93 Copyright 2016 G.Williams
Espruino is Open Source. Our work is supported
only by sales of official boards and donations:
http://espruino.com/Donate
Flash map 4MB:512/512, manuf 0xe0 chip 0x4016
>
=undefined
Erasing Flash..................
Writing.....
Compressed 25600 bytes to 2352
Checking...
DRunning onInit()...
connected? err =  null  info =  {
  "ip": "192.168.1.104",
  "netmask": "255.255.255.0",
  "gw": "192.168.1.1",
  "mac": "5c:cf:7f:f8:53:5a"
 }
connected to the server
server says:  I'm a server! Goodbye.
disconnected from the server
>