/* pppmon/src/pppmon.c
 *
 * Description:
 * Get stats from ppp layer and send it to connected clients.
 *
 * This software is in the public domain. Thanx to Al Longyear and
 * Brad Parker for initial ideas in the pppstats.c program.
 *
 * Please send all bug reports, requests for information, etc. to:
 *   Calle Karlsson
 *   KaSH Software AB
 *   calle@kash.se
 *
 * History:
 * 950525 ckn Initial coding - version 0.1
 * EndHistory.
 */

#include "pppmon.h"
#include <stdlib.h>
#include "/usr/src/linux/drivers/net/slhc.h"

#define MAXCLIENTS         64

typedef struct
{
  struct ppp_stats  stats;
  struct slcompress slcomp; /* Not used */
} DclPPPInfo;

static int SetupSocket (char * Service, char * Protocol);
static long MilliTime (void);
static void MilliSleep (int MilliSecs);
static int AddClient (void);
static void RemoveClient (int fd);
static void SendToAllClients (DclPPPMonitorMsg * PMMP);
static void SendToClientsAndWait (int RBytes, int SBytes, int TimeSpent,
                                  int Interval);

static int ListenSocket = -1;

static int Clients [MAXCLIENTS + 1];


int
main (int argc, char ** argv)
{
   int OldMask;
   DclPPPInfo PPPInfo [1];
   struct ifreq ifreq;
   int fd, unit = 0, Interval = 5000, Fake = 0;
   long InfoTime, LastInfoTime = -1, UsedTime, RBytes, SBytes, TBytes;
   struct ppp_stats * PPPStat, LastPPPStat [1];

   for (argc --, argv ++; argc; argc --, argv ++)
   {
      if (* argv [0] != '-')
      {
         printf ("Usage: pppcheck [-i <interval (ms)>]\n");
         exit (1);
      }

      switch (toupper (argv [0] [1]))
      {
       case 'I':
         if (argc < 1)
         {
            printf ("Missing interval argument\n");
            exit (1);
         }
         Interval = atoi (argv [1]);
         argc --, argv ++;
         break;

       case 'F':   /* Undocumented Feature :) */
         Fake = 1;
         break;
         
       case 'H':
       default:
         printf ("Usage: pppcheck [-i <interval>]\n");
         exit (1);
         break;
      }
   }

   signal (SIGPIPE, SIG_IGN);
   
   if ((ListenSocket = SetupSocket ("pppmon", "tcp")) < 0)
   {
      printf ("Couldn't setup socket\n");
      exit (1);
   }

   memset (Clients, 0, sizeof (Clients));
   
   /* Dunno what unit is, dumbly snarfed from pppstats.c */
   memset (& ifreq, 0, sizeof (ifreq));
   sprintf (ifreq.ifr_ifrn.ifrn_name, "ppp%d", unit);

   /* Setup buffer for the SIOCDEVPRIVATE ioctl */
   ifreq.ifr_ifru.ifru_data = (caddr_t) PPPInfo;
   PPPStat = & PPPInfo->stats;
   memset (LastPPPStat, 0, sizeof (LastPPPStat));

   while (1)
   {
      if (Fake)
      {
         /* Well well... it gives values, although not good ones... */
         PPPStat->rbytes = random () / (float) RAND_MAX * 1.8 * Interval;
         PPPStat->sbytes = random () / (float) RAND_MAX * 1.8 * Interval;
      }
      else
         if (ioctl (ListenSocket, SIOCDEVPRIVATE, (caddr_t) & ifreq) < 0)
         {
            perror ("ioctl(SIOCDEVPRIVATE)");
            exit (1);
         }

      InfoTime = MilliTime ();
      
      if (LastInfoTime != -1)
      {
         RBytes = PPPStat->rbytes - LastPPPStat->rbytes;
         SBytes = PPPStat->sbytes - LastPPPStat->sbytes;
         UsedTime = InfoTime - LastInfoTime;

         SendToClientsAndWait (RBytes, SBytes, UsedTime, Interval);
      }
      else
         MilliSleep (Interval / 2);    /* No valid data first time around */

      memcpy (LastPPPStat, PPPStat, sizeof (struct ppp_stats));
      LastInfoTime = InfoTime;
   }

   return 0;
}


