Finding the state of TCP/IP Sockets on Linux

Finding the state of TCP/IP Sockets on Linux

It is not possible through the sockets API to find the connection state of a socket. But on Linux there is a way, a user space way, to find the connection state of a socket.

Network information in the Proc File System

If you read the file /proc/net/tcp, you will find something like this:

  sl  local_address rem_address   st tx_queue rx_queue tr tm->when retrnsmt   uid  timeout inode
  1: D3F35D48:0050 36CB79C8:D94F 06 00000000:00000000 03:00000B71 00000000     0        0 0 3 ffff8809d09b81c0
  2: D3F35D48:0050 F8C5E6C9:6C14 01 00000000:00000000 00:00000000 00000000     0        0 265916098 1 ffff880ed4353000 66 4 7 12 -1

Each line represents a socket which is currently being managed by the operating system. This will include sockets that are closed and waiting for cleanup.

Each line is the output of a kernel level function called tcp_get_info().

The first column is that entry’s position in the table, and doesn’t mean anything useful. The other columns are documented in detail here, but briefly the interesting columns are:

local_address: The local IP and port number of the socket. This will normally be one of the IPs of the local server.

remote_address: The IP and port number of the server connected on the remote end of the socket.

st: The connection state. This is number corresponds to an enumeration in tcp_states.h:

enum {
  TCP_ESTABLISHED = 1,
  TCP_SYN_SENT,
  TCP_SYN_RECV,
  TCP_FIN_WAIT1,
  TCP_FIN_WAIT2,
  TCP_TIME_WAIT,
  TCP_CLOSE,
  TCP_CLOSE_WAIT,
  TCP_LAST_ACK,
  TCP_LISTEN,
  TCP_CLOSING,	/* Now a valid state */
  TCP_NEW_SYN_RECV,

  TCP_MAX_STATES	/* Leave at the end! */
};

inode: The last aligned column contains a string with a bunch of different fields. It looks something like this:

265916098 1 ffff880ed4353000 66 4 7 12 -1

The first field is the inode of the socket. The third field is the actual memory address of the socket structure. We will use the inode number to find which socket in a program corresponds to each line in this file and then read out the current socket state.

Matching sockets with lines in /proc/net/tcp

When you create a socket, with the socket() or the accept() functions, an integer is returned. This represent the currently open socket.

Internally this number maps to an inode in the linux virtual file system. All objects in the a file system, such as individual normal files will have an inode. But the kernel can create inodes for other things that you can read from and write to – including such things as pipes, devices and sockets.

Associated with any open file is a file descriptor.

The inode is a system-wide number describing an existing file. A file descriptor is a number identifying a currently open file in a given process.

To find the socket state we can match up the inode number of our socket with the inode value in the inode column.

To do this first we open the file, parse it and create a mapping with inode numbers as keys.

class TcpDictEntry
{
public:
   TcpDictEntry() { mState = 1000; }
   TcpDictEntry( const char* line )
   {
      const char* tok = getToken( line, 4 );
      mState = strtol( tok, NULL, 16 );
   }

   u32 mState;
};

class TcpDict : public KxDict<u32,TcpDictEntry>
{
public:
   void reset()
   {
      clear();

      Buffer buffer;
      if ( !readFile( "/proc/net/tcp", buffer )) return;

      const char* line = buffer.getToken( "\n" );
      while (( line = buffer.getToken( "\n" )))
      {
         const char* tok = getToken( line, 10 );
         u32 id = strtol( tok, NULL, 10 );
         insert( id, TcpDictEntry( line ));
      }
   }
};

This is from a larger project. KxDict is a dictionary class. It maps from keys to values. In this case we have it set up to return the contents of the state field when given the inode.

getToken() will return the value of the N’th field in a tab separated string. State is parsed from the 4th field. Inode is parsed from the 10th field. If you wanted other fields you could easily extend TcpDictEntry.

This class in its reset method will read in and parse the current contents of the /proc/net/tcp file. By the time the file is read and parsed a socket may have easily changed state.

With this dictionary you can just look up the socket state and print it:

TcpDict dict;
Socket s = accept();
TcpDictEntry e = dict.valueFromKey( s );
               
switch ( e.mState )
{
   case  0:  os << "**invalid**";              break;
   case  1:  os << "ESTABLISHED";              break;
   case  2:  os << "SYN_SENT";                 break;
   case  3:  os << "SYN_RECV";                 break;
   case  4:  os << "FIN_WAIT1";                break;
   case  5:  os << "FIN_WAIT2";                break;
   case  6:  os << "TIME_WAIT";                break;
   case  7:  os << "CLOSE";                    break;
   case  8:  os << "CLOSE_WAIT";               break;
   case  9:  os << "LAST_ACK";                 break;
   case 10:  os << "LISTEN";                   break; 
   case 11:  os << "CLOSING";                  break;
   default:  os << e.mState;                   break;
}

This will report the state of the socket at whatever time the tcp file was read. So you can’t use this to get the exact current state of each socket. This is only good for reporting, so you know more or less what is going on with each socket.

Resources:

List of possible internal socket statuses from /proc A StackOverflow answer that shows how to map from socket states numbers to tcp state enumeration.
Exploring the /proc/net/ Directory A detailed explanation of the columns in /proc/net/tcp
Unix file types Lists out the various types of files in Linux, that is all the things that can be represented by an inode.
tcp_states.h A header file that contains the actual TCP_STATE enumeration.