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

laptop-netconf.c

#include <pthread.h>
#include <semaphore.h>
#include <stdio.h>
#include <string.h>
#include <unistd.h>
#include <ctype.h>
#include <libnet.h>
#include <pcap.h>
#include <sys/time.h>
#include <sys/types.h>


#define CONFIGURATION_DIRECTORY "/etc/laptop-netconf"
#define CONFIGURATION_FILE CONFIGURATION_DIRECTORY "/opts"


/* nethost structure stores profile information */
00018 typedef struct nethost_t
{
  unsigned long local_ip_address;
  unsigned long probe_ip_address;
  unsigned char hardware_address[6];
  const char *profile;
  struct nethost_t *next;
} nethost;


/* global variables -- urgh! */
nethost default_nethost = { 0, 0, {"\0\0\0\0\0\0"}, "default", 0 };
nethost *nethosts;
int nethost_count;
char *ethernet_device;
int debug;
static char errbuf[256];
sem_t send_semaphore;
sem_t stop_semaphore;
pthread_t thread_id;


/* Handle parse errors */
void parse_error (char *buffer, char *ptr, int line)
{
  int spaces;

  fprintf (stderr, "laptop-netconf: Parse error in configuration file at line %d.\nlaptop-netconf: %slaptop-netconf: ", line, buffer);

  for (spaces=0; spaces<(ptr-buffer); ++spaces)
    fprintf (stderr, " ");

  fprintf (stderr, "^\n");

  exit (1);
}


/* Handle other fatal errors */
void fatal_error (char *ptr)
{
  fprintf (stderr, "laptop-netconf: %s\n", ptr);
  exit (1);
}


/* Handle fatal errors and print out the system error message */
void fatal_perror (char *ptr)
{
  fprintf (stderr, "laptop-netconf: %s\n", ptr);
  perror ("System error");
  exit (1);
}


/*
 * Parser states
 *
 *  0 optional space
 *  1 comment
 * 98 end of line
 *
 *  0 optional space
 *  2 self/host
 *  3 space
 *  4 ip address
 *  5 space
 *  6 probe
 *  7 space
 *  8 ip address
 *  9 space
 * 10 hwaddress
 * 11 space
 * 12 hardware address
 * 13 space
 * 14 profile
 * 15 space
 * 16 token
 * 17 optional space
 * 99 end of line
 *
 *  0 optional space
 * 30 device
 * 31 space
 * 32 token
 * 33 optional space
 * 98 end of line
 *
 *  0 optional space
 * 40 debug
 * 41 optional space
 * 98 end of line
 */


/* Parse space characters */
void parse_space (char *buffer, char **ptr, nethost *temp, int *state, int line)
{
  if (*state == 0)
    *state = 0;
  else if (*state == 2)
    *state = 3;
  else if (*state == 4)
    *state = 5;
  else if (*state == 6)
    *state = 7;
  else if (*state == 8)
    *state = 9;
  else if (*state == 10)
    *state = 11;
  else if (*state == 12)
    *state = 13;
  else if (*state == 14)
    *state = 15;
  else if (*state == 16)
    *state = 17;
  else if (*state == 30)
    *state = 31;
  else if (*state == 32)
    *state = 33;
  else if (*state == 40)
    *state = 41;
  else
    parse_error (buffer, *ptr, line);

  while (**ptr  &&  isspace (**ptr)) ++*ptr;
}


/* Parse device keyword */
void parse_device (char *buffer, char **ptr, nethost *temp, int *state, int line)
{
  if (*state == 0)
    *state = 30;
  else
    parse_error (buffer, *ptr, line);

  *ptr += 6;
}


/* Parse debug keyword */
void parse_debug (char *buffer, char **ptr, nethost *temp, int *state, int line)
{
  if (*state == 0)
    *state = 40;
  else
    parse_error (buffer, *ptr, line);

  *ptr += 5;

  ++debug;
}


