Logo Search packages:      
Sourcecode: laptop-net version File versions  Download package

ifmon.c

/* -*-C-*-

$Id: ifmon.c,v 1.2 2005/03/01 21:49:00 cph Exp $

Copyright 2000,2001,2002,2003,2005 Massachusetts Institute of Technology

This file is part of autonet.

Autonet is free software; you can redistribute it and/or modify it
under the terms of the GNU General Public License as published by the
Free Software Foundation; either version 2 of the License, or (at your
option) any later version.

Autonet is distributed in the hope that it will be useful, but WITHOUT
ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
for more details.

You should have received a copy of the GNU General Public License
along with autonet; if not, write to the Free Software Foundation,
Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.

*/

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <ctype.h>
#include <errno.h>
#include <syslog.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <sys/ioctl.h>
#include <net/if.h>

#include "ifmon.h"
#include "probe.h"
#include "utils.h"

typedef struct
{
  const char * name;
  unsigned int
    status : 5,
    mark : 1;
  double change_time;
} record_t;

int ifmon_use_old_mii_p = 0;
double ifmon_delta_change_time = 2.0;

static int kernel_connection;

/* Forward references */
static void process_record (record_t *, unsigned int, ifmon_proc_t *, void *);
static void initialize_records (void);
static void clear_record_marks (void);
static record_t * intern_record (const char *);
static void process_unmarked_records (ifmon_proc_t *, void *);
static void finalize_records (void);
static struct ifreq * get_interface_list (unsigned int *);
static struct ifreq * conf_interface_list (unsigned int *);
static struct ifreq * proc_interface_list (unsigned int *);
static void discard_line (FILE *);
static int proc_interface_name (FILE *, struct ifreq *);
static unsigned int get_link_status (const char *, int, int, unsigned int *);
static int read_if_flags (const char *, int *);
static int write_if_flags (const char *, int);

int
run_ifmon (ifmon_loop_t * loop_proc,
         ifmon_proc_t * proc,
         ifmon_managed_t * managed_p,
         void * context)
{
  int retval;

  /* Open a socket so we can talk to the kernel.  */
  kernel_connection = (socket (AF_INET, SOCK_DGRAM, 0));
  if (kernel_connection < 0)
    {
      log_perror ("socket");
      exit (1);
    }
  initialize_records ();

  /* The main loop.  Scan the set of interfaces, calling
     process_record on each one (except loopbacks).  Then delay by the
     polling interval and repeat. */
  while (1)
    {
      unsigned int n_ifcs;
      struct ifreq * ifcs;

      clear_record_marks ();
      ifcs = (get_interface_list (&n_ifcs));
      if (ifcs != 0)
      {
        struct ifreq * scan = ifcs;
        struct ifreq * end = (ifcs + n_ifcs);
        while (scan < end)
          {
            const char * if_name = (scan -> ifr_name);
            int if_flags;
            unsigned int status;

            if (((read_if_flags (if_name, (&if_flags))) == 0)
              && ((if_flags & IFF_LOOPBACK) == 0)
              && ((get_link_status (if_name,
                              if_flags,
                              ((*managed_p) (context, if_name)),
                              (&status)))
                  == 0))
            process_record
              ((intern_record (if_name)), status, proc, context);
            scan += 1;
          }
        free (ifcs);
      }
      process_unmarked_records (proc, context);
      retval = (loop_proc (context));
      if (retval != 0)
      break;
    }
  finalize_records ();
  close (kernel_connection);
  return (retval);
}

static void
process_record (record_t * record, unsigned int status,
            ifmon_proc_t * proc, void * context)
{
  unsigned int old_status = (record -> status);
  double old_change_time = (record -> change_time);
  double now = (get_mono_time ());
  (record -> mark) = 1;
#ifdef IFMON_DEBUG
  syslog (LOG_INFO, "process_record: %s 0x%02x 0x%02x\n",
        (record -> name), old_status, status);
#endif
  if ((status != old_status)
      && ((now - old_change_time) >= ifmon_delta_change_time))
    {
      (record -> status) = status;
      (record -> change_time) = now;
      (*proc) (context, (record -> name), old_status, status);
    }
}

static unsigned int records_length;
static unsigned int n_records;
static record_t * records;

static void
initialize_records (void)
{
  n_records = 0;
  records_length = 16;
  records = (xmalloc ((sizeof (record_t)) * records_length));
}

static void
clear_record_marks (void)
{
  record_t * scan = records;
  record_t * end = (scan + n_records);
  while (scan < end)
    ((scan++) -> mark) = 0;
}

