summaryrefslogblamecommitdiff
path: root/jobmon/netmon.c
blob: b571f1723ef62d31b02a3b709b0eb82ca7a6e7dc (plain) (tree)



































































































































                                                                                                                                         
                                                                                                            


























































































































                                                                                                         
/*
 * netmon --
 *    Monitor a network interface.  If the IP address disappears, run "ifconfig <interface> inet autoconf"
 */

#include <arpa/inet.h>
#include <errno.h>
#include <getopt.h>
#include <ifaddrs.h>
#include <netinet/in.h>
#include <signal.h>
#include <stdarg.h>
#include <stdbool.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <sys/wait.h>
#include <syslog.h>
#include <unistd.h>

#define DEFAULT_INTERVAL 10
#define MAX_INTERVAL 3600

bool foreground;
bool use_syslog;
#define PID_DIR "/var/run"
#define PID_FILE "netmon.pid"
const char *pid_file = PID_DIR"/"PID_FILE;

void
do_log (int priority, const char *msg, va_list ap) {
   if (use_syslog) {
      vsyslog (priority, msg, ap);
   } else {
      FILE *out = (priority == LOG_NOTICE ||
                   priority == LOG_INFO ||
                   priority == LOG_DEBUG) ? stdout : stderr;
      vfprintf (out, msg, ap);
      fputc ('\n', out);
   }
}

void
debug (const char *msg, ...) {
   if (foreground) {
      va_list ap;
      va_start (ap, msg);
      do_log (LOG_DEBUG, msg, ap);
      va_end (ap);
   }
}

void
info (const char *msg, ...) {
   va_list ap;
   va_start (ap, msg);
   do_log (LOG_INFO, msg, ap);
   va_end (ap);
}

void
error (const char *msg, ...) {
   va_list ap;
   va_start (ap, msg);
   do_log (LOG_ERR, msg, ap);
   va_end (ap);
}

void
die (const char *msg, ...) {
   va_list ap;
   va_start (ap, msg);
   do_log (LOG_ERR, msg, ap);
   va_end (ap);
   unlink (pid_file);
   exit (EXIT_FAILURE);
}


void
sigexit (int sig) {
   unlink (pid_file);
   exit (EXIT_SUCCESS);
}


bool
is_up (int af, char *interface) {
   struct ifaddrs *ifap, *ifa = NULL;
   bool found = false;

   if (getifaddrs (&ifa) == -1) die ("getifaddrs: %s", strerror (errno));

   for (ifap = ifa; ifap != NULL; ifap = ifap->ifa_next) {
      struct sockaddr *sa = ifap->ifa_addr;

      if (sa->sa_family == af && ! strcmp (interface, ifap->ifa_name)) {
         if (foreground) {
            if (sa->sa_family == AF_INET) {
               char addr[INET_ADDRSTRLEN] = {0};
               debug ("%s up: %s", ifap->ifa_name, inet_ntop (AF_INET, &(((struct sockaddr_in *)sa)->sin_addr), addr, sizeof (addr)));
            } else if (sa->sa_family == AF_INET6) {
               char addr[INET6_ADDRSTRLEN] = {0};
               debug ("%s up: %s", ifap->ifa_name, inet_ntop (AF_INET6, &(((struct sockaddr_in6 *)sa)->sin6_addr), addr, sizeof (addr)));
            }
         }

         found = true;
         goto out;
      }
   }

  out:
   freeifaddrs (ifa);

   return found;
}