/* Parse self/host keyword */
void parse_self (char *buffer, char **ptr, nethost *temp, int *state, int line)
{
  if (*state == 0)
    *state = 2;
  else
    parse_error (buffer, *ptr, line);

  *ptr += 4;
}


/* Parse probe keyword */
void parse_probe (char *buffer, char **ptr, nethost *temp, int *state, int line)
{
  if (*state == 5)
    *state = 6;
  else
    parse_error (buffer, *ptr, line);

  *ptr += 5;
}


/* Parse hardware address keyword */
void parse_hwaddress (char *buffer, char **ptr, nethost *temp, int *state, int line)
{
  if (*state == 9)
    *state = 10;
  else
    parse_error (buffer, *ptr, line);

  *ptr += 9;
}


/* Parse profile keyword */
void parse_profile (char *buffer, char **ptr, nethost *temp, int *state, int line)
{
  if (*state == 13)
    *state = 14;
  else
    parse_error (buffer, *ptr, line);

  *ptr += 7;
}


/* Parse comments */
void parse_comment (char *buffer, char **ptr, nethost *temp, int *state, int line)
{
  if (*state == 0)
    *state = 1;
  else
    parse_error (buffer, *ptr, line);

  *ptr += strlen (*ptr);
}


/* Parse profile/device names */
void parse_token (char *buffer, char **ptr, nethost *temp, int *state, int line)
{
  char *start, *token;
  int length;

  if (*state == 15)
    *state = 16;
  else if (*state == 31)
    *state = 32;
  else
    parse_error (buffer, *ptr, line);

  start = *ptr;
  while (**ptr  &&  isalnum (**ptr)) ++*ptr;

  length = *ptr - start;
  token = (char *)malloc (length + 1);
  if (!token) fatal_perror ("Memory allocation error.");
  strncpy (token, start, length);
  token[length] = '\0';

  if (*state == 16)
    temp->profile = token;
  else
    ethernet_device = token;
}


/* Parse ip addresses */
void parse_ip_address (char *buffer, char **ptr, nethost *temp, int *state, int line)
{
  int a, b, c, d;
  char tbuffer[32];

  if (*state == 3)
    *state = 4;
  else if (*state == 7)
    *state = 8;
  else
    parse_error (buffer, *ptr, line);

  if (sscanf (*ptr, "%d.%d.%d.%d", &a, &b, &c, &d) != 4)
    parse_error (buffer, *ptr, line);

  if (a<0  ||  a>255  ||  b<0  ||  b>255  ||  c<0  ||  c>255  ||  d<0  ||  d>255)
    parse_error (buffer, *ptr, line);

  sprintf (tbuffer, "%d.%d.%d.%d", a, b, c, d);
  *ptr += strlen (tbuffer);

  if (*state == 4)
    temp->local_ip_address = htonl ((a << 24) + (b << 16) + (c << 8) + d);
  else
    temp->probe_ip_address = htonl ((a << 24) + (b << 16) + (c << 8) + d);
}


/* Parse hardware addresses */
void parse_hardware_address (char *buffer, char **ptr, nethost *temp, int *state, int line)
{
  int a, b, c, d, e, f;

  if (*state == 11)
    *state = 12;
  else
    parse_error (buffer, *ptr, line);

  if (sscanf (*ptr, "%x:%x:%x:%x:%x:%x", &a, &b, &c, &d, &e, &f) != 6)
    parse_error (buffer, *ptr, line);

  if (a<0  ||  a>255  ||  b<0  ||  b>255  ||  c<0  ||  c>255  ||  d<0  ||  d>255  ||  e<0  || e>255  ||  f<0  ||  f>255)
    parse_error (buffer, *ptr, line);

  *ptr += 17;

  temp->hardware_address[0] = a;
  temp->hardware_address[1] = b;
  temp->hardware_address[2] = c;
  temp->hardware_address[3] = d;
  temp->hardware_address[4] = e;
  temp->hardware_address[5] = f;
}