static record_t *
intern_record (const char * name)
{
  record_t * scan = records;
  record_t * end = (scan + n_records);
  while (scan < end)
    {
      if ((strcmp ((scan -> name), name)) == 0)
      return (scan);
      scan += 1;
    }
  if (n_records == records_length)
    {
      records_length *= 2;
      records = (xrealloc (records, ((sizeof (record_t)) * records_length)));
    }
  scan = (records + n_records);
  (scan -> name) = (copy_string (name));
  (scan -> status) = IFMON_STATUS_INVALID;
  (scan -> mark) = 1;
  (scan -> change_time) = 0.0;
  n_records += 1;
  return (scan);
}

static void
process_unmarked_records (ifmon_proc_t * proc, void * context)
{
  record_t * scan = records;
  record_t * end = (scan + n_records);
  while (scan < end)
    {
      if ((scan -> mark) == 0)
      process_record (scan, IFMON_STATUS_INVALID, proc, context);
      scan += 1;
    }
}

static void
finalize_records (void)
{
  record_t * scan = records;
  record_t * end = (scan + n_records);
  while (scan < end)
    free ((void *) ((scan++) -> name));
}

/* Use /proc/net/dev if available, since that will give us a list of
   all of the interfaces.  Otherwise use SIOCGIFCONF, which will at
   least give us the interfaces that are up.  */

static struct ifreq *
get_interface_list (unsigned int * n_ifcs_return)
{
  struct ifreq * ifcs = (proc_interface_list (n_ifcs_return));
  return
    ((ifcs == 0)
     ? (conf_interface_list (n_ifcs_return))
     : ifcs);
}

static struct ifreq *
conf_interface_list (unsigned int * n_ifcs_return)
{
  struct ifconf ifc;
  unsigned int n_ifcs = 4;
  unsigned int n_bytes = ((sizeof (struct ifreq)) * n_ifcs);

  (ifc . ifc_len) = n_bytes;
  (ifc . ifc_req) = (xmalloc (n_bytes));
  while (1)
    {
      if ((ioctl (kernel_connection, SIOCGIFCONF, (&ifc))) < 0)
      {
        log_perror ("SIOCGIFCONF");
        free (ifc . ifc_req);
        return (0);
      }
      if ((ifc . ifc_len) < n_bytes)
      break;
      n_ifcs *= 2;
      n_bytes = ((sizeof (struct ifreq)) * n_ifcs);
      (ifc . ifc_req) = (xrealloc ((ifc . ifc_req), n_bytes));
    }

  (*n_ifcs_return) = ((ifc . ifc_len) / (sizeof (struct ifreq)));
  return (ifc . ifc_req);
}

static struct ifreq *
proc_interface_list (unsigned int * n_ifcs_return)
{
  FILE * s = (fopen ("/proc/net/dev", "r"));
  if (s == 0)
    {
      log_perror ("Unable to open /proc/net/dev");
      return (0);
    }
  discard_line (s);
  discard_line (s);
  {
    unsigned int i = 0;
    unsigned int n = 4;
    struct ifreq * ifcs = (xmalloc ((sizeof (struct ifreq)) * n));
    struct ifreq ifc;
    while (proc_interface_name (s, (&ifc)))
      {
      if (i == n)
        {
          n *= 2;
          ifcs = (xrealloc (ifcs, ((sizeof (struct ifreq)) * n)));
        }
      (ifcs[i++]) = ifc;
      }
    fclose (s);
    (*n_ifcs_return) = i;
    return (ifcs);
  }
}

static void
discard_line (FILE * s)
{
  while (1)
    {
      int c = (getc (s));
      if ((c == EOF) || (c == '\n'))
      break;
    }
}

#define PIN_GETC(c, s)                                      \
{                                                     \
  c = (getc (s));                                     \
  if (c == EOF)                                             \
    return (0);                                             \
  if (c == '\0')                                      \
    {                                                 \
      discard_line (s);                                     \
      goto restart;                                         \
    }                                                 \
}

#define PIN_ACCUM(c)                                        \
{                                                     \
  if (i < n)                                                \
    (name[i++]) = (c);                                      \
  else                                                      \
    {                                                 \
      discard_line (s);                                     \
      goto restart;                                         \
    }                                                 \
}

