#define _GNU_SOURCE
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <ctype.h>
#include <errno.h>
#include <assert.h>
#include <unistd.h>
#include <fcntl.h>
#include <getopt.h>
#include <fnmatch.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <limits.h>
#include <err.h>
#include <ifaddrs.h>
#include <signal.h>
#include "archcommon.h"
#include "header.h"
static const char *argv0;
bool do_interface_lock = true;
bool no_act = false;
bool no_act_commands = false;
bool run_scripts = true;
bool verbose = false;
bool no_loopback = false;
bool ignore_failures = false;
interfaces_file *defn;
static char *statedir;
static char *lockfile;
static char *statefile;
static char *tmpstatefile;
volatile bool interrupted = false;
static void signal_handler(int sig) {
interrupted = true;
fprintf(stderr, "Got signal %s, terminating...\n", strsignal(sig));
signal(sig, SIG_DFL);
}
static void usage(void) {
errx(1, "Use --help for help");
}
static void version(void) {
printf("%s version " IFUPDOWN_VERSION "\n"
"\n"
"Copyright (c) 1999-2009 Anthony Towns\n"
" 2010-2015 Andrej Shadura\n"
" 2015-2017 Guus Sliepen\n"
"\n"
"This program is free software; you can redistribute it and/or modify\n"
"it under the terms of the GNU General Public License as published by\n"
"the Free Software Foundation; either version 2 of the License, or (at\n"
"your option) any later version.\n", argv0);
exit(0);
}
static void help(int (*cmds) (interface_defn *)) {
printf("Usage: %s <options> <ifaces...>\n", argv0);
if ((cmds == iface_list) || (cmds == iface_query)) {
printf(" %s <options> --list\n", argv0);
printf(" %s --state <ifaces...>\n", argv0);
}
printf("\n"
"Options:\n"
"\t-h, --help this help\n"
"\t-V, --version copyright and version information\n"
"\t-a, --all process all interfaces marked \"auto\"\n"
"\t--allow CLASS ignore non-\"allow-CLASS\" interfaces\n"
"\t-i, --interfaces FILE use FILE for interface definitions\n"
"\t--state-dir DIR use DIR to store state information\n"
"\t-X, --exclude PATTERN exclude interfaces from the list of\n"
"\t interfaces to operate on by a PATTERN\n");
if (!(cmds == iface_list) && !(cmds == iface_query))
printf( "\t-n, --no-act print out what would happen, but don't do it\n"
"\t (note that this option doesn't disable mappings)\n");
printf( "\t-v, --verbose print out what would happen before doing it\n"
"\t-o OPTION=VALUE set OPTION to VALUE as though it were in\n"
"\t /etc/network/interfaces\n"
"\t--no-mappings don't run any mappings\n"
"\t--no-scripts don't run any hook scripts\n"
"\t--no-loopback don't act specially on the loopback device\n");
if (!(cmds == iface_list) && !(cmds == iface_query))
printf( "\t--force force de/configuration\n"
"\t--ignore-errors ignore errors\n");
if ((cmds == iface_list) || (cmds == iface_query))
printf( "\t--list list all matching known interfaces\n"
"\t--state show the state of specified interfaces\n");
exit(0);
}
static int lock_fd(int fd) {
struct flock lock = {
.l_type = no_act ? F_RDLCK : F_WRLCK,
.l_whence = SEEK_SET,
.l_start = 0,
.l_len = 0,
};
return fcntl(fd, F_SETLKW, &lock);
}
static FILE *lock_state(void) {
FILE *lock_fp = fopen(lockfile, no_act ? "re" : "a+e");
if (lock_fp == NULL) {
if (!no_act)
err(1, "failed to open lockfile %s", lockfile);
else
return NULL;
}
if (lock_fd(fileno(lock_fp)) != 0) {
if (!no_act)
err(1, "failed to lock lockfile %s", lockfile);
}
return lock_fp;
}
static char *strip(char *buf) {
char *pch = buf + strlen(buf) - 1;
while (pch > buf && isspace(*pch))
*pch-- = '\0';
while (isspace(*buf))
buf++;
return buf;
}
static void sanitize_file_name(char *name) {
for (; *name; name++)
if (*name == '/')
*name = '.';
}
void sanitize_env_name(char *name) {
for (; *name; name++)
if (*name == '=')
*name = '_';
}
static char *ifacestatefile(const char *iface) {
char *filename = NULL;
if(asprintf(&filename, "%s.%s", statefile, iface) == -1 || !filename)
err(1, "asprintf");
sanitize_file_name(filename + strlen(statefile) + 1);
return filename;
}
static bool is_locked(const char *iface) {
char *filename = ifacestatefile(iface);
FILE *lock_fp = fopen(filename, "r");
free(filename);
if (lock_fp == NULL)
return false;
struct flock lock = {.l_type = F_WRLCK, .l_whence = SEEK_SET};
if (fcntl(fileno(lock_fp), F_GETLK, &lock) != 0)
lock.l_type = F_UNLCK;
fclose(lock_fp);
return lock.l_type != F_UNLCK;
}
static FILE *lock_interface(const char *iface, char **state) {
char *filename = ifacestatefile(iface);
FILE *lock_fp = fopen(filename, no_act ? "re" : "a+e");
if (lock_fp == NULL) {
if (!no_act) {
err(1, "failed to open lockfile %s", filename);
} else {
free(filename);
return NULL;
}
}
struct flock lock = {.l_type = no_act ? F_RDLCK : F_WRLCK, .l_whence = SEEK_SET};
if (fcntl(fileno(lock_fp), F_SETLK, &lock) != 0) {
if (errno == EACCES || errno == EAGAIN) {
warnx("waiting for lock on %s", filename);
if (fcntl(fileno(lock_fp), F_SETLKW, &lock) != 0) {
if (!no_act)
err(1, "failed to lock lockfile %s", filename);
}
} else if (!no_act) {
err(1, "failed to lock lockfile %s", filename);
}
}
if (state) {
char buf[80];
char *p = fgets(buf, sizeof buf, lock_fp);
if(p) {
p = strip(buf);
*state = *p ? strdup(p) : NULL;
} else {
*state = NULL;
}
}
free(filename);
return lock_fp;
}
static void read_all_state(char ***ifaces, int *n_ifaces) {
FILE *lock_fp = lock_state();
FILE *state_fp = fopen(statefile, no_act ? "re" : "a+e");
*n_ifaces = 0;
*ifaces = NULL;
if (state_fp == NULL) {
if (!no_act)
err(1, "failed to open statefile %s", statefile);
else
goto end;
}
char buf[80];
char *p;
while ((p = fgets(buf, sizeof buf, state_fp)) != NULL) {
(*n_ifaces)++;
*ifaces = realloc(*ifaces, sizeof(**ifaces) * *n_ifaces);
(*ifaces)[(*n_ifaces) - 1] = strdup(strip(buf));
}
for (int i = 0; i < ((*n_ifaces) / 2); i++) {
char *temp = (*ifaces)[i];
(*ifaces)[i] = (*ifaces)[(*n_ifaces) - i - 1];
(*ifaces)[(*n_ifaces) - i - 1] = temp;
}
end:
if (state_fp)
fclose(state_fp);
if (lock_fp)
fclose(lock_fp);
}
static void update_state(const char *iface, const char *state, FILE *lock_fp) {
if (lock_fp && !no_act) {
rewind(lock_fp);
if(ftruncate(fileno(lock_fp), 0) == -1)
err(1, "failed to truncate lockfile");
fprintf(lock_fp, "%s\n", state ? state : "");
fflush(lock_fp);
}
lock_fp = lock_state();
FILE *state_fp = fopen(statefile, no_act ? "re" : "a+e");
if (state_fp == NULL) {
if (!no_act)
err(1, "failed to open statefile %s", statefile);
else
goto end;
}
if (no_act)
goto end;
if (lock_fd(fileno(state_fp)) != 0)
err(1, "failed to lock statefile %s", statefile);
FILE *tmp_fp = fopen(tmpstatefile, "w");
if (tmp_fp == NULL)
err(1, "failed to open temporary statefile %s", tmpstatefile);
char buf[80];
char *p;
while ((p = fgets(buf, sizeof buf, state_fp)) != NULL) {
char *pch = strip(buf);
if (strncmp(iface, pch, strlen(iface)) == 0) {
if (pch[strlen(iface)] == '=') {
if (state != NULL) {
fprintf(tmp_fp, "%s=%s\n", iface, state);
state = NULL;
}
continue;
}
}
fprintf(tmp_fp, "%s\n", pch);
}
if (state != NULL)
fprintf(tmp_fp, "%s=%s\n", iface, state);
fclose(tmp_fp);
if (rename(tmpstatefile, statefile))
err(1, "failed to overwrite statefile %s", statefile);
end:
if (state_fp)
fclose(state_fp);
if (lock_fp)
fclose(lock_fp);
}
char *make_pidfile_name(const char *command, interface_defn *ifd) {
char *filename = NULL;
if(asprintf(&filename, "%s/%s-%s.pid", statedir, command, ifd->real_iface) == -1 || !filename)
err(1, "asprintf");
sanitize_file_name(filename + strlen(statedir) + 1);
return filename;
}
/* Ensure stdin, stdout and stderr are valid, open filedescriptors */
static void check_stdio(void) {
for (int i = 0; i <= 2; i++) {
errno = 0;
if (fcntl(i, F_GETFD) == -1) {
if (errno == EBADF) {
/* filedescriptor closed, try to open /dev/null in its place */
if (open("/dev/null", 0) != i)
errx(2, "fd %d not available; aborting", i);
} else {
/* some other problem -- eeek */
err(2, "fcntl on fd %d", i);
}
}
}
}
typedef int (*cmds_t)(interface_defn *);
/* Determine whether we are being called as ifup, ifdown or ifquery */
static cmds_t determine_command(void) {
const char *command = argv0;
if (strcmp(command, "ifup") == 0) {
return iface_up;
} else if (strcmp(command, "ifdown") == 0) {
ignore_failures = true;
return iface_down;
} else if (strcmp(command, "ifquery") == 0) {
no_act = true;
do_interface_lock = false;
return iface_query;
} else {
errx(1, "this command should be called as ifup, ifdown, or ifquery");
}
}
static cmds_t cmds = NULL;
bool do_all = false;
static bool run_mappings = true;
static bool force = false;
static bool list = false;
static bool state_query = false;
char *allow_class = NULL;
static char *interfaces = NULL;
char **no_auto_down_int = NULL;
int no_auto_down_ints = 0;
char **no_scripts_int = NULL;
int no_scripts_ints = 0;
char **rename_int = NULL;
int rename_ints = 0;
static char **excludeint = NULL;
static int excludeints = 0;
static variable *option = NULL;
static int n_options = 0;
static int max_options = 0;
static int n_target_ifaces;
static char **target_iface;
static void parse_environment_variables(void) {
const char *val = getenv("VERBOSE");
if(val && !strcmp(val, "yes"))
verbose = true;
val = getenv("CONFIGURE_INTERFACES");
if(val && !strcmp(val, "no"))
no_act = true;
val = getenv("EXCLUDE_INTERFACES");
if(val) {
char *excludes = strdup(val);
for(char *tok = strtok(excludes, " \t\n"); tok; tok = strtok(NULL, " \t\n")) {
excludeints++;
excludeint = realloc(excludeint, excludeints * sizeof *excludeint);
if (excludeint == NULL)
err(1, "realloc");
excludeint[excludeints - 1] = strdup(tok);
}
free(excludes);
}
}
static void parse_options(int *argc, char **argv[]) {
static const struct option long_opts[] = {
{"help", no_argument, NULL, 'h'},
{"version", no_argument, NULL, 'V'},
{"verbose", no_argument, NULL, 'v'},
{"all", no_argument, NULL, 'a'},
{"allow", required_argument, NULL, 3},
{"interfaces", required_argument, NULL, 'i'},
{"exclude", required_argument, NULL, 'X'},
{"no-act", no_argument, NULL, 'n'},
{"no-act-commands", no_argument, NULL, 10},
{"no-mappings", no_argument, NULL, 1},
{"no-scripts", no_argument, NULL, 4},
{"no-loopback", no_argument, NULL, 5},
{"force", no_argument, NULL, 2},
{"ignore-errors", no_argument, NULL, 7},
{"option", required_argument, NULL, 'o'},
{"list", no_argument, NULL, 'l'},
{"state", no_argument, NULL, 6},
{"read-environment", no_argument, NULL, 8},
{"state-dir", required_argument, NULL, 9},
{0, 0, 0, 0}
};
for (;;) {
int c = getopt_long(*argc, *argv, "X:s:i:o:hVvnal", long_opts, NULL);
if (c == EOF)
break;
switch (c) {
case 'a':
do_all = true;
break;
case 'h':
help(cmds);
break;
case 'i':
free(interfaces);
interfaces = strdup(optarg);
break;
case 'l':
if (!(cmds == iface_query))
usage();
list = true;
cmds = iface_list;
break;
case 'n':
if ((cmds == iface_list) || (cmds == iface_query))
usage();
no_act = true;
no_act_commands = true;
break;
case 'o':
{
char *name = strdup(optarg);
char *val = strchr(name, '=');
if (val == NULL)
errx(1, "error in --option \"%s\" -- no \"=\" character", optarg);
*val++ = '\0';
if (strcmp(name, "post-up") == 0)
strcpy(name, "up");
if (strcmp(name, "pre-down") == 0)
strcpy(name, "down");
set_variable(name, val, &option, &n_options, &max_options);
free(name);
break;
}
case 'v':
verbose = true;
break;
case 'V':
version();
break;
case 'X':
excludeints++;
excludeint = realloc(excludeint, excludeints * sizeof *excludeint);
if (excludeint == NULL)
err(1, "realloc");
excludeint[excludeints - 1] = strdup(optarg);
break;
case 1: /* --no-mappings */
run_mappings = false;
break;
case 2: /* --force */
if ((cmds == iface_list) || (cmds == iface_query))
usage();
force = true;
break;
case 3: /* --allow */
allow_class = strdup(optarg);
break;
case 4: /* --no-scripts */
run_scripts = false;
break;
case 5: /* --no-loopback */
no_loopback = true;
break;
case 6: /* --state */
if (cmds != iface_query)
usage();
state_query = true;
break;
case 7: /* --ignore-errors */
ignore_failures = true;
break;
case 8: /* --read-environment */
parse_environment_variables();
break;
case 9: /* --state-dir */
free(statedir);
free(statefile);
free(tmpstatefile);
free(lockfile);
statedir = NULL;
statefile = NULL;
tmpstatefile = NULL;
lockfile = NULL;
if(asprintf(&statedir, "%s", optarg) == -1 || !statedir)
err(1, "asprintf");
if(asprintf(&statefile, "%s/ifstate", optarg) == -1 || !statefile)
err(1, "asprintf");
if(asprintf(&tmpstatefile, "%s/.ifstate.tmp", optarg) == -1 || !tmpstatefile)
err(1, "asprintf");
if(asprintf(&lockfile, "%s/.ifstate.lock", optarg) == -1 || !lockfile)
err(1, "asprintf");
break;
case 10: /* --no-act-commands */
no_act_commands = true;
break;
default:
usage();
break;
}
}
*argc -= optind;
*argv += optind;
}
/* Report the state of interfaces. Return true if all reported interfaces are up, false otherwise */
static bool do_state(int n_target_ifaces, char *target_iface[]) {
char **up_ifaces;
int n_up_ifaces;
read_all_state(&up_ifaces, &n_up_ifaces);
bool all_up = true;
if (n_target_ifaces == 0) {
for (int i = 0; i < n_up_ifaces; i++)
puts(up_ifaces[i]);
} else {
for (int j = 0; j < n_target_ifaces; j++) {
size_t l = strlen(target_iface[j]);
bool found = false;
for (int i = 0; i < n_up_ifaces; i++) {
if (strncmp(target_iface[j], up_ifaces[i], l) == 0) {
if (up_ifaces[i][l] == '=') {
puts(up_ifaces[i]);
found = true;
break;
}
}
}
if (!found)
all_up = false;
}
}
return all_up;
}
/* Add string to a list if it is not a duplicate */
static void append_to_list_nodup(char ***list, int *n, char *entry) {
for (int i = 0; i < *n; i++)
if (!strcmp((*list)[i], entry))
return;
(*n)++;
*list = realloc(*list, *n * sizeof **list);
(*list)[*n - 1] = entry;
}
struct ifaddrs *ifap = NULL;
/* Check if an interface name is actually pattern */
static bool is_pattern(const char *name) {
if(!strchr(name, '/'))
return false;
#ifdef __gnu_hurd__
/* On GNU/Hurd, literal interface names start with /dev/. */
if(!strncmp(name, "/dev/", 5))
return false;
#endif
return true;
}
/* Expand matches in the list of interfaces to act upon */
static void expand_matches(int *argc, char ***argv) {
char **exp_iface = NULL;
int n_exp_ifaces = 0;
for (int i = 0; i < *argc; i++) {
// Interface names not containing a slash are taken over literally.
if (!is_pattern((*argv)[i])) {
append_to_list_nodup(&exp_iface, &n_exp_ifaces, (*argv)[i]);
continue;
}
// Format is [variable]/pattern[/options]
char *buf = strdupa((*argv)[i]);
char *variable = NULL;
char *pattern = NULL;
char *options = NULL;
int match_n = 0;
char *slash = strchr(buf, '/');
if (slash != buf)
variable = buf;
*slash++ = 0;
pattern = slash;
if (*pattern) {
slash = strchr(slash, '/');
if (slash) {
options = slash + 1;
*slash = 0;
}
}
char *logical_iface = strchr(options ? options : pattern, '=');
if (logical_iface)
*logical_iface++ = 0;
if (options) {
match_n = atoi(options);
}
int n = 0;
// Find all matching network interfaces
for (struct ifaddrs *ifa = ifap; ifa; ifa = ifa->ifa_next) {
if(ifa->ifa_flags == ~0U || !ifa->ifa_name)
continue;
if(variable) {
if(!variable_match(ifa->ifa_name, variable, pattern))
continue;
} else {
if(fnmatch(pattern, ifa->ifa_name, FNM_EXTMATCH))
continue;
}
n++;
// Match a single interface
if (match_n > 0 && match_n != n)
continue;
char *exp = NULL;
if (logical_iface) {
if(asprintf(&exp, "%s=%s", ifa->ifa_name, logical_iface) == -1 || !exp)
err(1, "asprintf");
} else {
exp = ifa->ifa_name;
}
append_to_list_nodup(&exp_iface, &n_exp_ifaces, exp);
}
}
*argv = exp_iface;
*argc = n_exp_ifaces;
}
static void get_interface_list(void) {
if (ifap) {
freeifaddrs(ifap);
ifap = NULL;
}
// Get the list of network interfaces
getifaddrs(&ifap);
// Mark duplicates
if (ifap) {
for (struct ifaddrs *ifa = ifap; ifa; ifa = ifa->ifa_next)
for (struct ifaddrs *ifb = ifa->ifa_next; ifb; ifb = ifb->ifa_next)
if(!strcmp(ifa->ifa_name, ifb->ifa_name))
ifa->ifa_flags = ~0U;
}
}
/* Process interfaces that need to be renamed */
static void do_rename(void) {
if (!rename_ints || !ifap)
return;
int renamed_ints = 0;
for (int i = 0; i < rename_ints; i++) {
char *logical = strchr(rename_int[i], '=');
if (!logical)
errx(1, "missing target name for interface %s", rename_int[i]);
*logical++ = 0;
if (!strcmp(rename_int[i], logical)) {
rename_int[i] = NULL;
continue;
}
bool found = false;
for (struct ifaddrs *ifa = ifap; ifa; ifa = ifa->ifa_next) {
if (ifa->ifa_flags == ~0U || !ifa->ifa_name)
continue;
if (!strcmp(ifa->ifa_name, rename_int[i])) {
found = true;
break;
}
}
if (!found) {
rename_int[i] = NULL;
continue;
}
struct variable option = {
.name = "newname",
.value = logical,
};
struct interface_defn ifd = {
.real_iface = rename_int[i],
.logical_iface = logical,
.n_options = 1,
.option = &option,
};
if (!addr_link.method[0].rename(&ifd, doit)) {
errx(1, "unable to rename %s to %s", rename_int[i], logical);
}
logical[-1] = '=';
renamed_ints++;
}
if (renamed_ints)
get_interface_list();
}
/* Check non-option arguments and build a list of interfaces to act upon */
static void select_interfaces(int argc, char *argv[]) {
if (argc > 0 && (do_all)) {
warnx("either use the --all option, or specify interface(s), but not both");
usage();
}
if (argc == 0 && !do_all && !list) {
warnx("no interface(s) specified");
usage();
}
if (do_all && (cmds == iface_query))
usage();
defn = read_interfaces(interfaces);
if (!defn)
errx(1, "couldn't read interfaces file \"%s\"", interfaces);
get_interface_list();
if (rename_ints)
expand_matches(&rename_ints, &rename_int);
if (cmds == iface_up)
do_rename();
if (do_all || (list && !argc)) {
if ((cmds == iface_list) || (cmds == iface_up)) {
allowup_defn *autos = find_allowup(defn, allow_class ? allow_class : "auto");
target_iface = autos ? autos->interfaces : NULL;
n_target_ifaces = autos ? autos->n_interfaces : 0;
expand_matches(&n_target_ifaces, &target_iface);
} else if (cmds == iface_down) {
read_all_state(&target_iface, &n_target_ifaces);
} else {
warnx("can't tell if interfaces are going up or down");
usage();
}
} else {
target_iface = argv;
n_target_ifaces = argc;
expand_matches(&n_target_ifaces, &target_iface);
}
/* Remap interfaces that would have been renamed */
for (int j = 0; j < rename_ints; j++) {
if (!rename_int[j])
continue;
int rename_len = strcspn(rename_int[j], "=");
for (int i = 0; i < n_target_ifaces; i++) {
int iface_len = strcspn(target_iface[i], "=");
if (iface_len != rename_len || strncmp(target_iface[i], rename_int[j], rename_len))
continue;
char *newtarget = NULL;
if (target_iface[i][iface_len]) {
if(asprintf(&newtarget, "%s=%s", rename_int[j] + rename_len + 1, target_iface[i] + iface_len + 1))
err(1, "asprintf");
} else {
newtarget = strdup(rename_int[j] + rename_len + 1);
}
target_iface[i] = newtarget;
}
}
/* Bring up VLAN interfaces in the same allow class */
if (n_target_ifaces && cmds == iface_up && allow_class) {
allowup_defn *allowups = find_allowup(defn, allow_class);
if (allowups) {
int n = n_target_ifaces;
for (int i = 0; i < n; i++) {
if (strchr(target_iface[i], '.'))
continue;
int len = strlen(target_iface[i]);
for (int j = 0; j < allowups->n_interfaces; j++) {
if (!strncmp(target_iface[i], allowups->interfaces[j], len) && allowups->interfaces[j][len] == '.')
append_to_list_nodup(&target_iface, &n_target_ifaces, allowups->interfaces[j]);
}
}
}
}
}
static interface_defn meta_iface = {
.next = NULL,
.real_iface = "--all",
.address_family = &addr_meta,
.max_options = 0,
.n_options = 0,
.option = NULL
};
/* Run pre hooks for the meta interface when calling ifup/down with --all */
static void do_pre_all(void) {
meta_iface.logical_iface = allow_class ? allow_class : "auto";
meta_iface.method = meta_iface.address_family->method;
bool okay = true;
if (cmds == iface_up)
okay = iface_preup(&meta_iface);
if (cmds == iface_down)
okay = iface_predown(&meta_iface);
if (!okay)
errx(1, "pre-%s script failed", cmds == iface_up ? "up" : "down");
}
/* Run post hooks for the meta interface when calling ifup/down with --all */
static void do_post_all(void) {
bool okay = true;
if (cmds == iface_up)
okay = iface_postup(&meta_iface);
if (cmds == iface_down)
okay = iface_postdown(&meta_iface);
if (!okay)
errx(1, "post-%s script failed", cmds == iface_up ? "up" : "down");
}
bool match_patterns(const char *string, int argc, char *argv[]) {
if (!argc || !argv || !string)
return false;
for (int i = 0; i < argc; i++)
if (fnmatch(argv[i], string, 0) == 0)
return true;
return false;
}
/* Check whether we should ignore the given interface */
static bool ignore_interface(const char *iface) {
/* If --allow is used, ignore interfaces that are not in the given class */
if (allow_class != NULL) {
allowup_defn *allowup = find_allowup(defn, allow_class);
if (allowup == NULL) // empty class
return true;
bool found = false;
char **interfaces = allowup->interfaces;
int n_interfaces = allowup->n_interfaces;
expand_matches(&n_interfaces, &interfaces);
for (int i = 0; i < n_interfaces; i++) {
char *logical = strchr(interfaces[i], '=');
if (logical)
*logical++ = 0;
if (strcmp(interfaces[i], iface) == 0) {
found = true;
break;
}
}
free(interfaces);
if (!found)
return true;
}
/* Ignore interfaces specified with --exclude */
if ((excludeints != 0 && match_patterns(iface, excludeints, excludeint)))
return true;
/* Ignore no-auto-down interfaces during ifdown -a */
if(do_all && cmds == iface_down) {
if (no_auto_down_ints && match_patterns(iface, no_auto_down_ints, no_auto_down_int))
return true;
}
return false;
}
static bool do_interface(const char *target_iface, char *parent_state) {
/* Exit early if we were interrupted */
if (interrupted)
return false;
/* Split into physical and logical interface */
char iface[80], liface[80];
strncpy(iface, target_iface, sizeof(iface));
iface[sizeof(iface) - 1] = '\0';
char *pch;
if ((pch = strchr(iface, '='))) {
*pch = '\0';
strncpy(liface, pch + 1, sizeof(liface));
liface[sizeof(liface) - 1] = '\0';
} else {
strncpy(liface, iface, sizeof(liface));
liface[sizeof(liface) - 1] = '\0';
}
/* Check if we really want to process this interface */
if(ignore_interface(iface))
return true;
/* Check if this interface exists */
bool found = false;
for (interface_defn *currif = defn->ifaces; currif; currif = currif->next) {
if (strcmp(liface, currif->logical_iface) == 0) {
found = true;
break;
}
}
if (!found) {
for (mapping_defn *currmap = defn->mappings; currmap; currmap = currmap->next) {
for (int i = 0; i < currmap->n_matches; i++) {
if (fnmatch(currmap->match[i], liface, 0) == 0) {
found = true;
break;
}
}
}
}
if (cmds != iface_up) {
char *filename = ifacestatefile(iface);
if (access(filename, R_OK) == 0)
found = true;
free(filename);
}
if (!found) {
if (!parent_state) {
warnx("unknown interface %s", liface);
return false;
} else {
return true;
}
}
/* Bail out if we are being called recursively on the same interface */
if (!parent_state && do_interface_lock) {
char envname[160];
snprintf(envname, sizeof envname, "IFUPDOWN_%s", iface);
sanitize_env_name(envname + 9);
char *envval = getenv(envname);
if(envval && is_locked(iface)) {
warnx("recursion detected for interface %s in %s phase", iface, envval);
return false;
}
}
/* Are we configuring a VLAN interface? If so, lock the parent interface first. */
char piface[80];
FILE *plock = NULL;
strncpy(piface, iface, sizeof piface);
pch = strchr(piface, '.');
if (pch && !parent_state && do_interface_lock) {
*pch = '\0';
char envname[160];
snprintf(envname, sizeof envname, "IFUPDOWN_%s", piface);
sanitize_env_name(envname + 9);
char *envval = getenv(envname);
bool needs_parent_lock = true;
/* Allow the parent interface to recursively bring up/down VLANs */
if (envval) {
if (cmds == iface_up && !strcmp(envval, "post-up"))
needs_parent_lock = false;
else if (cmds == iface_down && !strcmp(envval, "pre-down"))
needs_parent_lock = false;
}
if (needs_parent_lock) {
if(envval && is_locked(piface)) {
warnx("recursion detected for parent interface %s in %s phase", piface, envval);
return false;
}
char *parent_state = NULL;
plock = lock_interface(piface, &parent_state);
if (cmds == iface_up) {
/* And ensure it's up. */
if (!do_interface(piface, parent_state ? parent_state : "")) {
warnx("could not bring up parent interface %s", piface);
return false;
}
}
free(parent_state);
}
}
/* Start by locking this interface */
bool success = false;
FILE *lock = NULL;
char *current_state = NULL;
if ((!parent_state || !*parent_state) && do_interface_lock)
lock = lock_interface(iface, ¤t_state);
else
current_state = parent_state;
/* Are we the parent of one or more VLAN interfaces? */
if (!pch && !parent_state && cmds == iface_down) {
size_t namelen = strlen(iface);
for (struct interface_defn *ifd = defn->ifaces; ifd; ifd = ifd->next) {
if (strncmp(iface, ifd->logical_iface, namelen) || ifd->logical_iface[namelen] != '.')
continue;
do_interface(ifd->logical_iface, "");
}
}
/* If we are not forcing the command, then exit with success if it is a no-op */
if (!force) {
if (cmds == iface_up) {
if (current_state != NULL) {
if (!parent_state && !do_all)
warnx("interface %s already configured", iface);
success = true;
goto end;
}
} else if (cmds == iface_down) {
if (current_state == NULL) {
if (!parent_state && !do_all)
warnx("interface %s not configured", iface);
success = true;
goto end;
}
strncpy(liface, current_state, 80);
liface[79] = 0;
} else if (cmds == iface_query) {
if (current_state != NULL) {
strncpy(liface, current_state, 80);
liface[79] = 0;
run_mappings = false;
}
} else {
assert(cmds == iface_list ||cmds == iface_query);
}
}
/* Run mapping scripts if necessary */
bool have_mapping = false;
if (((cmds == iface_up) && run_mappings) || (cmds == iface_query)) {
for (mapping_defn *currmap = defn->mappings; currmap; currmap = currmap->next) {
for (int i = 0; i < currmap->n_matches; i++) {
if (fnmatch(currmap->match[i], liface, 0) != 0)
continue;
if ((cmds == iface_query) && !run_mappings) {
if (verbose)
warnx("not running mapping scripts for %s", liface);
have_mapping = true;
break;
}
if (verbose)
warnx("running mapping script %s on %s", currmap->script, liface);
if(!run_mapping(iface, liface, sizeof(liface), currmap))
goto end;
break;
}
}
}
bool okay = false;
bool failed = false;
/* Update the state file already? */
if (cmds == iface_up) {
update_state(iface, liface, lock);
} else if (cmds == iface_down) {
update_state(iface, NULL, lock);
} else {
assert(cmds == iface_list ||cmds == iface_query);
}
/* Handle ifquery --list */
if (cmds == iface_list) {
for (interface_defn *currif = defn->ifaces; currif; currif = currif->next)
if (strcmp(liface, currif->logical_iface) == 0)
okay = true;
if (!okay) {
mapping_defn *currmap;
for (currmap = defn->mappings; currmap; currmap = currmap->next) {
for (int i = 0; i < currmap->n_matches; i++) {
if (fnmatch(currmap->match[i], liface, 0) != 0)
continue;
okay = true;
break;
}
}
}
if (okay) {
interface_defn *currif = defn->ifaces;
currif->real_iface = iface;
cmds(currif);
currif->real_iface = NULL;
success = true;
}
goto end;
}
/* Run the desired command for all matching logical interfaces */
for (interface_defn *currif = defn->ifaces; currif; currif = currif->next) {
if (strcmp(liface, currif->logical_iface) == 0) {
/* Bring the link up if necessary, but only once for each physical interface */
if (!okay && (cmds == iface_up)) {
interface_defn link = {
.real_iface = iface,
.logical_iface = liface,
.max_options = 0,
.address_family = &addr_link,
.method = &(addr_link.method[0]),
.n_options = 0,
.option = NULL
};
convert_variables(link.method->conversions, &link);
for (option_default *o = addr_link.method[0].defaults; o && o->option && o->value; o++) {
for (int j = 0; j < currif->n_options; j++) {
if (strcmp(currif->option[j].name, o->option) == 0) {
set_variable(o->option, currif->option[j].value, &link.option, &link.n_options, &link.max_options);
break;
}
}
}
if (!link.method->up(&link, doit))
break;
for (int i = 0; i < link.n_options; i++) {
free(link.option[i].name);
free(link.option[i].value);
}
if (link.option)
free(link.option);
}
okay = true;
for (option_default *o = currif->method->defaults; o && o->option && o->value; o++) {
bool found = false;
for (int j = 0; j < currif->n_options; j++) {
if (strcmp(currif->option[j].name, o->option) == 0) {
found = true;
break;
}
}
if (!found)
set_variable(o->option, o->value, &currif->option, &currif->n_options, &currif->max_options);
}
for (int i = 0; i < n_options; i++) {
if (option[i].value[0] == '\0') {
if (strcmp(option[i].name, "pre-up") != 0 && strcmp(option[i].name, "up") != 0 && strcmp(option[i].name, "down") != 0 && strcmp(option[i].name, "post-down") != 0) {
int j;
for (j = 0; j < currif->n_options; j++) {
if (strcmp(currif->option[j].name, option[i].name) == 0) {
currif->n_options--;
break;
}
}
for (; j < currif->n_options; j++) {
option[j].name = option[j + 1].name;
option[j].value = option[j + 1].value;
}
} else {
/* do nothing */
}
} else {
set_variable(option[i].name, option[i].value, &currif->option, &currif->n_options, &currif->max_options);
}
}
currif->real_iface = iface;
convert_variables(currif->method->conversions, currif);
if (verbose)
warnx("%s interface %s=%s (%s)", (cmds == iface_query) ? "querying" : "configuring", iface, liface, currif->address_family->name);
char *pidfilename = make_pidfile_name(argv0, currif);
if (!no_act) {
FILE *pidfile = fopen(pidfilename, "w");
if (pidfile) {
fprintf(pidfile, "%d", getpid());
fclose(pidfile);
} else {
warn("failed to open pid file %s", pidfilename);
}
}
switch (cmds(currif)) {
case -1:
warnx("missing required configuration variables for interface %s/%s", liface, currif->address_family->name);
failed = true;
break;
case 0:
failed = true;
break;
/* not entirely successful */
case 1:
failed = false;
break;
/* successful */
default:
warnx("unexpected value when configuring interface %s/%s; considering it failed", liface, currif->address_family->name);
failed = true;
/* what happened here? */
}
if (!no_act)
unlink(pidfilename);
free(pidfilename);
currif->real_iface = NULL;
if (failed)
break;
/* Otherwise keep going: this interface may have match with other address families */
}
}
/* Bring the link down if necessary */
if (okay && (cmds == iface_down)) {
interface_defn link = {
.real_iface = iface,
.logical_iface = liface,
.max_options = 0,
.address_family = &addr_link,
.method = &(addr_link.method[0]),
.n_options = 0,
.option = NULL
};
convert_variables(link.method->conversions, &link);
if (!link.method->down(&link, doit))
goto end;
if (link.option)
free(link.option);
}
if (!okay && (cmds == iface_query)) {
if (!run_mappings)
if (have_mapping)
okay = true;
if (!okay) {
warnx("unknown interface %s", iface);
goto end;
}
}
/* Update the state */
if (!okay && !force) {
warnx("ignoring unknown interface %s=%s", iface, liface);
update_state(iface, NULL, lock);
} else {
if (cmds == iface_up) {
if ((current_state == NULL) || (no_act)) {
if (failed == true) {
warnx("failed to bring up %s", liface);
update_state(iface, NULL, lock);
goto end;
} else {
update_state(iface, liface, lock);
}
} else {
update_state(iface, liface, lock);
}
} else if (cmds == iface_down) {
update_state(iface, NULL, lock);
} else {
assert(cmds == iface_list ||cmds == iface_query);
}
}
success = true;
end:
if(lock)
fclose(lock);
if(plock)
fclose(plock);
return success;
}
int main(int argc, char *argv[]) {
argv0 = strrchr(argv[0], '/');
if(argv0)
argv0++;
else
argv0 = argv[0];
check_stdio();
cmds = determine_command();
parse_options(&argc, &argv);
if (!statedir) {
statedir = strdup(RUN_DIR);
statefile = strdup(RUN_DIR "ifstate");
tmpstatefile = strdup(RUN_DIR ".ifstate.tmp");
lockfile = strdup(RUN_DIR ".ifstate.lock");
}
if (!interfaces)
interfaces = strdup("/etc/network/interfaces");
if (!interfaces || !statedir || !statefile || !tmpstatefile || !lockfile)
err(1, "strdup");
mkdir(statedir, 0755);
bool success = true;
if (state_query) {
success = do_state(argc, argv);
goto finish;
}
signal(SIGINT, signal_handler);
signal(SIGTERM, signal_handler);
signal(SIGHUP, signal_handler);
select_interfaces(argc, argv);
if (do_all)
do_pre_all();
for (int i = 0; i < n_target_ifaces; i++)
success &= do_interface(target_iface[i], NULL);
if (do_all)
do_post_all();
finish:
free(interfaces);
free(lockfile);
free(tmpstatefile);
free(statefile);
free(statedir);
return success ? 0 : 1;
}