/* Parse end-of-lines */
void parse_eol (char *buffer, char **ptr, nethost *temp, int *state, int line)
{
  if (*state == 0  ||  *state == 1  ||
      *state == 32  ||  *state == 33  ||
      *state == 40  ||  *state == 41) {
    *state = 98;
    return;
  } else if (*state == 16  ||  *state == 17)
    *state = 99;
  else
    parse_error (buffer, *ptr, line);
}


/* Read and parse the configuration file */
void parse_configuration ()
{
  char buffer[2048];
  int line = 0;
  FILE *f;

  /* open the configuration file */
  if (!(f = fopen (CONFIGURATION_FILE, "r")))
    fatal_perror ("Unable to read configuration file.");

  /* main scanner loop */
  while (fgets (buffer, 2047, f)) {
    int state = 0;
    char *p = buffer;
    nethost temp;
    ++line;

    /* very simple scanner to kick the parser into action */
    while (*p) {
      if (isspace (*p))
      parse_space (buffer, &p, &temp, &state, line);

      else if (strncmp (p, "device", 6) == 0)
      parse_device (buffer, &p, &temp, &state, line);

      else if (strncmp (p, "debug", 5) == 0)
      parse_debug (buffer, &p, &temp, &state, line);

      else if (strncmp (p, "self", 4) == 0  ||  strncmp (p, "host", 4) == 0)
      parse_self (buffer, &p, &temp, &state, line);

      else if (strncmp (p, "probe", 5) == 0)
      parse_probe (buffer, &p, &temp, &state, line);

      else if (strncmp (p, "hwaddress", 9) == 0)
      parse_hwaddress (buffer, &p, &temp, &state, line);

      else if (strncmp (p, "profile", 7) == 0)
      parse_profile (buffer, &p, &temp, &state, line);

      else if (*p == '#')
      parse_comment (buffer, &p, &temp, &state, line);

      else if ((state == 3  ||  state == 7)  &&  isdigit (*p))
      parse_ip_address (buffer, &p, &temp, &state, line);

      else if (state == 11  &&  isxdigit (*p))
      parse_hardware_address (buffer, &p, &temp, &state, line);

      else if (isalnum (*p))
      parse_token (buffer, &p, &temp, &state, line);

      else
      parse_error (buffer, p, line);
    }

    /* each line must end with an end of line character */
    parse_eol (buffer, &p, &temp, &state, line);

    /* have we got a complete nethost record? */
    if (state == 99) {
      /* add the record to the linked list */
      if (nethost_count == 0) {
      nethosts = (nethost *)malloc (sizeof (nethost));
      if (!nethosts) fatal_perror ("Memory allocation error.");
      memcpy (nethosts, &temp, sizeof (nethost));
      nethosts->next = NULL;
      } else {
      nethost *t = nethosts;
      while (t->next) t = t->next;
      t->next = (nethost *)malloc (sizeof (nethost));
      if (!t->next) fatal_perror ("Memory allocation error.");
      memcpy (t->next, &temp, sizeof (nethost));
      t->next->next = NULL;
      }

      ++nethost_count;
    }

    /* have we got a complete line? */
    else if (state == 98)
      continue;

    /* nothing we understand */
    else
      parse_error (buffer, p, line);
  }

  /* default the ethernet device to eth0 */
  if (!ethernet_device) {
    if (!(ethernet_device = (char *)malloc (5)))
      fatal_perror ("Memory allocation error.");
    strcpy (ethernet_device, "eth0");
  }

  /* check we have a sane configuration */
  if (nethost_count==0)
    fatal_error ("You should define at least one profile in /etc/laptop-netconf/opts.");
}