static int
proc_interface_name (FILE * s, struct ifreq * ifc)
{
  char * name = (ifc -> ifr_name);
  unsigned int n = (sizeof (ifc -> ifr_name));
  unsigned int i;
  int c;

 restart:
  do
    {
      PIN_GETC (c, s);
    }
  while (isspace (c));
  i = 0;
  while (1)
    {
      PIN_ACCUM (c);
      PIN_GETC (c, s);
      if (isspace (c))
      {
        if (c != '\n')
          discard_line (s);
        break;
      }
      if (c == ':')
      {
        if (i < n)
          /* Might be part of an alias; check.  */
          {
            unsigned int saved_i = i;
            while (1)
            {
              PIN_ACCUM (c);
              PIN_GETC (c, s);
              if (!isdigit (c))
                {
                  if (c != ':')
                  /* Not an alias; first colon was name terminator.  */
                  i = saved_i;
                  if (c != '\n')
                  discard_line (s);
                  break;
                }
            }
          }
        break;
      }
    }
  if (i < n)
    (name[i]) = '\0';
  return (1);
}

static unsigned int
get_link_status (const char * if_name, int if_flags, int managed_p,
             unsigned int * result_r)
{
  int conn_p = (-1);
  unsigned int retval;

  /* As of 2.4.20, interfaces must be up in order to be interrogated.
     Leave the interface up, because some interfaces (sis900) don't
     detect link beat correctly if we bring them up quickly and then
     bring them down.  */
  if ((if_flags & IFF_UP) == 0)
    {
      struct ifreq ifr;

      if (!managed_p)
      {
        retval = ENODEV;
        goto done;
      }

      /* Delete the interface address.  Otherwise when we set IFF_UP
       the interface will become active again.  */
      strncpy ((ifr . ifr_name), if_name, IFNAMSIZ);
      memset ((& (ifr . ifr_addr)), 0, (sizeof (struct sockaddr)));
      (ifr . ifr_addr . sa_family) = AF_INET;
      if ((ioctl (kernel_connection, SIOCSIFADDR, (&ifr))) < 0)
      log_perror ("SIOCSIFADDR");

      /* Bring the interface up now.  */
      write_if_flags (if_name, (if_flags | IFF_UP));
    }

  retval = (probe_link_ethtool (kernel_connection, if_name, (&conn_p)));
  if (retval == EOPNOTSUPP)
    retval = (probe_link_mii (kernel_connection, if_name, (&conn_p)));

  /* We must know in advance what network driver we are using in order
     to use this safely. */
  if ((retval == EOPNOTSUPP) && managed_p && ifmon_use_old_mii_p)
    retval = (probe_link_old_mii (kernel_connection, if_name, (&conn_p)));

 done:
  switch (retval)
    {
    case 0:
      (*result_r)
      = (IFMON_STATUS_VALID
         | IFMON_STATUS_AVAILABLE
         | IFMON_STATUS_UP
         | ((conn_p > 0) ? IFMON_STATUS_CONNECTED : 0)
         | ((conn_p >= 0) ? IFMON_STATUS_CONNVALID : 0));
      break;
    case ENODEV:
      (*result_r) = IFMON_STATUS_VALID;
      break;
    case EINVAL:
    case EOPNOTSUPP:
      break;
    default:
      syslog (LOG_DEBUG, "probe_link on %s failed: %s\n",
            if_name, (strerror (retval)));
      break;
    }
#ifdef IFMON_DEBUG
  syslog (LOG_INFO, "get_link_status: %s 0x%x %s %d 0x%02x\n",
        if_name, if_flags, (managed_p ? "managed" : "unmanaged"),
        retval, ((retval == 0) ? (*result_r) : 0));
#endif
  return (retval);
}

static int
read_if_flags (const char * if_name, int * if_flags_r)
{
  struct ifreq ifr;
  strncpy ((ifr . ifr_name), if_name, IFNAMSIZ);
  if ((ioctl (kernel_connection, SIOCGIFFLAGS, (&ifr))) < 0)
    {
      log_perror ("SIOCGIFFLAGS");
      return (-1);
    }
  (*if_flags_r) = (ifr . ifr_flags);
  return (0);
}

static int
write_if_flags (const char * if_name, int if_flags)
{
  struct ifreq ifr;
  strncpy ((ifr . ifr_name), if_name, IFNAMSIZ);
  (ifr . ifr_flags) = if_flags;
  if ((ioctl (kernel_connection, SIOCSIFFLAGS, (&ifr))) < 0)
    {
      log_perror ("SIOCSIFFLAGS");
      return (-1);
    }
  return (0);
}

Generated by  Doxygen 1.6.0   Back to index