/*
* 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;
}