/* Bring the network interface up if it is down */
void ifup_interface ()
{
  struct libnet_link_int *link_interface;
  struct ether_addr *local_hardware_address;
  char command[1024];
  char buffer[1024];
  unsigned char ethernet_broadcast_address[6] = {0xff, 0xff, 0xff, 0xff, 0xff, 0xff};
  unsigned char ethernet_no_address[6] = {0x00, 0x00, 0x00, 0x00, 0x00, 0x00};
  int count, null=0;

  /* open the interface */
  if (!(link_interface = libnet_open_link_interface (ethernet_device, buffer)))
    fatal_error ("Unable to open link interface.");

  /* build the command to bring the network interface up */
  sprintf (command, "/sbin/ifconfig %s 0.0.0.0", ethernet_device);

  /* retrieve the local hardware address */
  if (!(local_hardware_address = libnet_get_hwaddr (link_interface, ethernet_device, buffer)))
    fatal_error ("Unable to determine local hardware address.");

  /* construct an ethernet packet... */
  if (libnet_build_ethernet (ethernet_broadcast_address, local_hardware_address->ether_addr_octet, ETHERTYPE_ARP, NULL, 0, buffer)<0)
    fatal_perror ("Unable to construct ethernet packet.");

  /* ...containing an arp request... */
  if (libnet_build_arp (ARPHRD_ETHER, ETHERTYPE_IP, ETHER_ADDR_LEN, 4, ARPOP_REQUEST, local_hardware_address->ether_addr_octet, (u_char *)&null, ethernet_no_address, (u_char *)&null, NULL, 0, buffer + ETH_H)<0)
    fatal_perror ("Unable to construct arp packet.");

  /* ...and send it out onto the network -- note the comparison >= 0 */
  if (libnet_write_link_layer (link_interface, ethernet_device, buffer, LIBNET_ARP_H + LIBNET_ETH_H)>=0) {
    /* close the interface */
    libnet_close_link_interface (link_interface);

    /* return without touching the interface */
    return;
  }

  /* send failed! */

  /* execute the command to bring the network interface up */
  if (system (command) != 0)
    fatal_perror ("Unable to bring network interface up.");

  for (count=0; count<3; ++count) {
    /* try resending -- note the comparison >= 0 */
    if (libnet_write_link_layer (link_interface, ethernet_device, buffer, LIBNET_ARP_H + LIBNET_ETH_H)>=0) {
      /* close the interface */
      libnet_close_link_interface (link_interface);

      /* sleep for a few seconds until the interface has stabilised */
      sleep (3);

      /* the interface has come up -- return */
      return;
    }
  }

  /* interface did not come up */
  fatal_perror ("Unable to send ARP packet.");
}


/* Routine to intermittently send out ARP packets */
void *send_arp_requests_threaded (void *arg)
{
  char buffer[1024];
  struct libnet_link_int *link_interface;
  struct ether_addr *local_hardware_address;
  unsigned char ethernet_broadcast_address[6] = {0xff, 0xff, 0xff, 0xff, 0xff, 0xff};
  unsigned char ethernet_no_address[6] = {0x00, 0x00, 0x00, 0x00, 0x00, 0x00};
  nethost *cycle;
  int repeat, errors=0;

  /* open the interface */
  if (!(link_interface = libnet_open_link_interface (ethernet_device, buffer)))
    fatal_error ("Unable to open link interface.");

  /* retrieve the local hardware address */
  if (!(local_hardware_address = libnet_get_hwaddr (link_interface, ethernet_device, buffer)))
    fatal_error ("Unable to determine local hardware address.");

  /* main sending loop */
  while (1) {
    /* wait to be prodded by the listener */
    sem_wait (&send_semaphore);

    /* has the listener received a reply or become fed up? */
    if (sem_trywait (&stop_semaphore)==0)
      break;

    /* for each nethost record... */
    for (cycle=nethosts; cycle; cycle=cycle->next) {
      /* ...construct an ethernet packet... */
      if (libnet_build_ethernet (ethernet_broadcast_address, local_hardware_address->ether_addr_octet, ETHERTYPE_ARP, NULL, 0, buffer)<0)
      fatal_perror ("Unable to construct ethernet packet.");

      /* ...containing an arp request.. */
      if (libnet_build_arp (ARPHRD_ETHER, ETHERTYPE_IP, ETHER_ADDR_LEN, 4, ARPOP_REQUEST, local_hardware_address->ether_addr_octet, (u_char *)&cycle->local_ip_address, ethernet_no_address, (u_char *)&cycle->probe_ip_address, NULL, 0, buffer + ETH_H)<0)
      fatal_perror ("Unable to construct arp packet.");

      /* ...log it if requested to... */
      if (debug>1)
      printf ("Sending packet %08x.\n", *((int *)(buffer + LIBNET_ETH_H + 14)));

      /* ...and send it out onto the network */
      if (libnet_write_link_layer (link_interface, ethernet_device, buffer, LIBNET_ARP_H + LIBNET_ETH_H)<0)
      fatal_perror ("Unable to send ARP packet.");
    }
  }

  /* close the interface */
  libnet_close_link_interface (link_interface);
}


