
/* tcpmon.c */
/* Andrew Davison (ad@ratree.psu.ac.th), May 1998  */

/* Monitors TCP packets under Linux using the sock_packet
   socket interface and by setting the device (NETDEV) into
   promiscuous mode.
*/

/* Usage:
     ./tcpmon [-p portno] | more
   e.g.
     ./tcpmon -p 80 | more
*/

#include <stdio.h>
#include <ctype.h>
#include <string.h>
#include <stdlib.h>
#include <unistd.h>
#include <signal.h>
#include <sys/ioctl.h>
#include <netdb.h> 
#include <netinet/in.h>
#include <arpa/inet.h> 
#include <net/if.h>
#include <netinet/ip.h>

/* On Red Hat Linux release 5.0 (Hurricane); Kernel 2.0.32 */
#include <netinet/if_ether.h> 
#include <netinet/tcp.h>

/* On Debian Linux release 1.1; Kernel 2.0.24 */
/*
#include <linux/if_ether.h>
#include <linux/tcp.h>
*/

#define NETDEV "eth0"    /* network device; obtained using netstat -r */
#define BUFLEN 1600      /* max length of packet data */
#define ADDRLEN 100      /* max length of an address */
#define COLWIDTH 75      /* width of output for packet data */


typedef struct {
  struct ethhdr eth;
  struct iphdr  ip;
  struct tcphdr tcp;
  char data[BUFLEN];
} etherpkt;


int get_port(int argc, char *argv[]);
int open_device(char *device);
void print_packet(etherpkt ep, int port);
void host_lookup(unsigned long int, char haddr[]);
int matches_port(int sp, int dp, int port);
void print_data(int datalen, char *data);
void clean_up(int sig);


/* global */
int ethsd;                 /* socket bound to the network device */
struct ifreq old_ifr;      /* old interface flags (for later restoration) */


int main(int argc, char *argv[])
{
  etherpkt ep;
  struct sockaddr_in client;
  int port, client_len;

  /* Setup signal handlers */
  signal(SIGHUP, SIG_IGN);
  signal(SIGINT, clean_up);
  signal(SIGTERM, clean_up);
  signal(SIGKILL, clean_up);
  signal(SIGQUIT, clean_up);

  port = get_port(argc, argv);

  ethsd = open_device(NETDEV); 

  while(1) {
    client_len = sizeof(client);
    if (recvfrom(ethsd, (etherpkt *)&ep, sizeof(etherpkt), 0,
                    (struct sockaddr *)&client, &client_len) == -1) {
      perror("Packet receive error");
      clean_up(-1);
    }
    if (ep.eth.h_proto == ntohs(ETH_P_IP))    /* if an IP packet */
      print_packet(ep, port);
  }
  return 0;          /* control never reaches here */
}


int get_port(int argc, char *argv[])
/* Return the port number, or -1 if no number was supplied. */
{
  int port = -1;

  if ((argc != 1) && (argc != 3)) {
    fprintf(stderr, "Usage tcpmon [-p <port>]\n");
    exit(1);
  }
  if (argc == 3) {
    port = atoi(argv[2]);
    fprintf(stderr, "Monitoring port %d\n", port);
  }
  return port;
}


int open_device(char *device)
/* Return a socket descriptor for a socket bound to device
   and able to see every packet.
   Back up the original interface flags in old_ifr.
*/
{
  int sd;
  struct sockaddr sa;
  struct ifreq ifr; 

  /* Create a socket of the kind that can see every packet */
  if ((sd = socket(PF_INET, SOCK_PACKET, htons(ETH_P_ALL))) < 0) {
    perror("Cannot create an ETH_P_ALL socket");
    exit(1);
  }

  /* Build device socket address */
  memset(&sa, 0, sizeof(sa));
  sa.sa_family = AF_INET;
  strncpy(sa.sa_data, device, sizeof(sa.sa_data));

  /* Bind address to socket */
  if (bind(sd, &sa, sizeof(sa)) < 0) {
    perror("Cannot bind socket and address");
    exit(1);
  }

  memset(&ifr, 0, sizeof(ifr)); 
  strcpy(ifr.ifr_name, device);
  if( ioctl(sd, SIOCGIFFLAGS, &ifr) < 0 ) {    /* get flags */
    close(sd);
    perror("Cannot get interface flags");
    exit(2);
  }
  old_ifr = ifr;                        /* backup present flags */
  
  ifr.ifr_flags |= IFF_PROMISC;         /* set promiscuous mode */

  if( ioctl(sd, SIOCSIFFLAGS, &ifr) < 0 ) {    /* set flags */
    close(sd);
    perror("Cannot set interface flags");
    exit(2);
  }
  else
    fprintf(stderr, "Promiscuous mode switched on\n");

  return sd;
}


