February 14, 2012

Logging TCP state variables such as Congestion Window from user space

If you are familiar with hijacking library calls using LD_PRELOAD then this should be very intuitive. The following code should help you monitor some of the tcp state variables. I am using the following code to log the evolution of the TCP congestion window during an scp session. Please have a look at tcp_info structure available at /usr/include/linux/tcp.h and the tcp_info structure in the kernel tree. I needed only the variable related to the congestion window and timers for my work. You can modify the periodic_alarm function to suit your need. This code works for scp, for other applications you might need to modify the socket descriptor used in the periodic alarm file. Happy Hacking!
 
#define _GNU_SOURCE  
#include <sys/syscall.h>  
#include <sys/types.h>  
#include <sys/socket.h>  
#include <stdio.h>  
#include <stdint.h>  
#include <dlfcn.h>  
#include <signal.h>  
#include <netinet/tcp.h>  
#include <sys/syscall.h>  

/*
 * THIS WORKS FOR SCP WHERE ONLY ONE CONNECTION IS USED
 */ 

int (*real_socket)(int domain, int type, int protocol) = NULL; 
int sockfd = 0;
struct sigaction sact;
long long start_time;

/*
 * Periodically dump the info from the socket.
 * Note this assumes that the tcp_info struct includes tcpi_total_retrans. 
 * Older versions do not support tcpi_total_retrans
 */
   
void periodic_alarm( int sig )
{
  
  struct tcp_info info;
  int infoLen = sizeof(info);
  struct timeval now;
  long long curr_time;
  
  gettimeofday(&now, NULL);
  curr_time = (now.tv_sec*1000)+(now.tv_usec/1000);  

  getsockopt(sockfd, SOL_TCP, TCP_INFO, (void *)&info, (socklen_t *)&infoLen);
  
  fprintf(stderr,"%llu %llu %u %u %u %u %u %u %u %u %u %u\n", curr_time,
   curr_time - start_time, info.tcpi_snd_cwnd, info.tcpi_snd_ssthresh,
   info.tcpi_unacked, info.tcpi_lost, info.tcpi_retrans, info.tcpi_total_retrans,
   info.tcpi_rtt, info.tcpi_rttvar, info.tcpi_rcv_rtt, info.tcpi_rto);  
}

int socket(int domain, int type, int protocol)
{
  int fd;
  struct timeval now;
  
  if (NULL == real_socket)
    {
      real_socket = dlsym(RTLD_NEXT, "socket");
    }
  fd = real_socket(domain, type, protocol);  
  /*
   * For scp the first socket is enough
   */
  if (SOCK_STREAM == type&SOCK_STREAM && 0 == sockfd)
    {
      gettimeofday(&now, NULL);
      start_time = (now.tv_sec*1000)+(now.tv_usec/1000); 
      // if (sact.sa_handler != periodic_alarm)
      sigemptyset(&sact.sa_mask);
      sact.sa_flags = 0;
      sact.sa_handler = periodic_alarm;
      sigaction(SIGALRM, &sact, NULL);
      ualarm(999000,10000);// Wait for 1 second then every 10 ms      
      sockfd = fd;
    }  
  return fd;
}

/*
Compile with the following command
gcc -shared -ldl -fPIC wrap_socket.c -o wrap_socket.so
Run scp as follows 
LD_PRELOAD=./wrap_socket.so scp fname username@machine:path 2>logs-1
*/