static int
SetupSocket (char * Service, char * Protocol)
{
   struct servent * sp;
   struct sockaddr_in Addr [1];
   int Socket, Port;
   
   if (! (sp = getservbyname (Service, Protocol)))
   {
      perror ("getservbyname");
      Port = 5676; /* Or any unused port.. */
   }
   else
      Port = sp->s_port;

   if ((Socket = socket (AF_INET, SOCK_STREAM, 0)) < 0)
   {
      perror ("socket");
      return -1;
   }

   memset (& Addr->sin_addr, 0, sizeof (Addr->sin_addr));
   Addr->sin_port   = ntohs (Port);
   Addr->sin_family = AF_INET;
   
   if (bind (Socket, (struct sockaddr *) Addr, sizeof (Addr)))
   {
      perror ("bind");
      return -1;
   }

   if (listen (Socket, 5) < 0)
   {
      perror ("listen");
      return -1;
   }

   return Socket;
}


/*
 * Returns milliseconds since midnight. There's one flaw with the use
 * of this function, which is left as an excersice for the interested
 * reader. ;)
 */

static long
MilliTime (void)
{
   struct timeval tv [1];

   if (gettimeofday (tv, NULL) < 0)
   {
      perror ("gettimeofday");
      exit (1);
   }

   return (tv->tv_sec % 86400) * 1000 + tv->tv_usec / 1000;
}

/*
 * Sleep a few milliseconds
 */

static void
MilliSleep (int MilliSecs)
{
   struct timeval tv [1];

   tv->tv_sec  = MilliSecs / 1000;
   tv->tv_usec = (MilliSecs % 1000) * 1000;

   select (0, NULL, NULL, NULL, tv);
}


/*
 * Incoming client that wants some data from us. Just accept the session.
 */

static int
AddClient (void)
{
   int fd;
   
   if ((fd = accept (ListenSocket, NULL, 0)) < 0)
   {
      perror ("accept");
      return -1;
   }

   if (fd > MAXCLIENTS)
   {
      printf ("Too many clients connecting (%d)\n", MAXCLIENTS);
      return -1;
   }
   
   Clients [fd] = 1;

   printf ("Accepted client at descriptor %d\n", fd);
   
   return fd;
}
   

/*
 * A client got tired of us
 */

static void
RemoveClient (int fd)
{
   close (fd);
   Clients [fd] = 0;
   printf ("Client %d removed\n", fd);
}
   

/*
 * Send statistics to currently connected clients
 */

static void
SendToAllClients (DclPPPMonitorMsg * PMMP)
{
   int fd;

   for (fd = 0; fd < MAXCLIENTS; fd ++)
   {
      if (Clients [fd])
      {
         int rc = write (fd, PMMP, sizeof (DclPPPMonitorMsg));

         /* Probably disconnected. */
         if (rc != sizeof (DclPPPMonitorMsg))
            RemoveClient (fd);
      }
   }
}


/*
 * Send statistics, wait specified amount of time, and handle incoming
 * connections.
 */

static void
SendToClientsAndWait (int RBytes, int SBytes, int TimeSpent, int Interval)
{
   struct timeval tv [1];
   DclPPPMonitorMsg PPPMonitorMsg [1];
   fd_set ReadSet [1];
   int rc;
   unsigned long UpTime;
   FILE * fp;

   if (! (fp = fopen ("/etc/ppp/UP", "r")))
   {
      /* We're not connected. Chill out... */
      UpTime = 0;
      Interval *= 3;
   }
   else
   {
      /* We're connected. */
      fscanf (fp, "%d", & UpTime);
      fclose (fp);
      UpTime = time (0) - UpTime;
   }
   
   PPPMonitorMsg->UpTime    = htonl (UpTime);
   PPPMonitorMsg->RBytes    = htonl (RBytes);
   PPPMonitorMsg->SBytes    = htonl (SBytes);
   PPPMonitorMsg->TimeSpent = htonl (TimeSpent);
   PPPMonitorMsg->RTT       = htonl (0); /* For future use */
   
   SendToAllClients (PPPMonitorMsg);

   tv->tv_sec  = Interval / 1000;
   tv->tv_usec = (Interval % 1000) * 1000;

   while (1)
   {
      FD_ZERO (ReadSet);
      FD_SET (ListenSocket, ReadSet);

/*       printf ("Into select, Time %d.%06d\n", tv->tv_sec, tv->tv_usec); */
      
      /*
       * Sleep the interval. A client might connect to us; handle that
       * as well.
       */
      
      rc = select (ListenSocket+1, ReadSet, NULL, NULL, tv);
      if (rc == -1 && errno == EINTR)
         continue;

      else if (rc == 0) /* Timeout */
         break;
      
      else if (FD_ISSET (ListenSocket, ReadSet))
         AddClient ();
   }
}