void
configure (int af, char *interface) {
   pid_t p, wp;
   int status;
   unsigned int iters = 0;
#define MAX_ITERS 30

   info ("reconfiguring %s", interface);

   if ((p = fork ()) == -1) die ("fork failure: %s", strerror (errno));

   if (p == 0) {
      execl ("/sbin/ifconfig", "ifconfig", interface, (af == AF_INET) ? "inet" : "inet6", "autoconf", NULL);
      die ("exec failure: %s", strerror (errno));
   }

   while (1) {
      wp = waitpid (p, &status, WNOHANG);

      if (wp == -1) {
         if (errno == ECHILD) { break; }
         die ("waitpid failure: %s", strerror (errno));
      } else if (wp == 0) {
         if (iters++ > MAX_ITERS) {
            error ("child overdue, killing and bailing");
            kill (p, SIGTERM);
            sleep (1);
            kill (p, SIGKILL);
            unlink (pid_file);
            exit (EXIT_FAILURE);
         }
      } else {
         if (WIFEXITED (status)) {
            debug ("child exited with exit code %d", WEXITSTATUS (status));
         } else if (WIFSIGNALED (status)) {
            debug ("child exited with signal %d", WTERMSIG (status));
         } else {
            debug ("child exited with status %d", status);
         }
      }

      sleep (1);
   }
}

void
help (int ec) {
   printf ("netmon [--interval|-i <seconds>] [-4|-6] <interface>\n"
           "\n"
           "    Monitor a network interface and reconfigure it if it goes down\n"
           "\n"
           "Options:\n"
           "    --debug|-d      run in the foreground and send output to the console\n"
           "    --interval|-i   how frequently to check in seconds (default %u)\n"
           "    --ipv4|-4       check for configured IPv4 address (default)\n"
           "    --ipv6|-6       check for a configured IPv6 address instead of IPv4\n",
           DEFAULT_INTERVAL);
   exit (ec);
}

int
main (int argc, char *argv[]) {
   struct option options[] = {
      { "interval", required_argument, NULL, 'i' },
      { "debug",          no_argument, NULL, 'd' },
      { "ipv4",           no_argument, NULL, '4' },
      { "ipv6",           no_argument, NULL, '6' },
      { "help",           no_argument, NULL, 'h' },
      { 0, 0, 0, 0 }
   };
   int opt;
   int af = AF_INET;
   unsigned long interval = DEFAULT_INTERVAL;
   char *interface = NULL;

   while ((opt = getopt_long (argc, argv, "i:46dh", options, NULL)) != -1) {
      switch (opt) {
      case 'i':
         errno = 0;
         interval = strtoul (optarg, NULL, 10);
         if (interval < 1 || interval > MAX_INTERVAL || errno) {
            die ("invalid interval '%s', must be in the range [1,%u]", optarg, MAX_INTERVAL);
         }
         break;
      case '4':
         af = AF_INET;
         break;
      case '6':
         af = AF_INET6;
         break;
      case 'd':
         foreground = true;
         break;
      case 'h':
         help (EXIT_SUCCESS);
         break;
      default:
         help (EXIT_FAILURE);
      }
   }

   if (optind >= argc) {
      help (EXIT_FAILURE);
   }

   interface = argv[optind];

   if (! foreground) {
      if (access (PID_DIR, W_OK) == -1) die ("unable to write pid file '%s'", pid_file);

      use_syslog = true;
      if (daemon (0, 0) == -1) die ("daemon failed: %s", strerror (errno));

      FILE *fp = fopen (pid_file, "w");
      if (fp == NULL) die ("error writing pid file '%s': %s", pid_file, strerror (errno));
      fprintf (fp, "%d\n", getpid ());
      fclose (fp);

      if (pledge ("cpath exec inet proc stdio unveil", NULL) == -1) die ("pledge: %s", strerror (errno));

      if (unveil (pid_file, "c") == -1) die ("unveil %s: %s", pid_file, strerror (errno));
      if (unveil ("/sbin", "rx") == -1) die ("unveil /sbin: %s", strerror (errno));
      if (unveil (NULL, NULL) == -1) die ("unveil: %s", strerror (errno));
   }

   signal (SIGINT, sigexit);
   signal (SIGTERM, sigexit);
   signal (SIGQUIT, sigexit);

   while (1) {
      if (! is_up (af, interface)) configure (af, interface);
      sleep ((unsigned int)interval);
   }

   return 0;
}