/* Create thread to intermittently send out ARP packets */
void send_arp_requests ()
{
  /* create a new thread */
  if (pthread_create (&thread_id, 0, send_arp_requests_threaded, 0))
    fatal_perror ("Unable to create ARP request thread.");
}


/* Configure the network for the given profile */
void configure_network (nethost *profile)
{
  char command[1024];
  char ip_address[32];
  int a, b, c, d;

  /* build the zeroth command argument: /etc/laptop-netconf/PROFILE */
  sprintf (command, "%s/%s", CONFIGURATION_DIRECTORY, profile->profile);

  /* build the first command argument: the local ip address */
  d = ntohl (profile->local_ip_address);
  a = (d >> 24) & 255;
  b = (d >> 16) & 255;
  c = (d >> 8) & 255;
  d = d & 255;
  sprintf (ip_address, "%d.%d.%d.%d", a, b, c, d);

  /* debug information */
  if (debug) printf ("Running command: \"%s %s\".\n", command, ip_address);

  /* execute the command */
  execlp (command, command, ip_address, NULL);

  /* failed to execute => bail out */
  fatal_error ("Unable to configure network.");
}


/* Wait for ARP replies to be received */
nethost *receive_arp_replies ()
{
  char buffer[1024];
  unsigned char *packet;
  struct pcap_pkthdr pcap_header;
  pcap_t *pcap_interface;
  fd_set fd_wait;
  struct timeval time_to_sleep, end, now;
  int result;
  nethost *found_host=0;
  struct libnet_ethernet_hdr *packet_header;
  struct libnet_arp_hdr *arp_header;
  void *thread_result;
  int iterations=3;

  /* open the packet capture interface */
  if (!(pcap_interface = pcap_open_live (ethernet_device, LIBNET_ARP_H+LIBNET_ETH_H, 0, 500, buffer)))
    fatal_error ("Unable to open PCAP interface.");

  /* prepare the file descriptor set for select() */
  FD_ZERO (&fd_wait);
  FD_SET (pcap_fileno (pcap_interface), &fd_wait);

  /* prepare the timeout for select () */
  time_to_sleep.tv_sec = 1;
  time_to_sleep.tv_usec = 0;

  /* first send cycle */
  sem_post (&send_semaphore);

  /* main send/receive loop */
  while (iterations && !found_host) {
    /* get the current time */
    if (gettimeofday (&now, NULL)<0)
      fatal_perror ("Unable to determine the current time.");

    /* sleep (waiting for input) */
    if ((result = select (FD_SETSIZE, &fd_wait, NULL, NULL, &time_to_sleep)) < 0)
      fatal_perror ("Insomnia -- cannot sleep.");

    /* check for timer expiry */
    if (result == 0) {
      /* was this the last iteration */
      if (--iterations == 0)
      break;

      /* prepare a new timeout for select() */
      time_to_sleep.tv_sec = 1;
      time_to_sleep.tv_usec = 0;

      /* next send/receive cycle */
      sem_post (&send_semaphore);
      continue;
    }

    /* we have data waiting */
    packet = ((u_char *)pcap_next (pcap_interface, &pcap_header));
    packet_header = (struct libnet_ethernet_hdr *)packet;

    /* it is an arp packet */
    if (ntohs (packet_header->ether_type) == ETHERTYPE_ARP) {
      arp_header = (struct libnet_arp_hdr *)(packet + ETH_H);

      /* an arp reply -- we are getting close */
      if (ntohs (arp_header->ar_op) == ARPOP_REPLY) {
      unsigned long source, target;
      nethost *cycle;

      /* retrieve the source and target ip addresses */
      source = *((int *)&arp_header->ar_spa);
      target = *((int *)arp_header->ar_tpa);

      /* log them if requested to */
      if (debug>1)
        printf ("Received ARP reply packet\n  from %08X to %08X\n", source, target);

      /* for each nethost... */
      for (cycle=nethosts; cycle; cycle=cycle->next) {
        int hwcount;

        /* ...log its ip addresses if requested to... */
        if (debug>1)
          printf ("  test %08X to %08X\n", cycle->probe_ip_address, cycle->local_ip_address);
      
        /* ...check whether the ip addreses match... */
        if (cycle->local_ip_address != target  ||  cycle->probe_ip_address != source)
          continue;

        /* ...log its hardware address if requested to... */
        if (debug>1)
          printf ("%02x:%02x:%02x:%02x:%02x:%02x\n%02x:%02x:%02x:%02x:%02x:%02x\n", cycle->hardware_address[0], cycle->hardware_address[1], cycle->hardware_address[2], cycle->hardware_address[3], cycle->hardware_address[4], cycle->hardware_address[5], arp_header->ar_sha[0], arp_header->ar_sha[1], arp_header->ar_sha[2], arp_header->ar_sha[3], arp_header->ar_sha[4], arp_header->ar_sha[5]);

        /* ...check whether the harware address matches... */
        if (memcmp (cycle->hardware_address, arp_header->ar_sha, 6))
          continue;

        /* ...log the match if requested to... */
        if (debug>1)
          printf ("Found entry:\n  local ip address %08x\n  hwaddress %02x:%02x:%02x:%02x:%02x:%02x\n  profile %s\n",
                cycle->local_ip_address,
                cycle->hardware_address[0], cycle->hardware_address[1], cycle->hardware_address[2],
                cycle->hardware_address[3], cycle->hardware_address[4], cycle->hardware_address[5],
                cycle->profile);

        /* ...and store the match for later */
        found_host = cycle;
        break;
      }
      }
    }
  }

  /* stop the sending thread */
  sem_post (&stop_semaphore);
  sem_post (&send_semaphore);

  /* wait for it */
  if (pthread_join (thread_id, NULL)<0) {
    while (errno == EINTR) {
      if (pthread_join (thread_id, NULL)<0  &&  errno != EINTR)
      fatal_perror ("Cannot join with ARP sending thread.");
      break;
    }
  }

  /* return result if there is one */
  if (found_host)
    return found_host;

  /* return default result */
  return &default_nethost;
}


/* Application entry point */
int main (int argc, char *argv[])
{
  nethost *found_host;

  /* check user id */
  if (geteuid () != 0)
    fatal_error ("You must run this command as root.");

  /* initialise */
  nethost_count = 0;
  nethosts = NULL;
  ethernet_device = NULL;
  debug = 0;
  sem_init (&send_semaphore, 0, 0);
  sem_init (&stop_semaphore, 0, 0);

  /* parse the configuration file */
  parse_configuration ();

  /* bring the network interface up */
  ifup_interface ();

  /* start the ARP request thread */
  send_arp_requests ();

  /* listen for ARP replies */
  found_host = receive_arp_replies ();

  /* configure the network appropriately */
  configure_network (found_host);

  return 0;
}

Generated by  Doxygen 1.6.0   Back to index