void print_packet(etherpkt ep, int port)
/* Print the packet in ep, but only if it is a TCP packet
   and port matches one of the port numbers in the packet.
*/
{
  int sport, dport;
  char saddr[ADDRLEN], daddr[ADDRLEN];
  struct iphdr *ip;
  struct tcphdr *tcp;

  /* Use ip and tcp to refer to the IP and TCP headers, and
     correct for the 2-byte hole in etherpkt after the ethhdr field
  */
  ip = (struct iphdr *)(((unsigned long)&ep.ip)-2);
  tcp = (struct tcphdr *)(((unsigned long)&ep.tcp)-2);

  if(ip->protocol != IPPROTO_TCP)     /* not a TCP packet? */
    return;

  sport = ntohs(tcp->source);         /* get the port numbers */
  dport = ntohs(tcp->dest);

  if (matches_port(sport, dport, port)) {
    host_lookup(ip->saddr, saddr);    /* get the addresses */
    host_lookup(ip->daddr, daddr);
    printf("---------------------------------------\n");
    printf("%s [%d] -> %s [%d]\n", saddr, sport, daddr, dport);
    putchar('\t');
    print_data( htons(ip->tot_len)-sizeof(ep.ip)-sizeof(ep.tcp),
                   ep.data-2);
  }
}


void host_lookup(unsigned long int addr, char hname[])          
/* Convert the dotted decimal address in addr to a name. 
   First try to get the host name, otherwise settle for
   the string version of the dotted decimal.
*/
{
  struct in_addr iaddr;
  struct hostent *he;

  iaddr.s_addr = addr; 
  if ((he = gethostbyaddr((char *)&iaddr,
                 sizeof(struct in_addr), AF_INET)) == NULL)
    strcpy(hname, inet_ntoa(iaddr));          
  else
    strcpy(hname, he->h_name); 
}



int matches_port(int sp, int dp, int port)
/* Does port match either sp or dp? */
{
  if (port == -1)          /* port was not set */
    return 1;
  if ((sp == port) || (dp == port))
    return 1;
  return 0;
}


void print_data(int datalen, char *data)
/* Print the packet data in a COLWIDTH wide column.
   Only print printable characters, '.' otherwise.
   Replace a CR or LF by a newline.
   Start every line with a tab to emulate the output
   from tcpdump/tcpshow
*/
{
  int i;
  int width = 0;         /* width of output */
  
  for(i=0; i < datalen; i++) {
    if ((data[i] == 13) || (data[i] == 10)) {   /* CR or LF */
      printf("\n\t"); 
      width = 5; 
    }

    if(isprint(data[i]))
      printf("%c", data[i]);
    else
      printf(".");
    width++;

    if(width > COLWIDTH) {
      printf("\n\t");
      width = 5;
    }
  }
  printf("\n");
  fflush(NULL);
}


void clean_up(int sig)
/* Reset the device interface to its original value, and
   close the socket.
*/
{
  fprintf(stderr, "Restoring old interface\n");
  if (ioctl(ethsd, SIOCSIFFLAGS, &old_ifr) < 0 )
    perror("Cannot restore old interface");

  fprintf(stderr, "Exiting tcpmon...\n");
  close(ethsd);
  exit(0);
}


