A closer look at sockets and TCP/IP

Nov 2019

Socket programming is easy. Languages like Python provides robust libraries to create and manage sockets and socket communications. However, do you know the packet-level details of the socket library functions/commands that you execute? If you are curious about it, you came to the right place.

The best way to learn is to experiment. So in this article, we will set up a sender, listener and Wireshark to experiment and learn.

The setup

Here is our experiment setup:

Under this simple setup, we will execute commands of our interest and analyse the network traffic. Let's not waste time going through TCP/IP and directly dive into the details. (If you need to brush up your memory on TCP/IP, there are tons of resources available online)

Experiments

We will use two simple python scripts as our sender(client) [source code] and receiver [source code]. In the given script, the receiver is hard-coded to listen to port 10000. We shall execute both the receiver and the sender from the same computer for simplicity. We will analyze the communication between sender and receiver using Wireshark. In Wireshark, the packets sent by the receiver will have their source port 10000. The packets sent by the sender will have its destination port 10000.

To start the experiment, let's start Wireshark and use a filter: port 10000 to filter out traffic between our sender and receiver. Now, let's try executing the sender script [step-1]. You can see that the script fails at socket.connect() with an error: ConnectionRefusedError: [Errno 111] Connection refused. To understand this error, let's see what connect() function does. It chooses and binds to an available port in the system and sends out a SYN packet to the receiver as a request to start the communication. But we did not start the receiver yet. Hence the target system (the system where you expect the receiver to be running) rejects the communication-request and sends an RST packet back. Thus, connect()'s request to connect was rejected(refused) by the target. You can see both the packets of this step in Wireshark:

The first row here represents the SYN packet sent by the receiver and the second row represents the RST packet sent by the target system.

Now let's start the receiver script. Three important commands we encounter here are socket.bind(), socket.listen() and socket.accept(). Let's understand each of these functions:

  • bind(): Specifies the Network Interface and Port the socket object should use.
  • listen(): Puts the socket to server mode and listens to connection requests on the interface-port specified by bind().
  • accept(): Accept data.

Let's understand this better. For this, we will execute the script in debug mode so that we can see what is happening for each of these commands.

First, let's put the breakpoint right after socket.bind() and execute the receiver script [step-2]. Once the script pauses at the breakpoint, execute the sender. Notice that you are still getting the error: ConnectionRefusedError: [Errno 111] Connection refused. This is because bind() only makes the association with the given port. The port is not "open" yet. Thus, if you look at Wireshark you will find SYN and RST packets belonging to our step-2, similar to step-1.

One thing to note here is that, though the receiver isn't utilising the port now, as far as the operating system is concerned, the port is in use. No other process can bind to this port until our receiver frees it. To see this, simply execute the receiver script from a different terminal to get OSError: [Errno 98] Address already in use.

Now, let's continue executing our receiver with a breakpoint right after socket.listen(). Once the receiver pause after socket.listen(), execute the sender. You can now see the exchange of 3 packets (SYN, SYN-ACK and ACK) corresponding to the 3-way handshake. This means that the sender made a successful connection with the receiver.

But how?
The script was in suspended mode! So, who did the 3-way handshake?

By now, you must have guessed about socket.accept(). Yes, the accept function waits and accepts data sent by the client.

I hope the article sets you up to get started with further experiments and deep dive into the topic. There are so many things you should try out with this simple experimental setup to learn more.


The end
Other Articles