/* $OpenBSD: ifstated.c,v 1.65 2020/12/30 18:40:44 benno Exp $ */ /* * Copyright (c) 2004 Marco Pfatschbacher * Copyright (c) 2004 Ryan McBride * * Permission to use, copy, modify, and distribute this software for any * purpose with or without fee is hereby granted, provided that the above * copyright notice and this permission notice appear in all copies. * * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ /* * ifstated listens to link_state transitions on interfaces * and executes predefined commands. */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "ifstated.h" #include "log.h" struct ifsd_config *conf, *newconf; int opts; int opt_inhibit; char *configfile = "/etc/ifstated.conf"; struct event rt_msg_ev, sighup_ev, startup_ev, sigchld_ev; void startup_handler(int, short, void *); void sighup_handler(int, short, void *); int load_config(void); void sigchld_handler(int, short, void *); void rt_msg_handler(int, short, void *); void external_handler(int, short, void *); void external_exec(struct ifsd_external *, int); void check_external_status(struct ifsd_state *); void check_ifdeparture(void); void external_evtimer_setup(struct ifsd_state *, int); void scan_ifstate(const char *, int, int); int scan_ifstate_single(const char *, int, struct ifsd_state *); void fetch_ifstate(int); __dead void usage(void); void adjust_expressions(struct ifsd_expression_list *, int); void adjust_external_expressions(struct ifsd_state *); void eval_state(struct ifsd_state *); int state_change(void); void do_action(struct ifsd_action *); void remove_action(struct ifsd_action *, struct ifsd_state *); void remove_expression(struct ifsd_expression *, struct ifsd_state *); __dead void usage(void) { extern char *__progname; fprintf(stderr, "usage: %s [-dhinv] [-D macro=value] [-f file]\n", __progname); exit(1); } int main(int argc, char *argv[]) { struct timeval tv; int ch, rt_fd; int debug = 0; unsigned int rtfilter; log_init(1, LOG_DAEMON); /* log to stderr until daemonized */ log_setverbose(1); while ((ch = getopt(argc, argv, "dD:f:hniv")) != -1) { switch (ch) { case 'd': debug = 1; break; case 'D': if (cmdline_symset(optarg) < 0) fatalx("could not parse macro definition %s", optarg); break; case 'f': configfile = optarg; break; case 'h': usage(); break; case 'n': opts |= IFSD_OPT_NOACTION; break; case 'i': opt_inhibit = 1; break; case 'v': if (opts & IFSD_OPT_VERBOSE) opts |= IFSD_OPT_VERBOSE2; opts |= IFSD_OPT_VERBOSE; break; default: usage(); } } argc -= optind; argv += optind; if (argc > 0) usage(); if (opts & IFSD_OPT_NOACTION) { if ((newconf = parse_config(configfile, opts)) == NULL) exit(1); fprintf(stderr, "configuration OK\n"); exit(0); } if (!debug) daemon(1, 0); event_init(); log_init(debug, LOG_DAEMON); log_setverbose(opts & IFSD_OPT_VERBOSE); if ((rt_fd = socket(AF_ROUTE, SOCK_RAW, 0)) == -1) fatal("no routing socket"); rtfilter = ROUTE_FILTER(RTM_IFINFO) | ROUTE_FILTER(RTM_IFANNOUNCE); if (setsockopt(rt_fd, AF_ROUTE, ROUTE_MSGFILTER, &rtfilter, sizeof(rtfilter)) == -1) /* not fatal */ log_warn("%s: setsockopt msgfilter", __func__); rtfilter = RTABLE_ANY; if (setsockopt(rt_fd, AF_ROUTE, ROUTE_TABLEFILTER, &rtfilter, sizeof(rtfilter)) == -1) /* not fatal */ log_warn("%s: setsockopt tablefilter", __func__); if (unveil(configfile, "r") == -1) fatal("unveil"); if (unveil(_PATH_BSHELL, "x") == -1) fatal("unveil"); if (pledge("stdio rpath route proc exec", NULL) == -1) fatal("pledge"); signal_set(&sigchld_ev, SIGCHLD, sigchld_handler, NULL); signal_add(&sigchld_ev, NULL); /* Loading the config needs to happen in the event loop */ timerclear(&tv); evtimer_set(&startup_ev, startup_handler, (void *)(long)rt_fd); evtimer_add(&startup_ev, &tv); event_loop(0); exit(0); } void startup_handler(int fd, short event, void *arg) { int rfd = (int)(long)arg; if (load_config() != 0) { log_warnx("unable to load config"); exit(1); } event_set(&rt_msg_ev, rfd, EV_READ|EV_PERSIST, rt_msg_handler, NULL); event_add(&rt_msg_ev, NULL); signal_set(&sighup_ev, SIGHUP, sighup_handler, NULL); signal_add(&sighup_ev, NULL); log_info("started"); } void sighup_handler(int fd, short event, void *arg) { log_info("reloading config"); if (load_config() != 0) log_warnx("unable to reload config"); } int load_config(void) { if ((newconf = parse_config(configfile, opts)) == NULL) return (-1); if (conf != NULL) clear_config(conf); conf = newconf; conf->initstate.entered = time(NULL); fetch_ifstate(0); external_evtimer_setup(&conf->initstate, IFSD_EVTIMER_ADD); adjust_external_expressions(&conf->initstate); eval_state(&conf->initstate); if (conf->curstate != NULL) { log_info("initial state: %s", conf->curstate->name); conf->curstate->entered = time(NULL); conf->nextstate = conf->curstate; conf->curstate = NULL; while (state_change()) { do_action(conf->curstate->init); do_action(conf->curstate->body); } } return (0); } void rt_msg_handler(int fd, short event, void *arg) { char msg[2048]; struct rt_msghdr *rtm = (struct rt_msghdr *)&msg; struct if_msghdr ifm; struct if_announcemsghdr ifan; char ifnamebuf[IFNAMSIZ]; char *ifname; ssize_t len; if ((len = read(fd, msg, sizeof(msg))) == -1) { if (errno == EAGAIN || errno == EINTR) return; fatal("%s: routing socket read error", __func__); } if (len == 0) fatal("%s: routing socket closed", __func__); if (rtm->rtm_version != RTM_VERSION) return; switch (rtm->rtm_type) { case RTM_IFINFO: memcpy(&ifm, rtm, sizeof(ifm)); ifname = if_indextoname(ifm.ifm_index, ifnamebuf); /* ifname is NULL on interface departure */ if (ifname != NULL) scan_ifstate(ifname, ifm.ifm_data.ifi_link_state, 1); break; case RTM_IFANNOUNCE: memcpy(&ifan, rtm, sizeof(ifan)); switch (ifan.ifan_what) { case IFAN_DEPARTURE: log_warnx("interface %s departed", ifan.ifan_name); check_ifdeparture(); break; case IFAN_ARRIVAL: log_warnx("interface %s arrived", ifan.ifan_name); fetch_ifstate(1); break; } break; case RTM_DESYNC: /* we lost some routing messages so rescan interfaces */ check_ifdeparture(); fetch_ifstate(1); break; } return; } void sigchld_handler(int fd, short event, void *arg) { check_external_status(&conf->initstate); if (conf->curstate != NULL) check_external_status(conf->curstate); } void external_handler(int fd, short event, void *arg) { struct ifsd_external *external = (struct ifsd_external *)arg; struct timeval tv; /* re-schedule */ timerclear(&tv); tv.tv_sec = external->frequency; evtimer_set(&external->ev, external_handler, external); evtimer_add(&external->ev, &tv); /* execute */ external_exec(external, 1); } void external_exec(struct ifsd_external *external, int async) { char *argp[] = {"sh", "-c", NULL, NULL}; pid_t pid; int s; if (external->pid > 0) { log_debug("previous command %s [%d] still running, killing it", external->command, external->pid); kill(external->pid, SIGKILL); waitpid(external->pid, &s, 0); external->pid = 0; } argp[2] = external->command; log_debug("running %s", external->command); pid = fork(); if (pid == -1) { log_warn("fork error"); } else if (pid == 0) { execv(_PATH_BSHELL, argp); _exit(1); /* NOTREACHED */ } else { external->pid = pid; } if (!async) { waitpid(external->pid, &s, 0); external->pid = 0; if (WIFEXITED(s)) external->prevstatus = WEXITSTATUS(s); } } void adjust_external_expressions(struct ifsd_state *state) { struct ifsd_external *external; struct ifsd_expression_list expressions; TAILQ_INIT(&expressions); TAILQ_FOREACH(external, &state->external_tests, entries) { struct ifsd_expression *expression; if (external->prevstatus == -1) continue; TAILQ_FOREACH(expression, &external->expressions, entries) { TAILQ_INSERT_TAIL(&expressions, expression, eval); expression->truth = !external->prevstatus; } adjust_expressions(&expressions, conf->maxdepth); } } void check_external_status(struct ifsd_state *state) { struct ifsd_external *external, *end = NULL; int status, s, changed = 0; /* Do this manually; change ordering so the oldest is first */ external = TAILQ_FIRST(&state->external_tests); while (external != NULL && external != end) { struct ifsd_external *newexternal; newexternal = TAILQ_NEXT(external, entries); if (external->pid <= 0) goto loop; if (wait4(external->pid, &s, WNOHANG, NULL) == 0) goto loop; external->pid = 0; if (end == NULL) end = external; if (WIFEXITED(s)) status = WEXITSTATUS(s); else { log_warnx("%s exited abnormally", external->command); goto loop; } if (external->prevstatus != status && (external->prevstatus != -1 || !opt_inhibit)) { changed = 1; external->prevstatus = status; } external->lastexec = time(NULL); TAILQ_REMOVE(&state->external_tests, external, entries); TAILQ_INSERT_TAIL(&state->external_tests, external, entries); loop: external = newexternal; } if (changed) { adjust_external_expressions(state); eval_state(state); } } void external_evtimer_setup(struct ifsd_state *state, int action) { struct ifsd_external *external; int s; if (state != NULL) { switch (action) { case IFSD_EVTIMER_ADD: TAILQ_FOREACH(external, &state->external_tests, entries) { struct timeval tv; /* run it once right away */ external_exec(external, 0); /* schedule it for later */ timerclear(&tv); tv.tv_sec = external->frequency; evtimer_set(&external->ev, external_handler, external); evtimer_add(&external->ev, &tv); } break; case IFSD_EVTIMER_DEL: TAILQ_FOREACH(external, &state->external_tests, entries) { if (external->pid > 0) { kill(external->pid, SIGKILL); waitpid(external->pid, &s, 0); external->pid = 0; } evtimer_del(&external->ev); } break; } } } #define LINK_STATE_IS_DOWN(_s) (!LINK_STATE_IS_UP((_s))) int scan_ifstate_single(const char *ifname, int s, struct ifsd_state *state) { struct ifsd_ifstate *ifstate; struct ifsd_expression_list expressions; int changed = 0; TAILQ_INIT(&expressions); TAILQ_FOREACH(ifstate, &state->interface_states, entries) { if (strcmp(ifstate->ifname, ifname) == 0) { if (ifstate->prevstate != s && (ifstate->prevstate != -1 || !opt_inhibit)) { struct ifsd_expression *expression; int truth; truth = (ifstate->ifstate == IFSD_LINKUNKNOWN && s == LINK_STATE_UNKNOWN) || (ifstate->ifstate == IFSD_LINKDOWN && LINK_STATE_IS_DOWN(s)) || (ifstate->ifstate == IFSD_LINKUP && LINK_STATE_IS_UP(s)); TAILQ_FOREACH(expression, &ifstate->expressions, entries) { expression->truth = truth; TAILQ_INSERT_TAIL(&expressions, expression, eval); changed = 1; } ifstate->prevstate = s; } } } if (changed) adjust_expressions(&expressions, conf->maxdepth); return (changed); } void scan_ifstate(const char *ifname, int s, int do_eval) { struct ifsd_state *state; int cur_eval = 0; if (scan_ifstate_single(ifname, s, &conf->initstate) && do_eval) eval_state(&conf->initstate); TAILQ_FOREACH(state, &conf->states, entries) { if (scan_ifstate_single(ifname, s, state) && (do_eval && state == conf->curstate)) cur_eval = 1; } /* execute actions _after_ all expressions have been adjusted */ if (cur_eval) eval_state(conf->curstate); } /* * Do a bottom-up ajustment of the expression tree's truth value, * level-by-level to ensure that each expression's subexpressions have been * evaluated. */ void adjust_expressions(struct ifsd_expression_list *expressions, int depth) { struct ifsd_expression_list nexpressions; struct ifsd_expression *expression; TAILQ_INIT(&nexpressions); while ((expression = TAILQ_FIRST(expressions)) != NULL) { TAILQ_REMOVE(expressions, expression, eval); if (expression->depth == depth) { struct ifsd_expression *te; switch (expression->type) { case IFSD_OPER_AND: expression->truth = expression->left->truth && expression->right->truth; break; case IFSD_OPER_OR: expression->truth = expression->left->truth || expression->right->truth; break; case IFSD_OPER_NOT: expression->truth = !expression->right->truth; break; default: break; } if (expression->parent != NULL) { if (TAILQ_EMPTY(&nexpressions)) te = NULL; TAILQ_FOREACH(te, &nexpressions, eval) if (expression->parent == te) break; if (te == NULL) TAILQ_INSERT_TAIL(&nexpressions, expression->parent, eval); } } else TAILQ_INSERT_TAIL(&nexpressions, expression, eval); } if (depth > 0) adjust_expressions(&nexpressions, depth - 1); } void eval_state(struct ifsd_state *state) { struct ifsd_external *external; external = TAILQ_FIRST(&state->external_tests); if (external == NULL || external->lastexec >= state->entered || external->lastexec == 0) { do_action(state->body); while (state_change()) { do_action(conf->curstate->init); do_action(conf->curstate->body); } } } int state_change(void) { if (conf->nextstate != NULL && conf->curstate != conf->nextstate) { log_info("changing state to %s", conf->nextstate->name); if (conf->curstate != NULL) { evtimer_del(&conf->curstate->ev); external_evtimer_setup(conf->curstate, IFSD_EVTIMER_DEL); } conf->curstate = conf->nextstate; conf->nextstate = NULL; conf->curstate->entered = time(NULL); external_evtimer_setup(conf->curstate, IFSD_EVTIMER_ADD); adjust_external_expressions(conf->curstate); return (1); } return (0); } /* * Run recursively through the tree of actions. */ void do_action(struct ifsd_action *action) { struct ifsd_action *subaction; switch (action->type) { case IFSD_ACTION_COMMAND: log_debug("running %s", action->act.command); system(action->act.command); break; case IFSD_ACTION_CHANGESTATE: conf->nextstate = action->act.nextstate; break; case IFSD_ACTION_CONDITION: if ((action->act.c.expression != NULL && action->act.c.expression->truth) || action->act.c.expression == NULL) { TAILQ_FOREACH(subaction, &action->act.c.actions, entries) do_action(subaction); } break; default: log_debug("%s: unknown action %d", __func__, action->type); break; } } /* * Fetch the current link states. */ void fetch_ifstate(int do_eval) { struct ifaddrs *ifap, *ifa; if (getifaddrs(&ifap) != 0) fatal("getifaddrs"); for (ifa = ifap; ifa; ifa = ifa->ifa_next) { if (ifa->ifa_addr != NULL && ifa->ifa_addr->sa_family == AF_LINK) { struct if_data *ifdata = ifa->ifa_data; scan_ifstate(ifa->ifa_name, ifdata->ifi_link_state, do_eval); } } freeifaddrs(ifap); } void check_ifdeparture(void) { struct ifsd_state *state; struct ifsd_ifstate *ifstate; TAILQ_FOREACH(state, &conf->states, entries) { TAILQ_FOREACH(ifstate, &state->interface_states, entries) { if (if_nametoindex(ifstate->ifname) == 0) scan_ifstate(ifstate->ifname, LINK_STATE_DOWN, 1); } } } void clear_config(struct ifsd_config *oconf) { struct ifsd_state *state; external_evtimer_setup(&conf->initstate, IFSD_EVTIMER_DEL); if (conf != NULL && conf->curstate != NULL) external_evtimer_setup(conf->curstate, IFSD_EVTIMER_DEL); while ((state = TAILQ_FIRST(&oconf->states)) != NULL) { TAILQ_REMOVE(&oconf->states, state, entries); remove_action(state->init, state); remove_action(state->body, state); free(state->name); free(state); } remove_action(oconf->initstate.init, &oconf->initstate); remove_action(oconf->initstate.body, &oconf->initstate); free(oconf); } void remove_action(struct ifsd_action *action, struct ifsd_state *state) { struct ifsd_action *subaction; if (action == NULL || state == NULL) return; switch (action->type) { case IFSD_ACTION_COMMAND: free(action->act.command); break; case IFSD_ACTION_CHANGESTATE: break; case IFSD_ACTION_CONDITION: if (action->act.c.expression != NULL) remove_expression(action->act.c.expression, state); while ((subaction = TAILQ_FIRST(&action->act.c.actions)) != NULL) { TAILQ_REMOVE(&action->act.c.actions, subaction, entries); remove_action(subaction, state); } } free(action); } void remove_expression(struct ifsd_expression *expression, struct ifsd_state *state) { switch (expression->type) { case IFSD_OPER_IFSTATE: TAILQ_REMOVE(&expression->u.ifstate->expressions, expression, entries); if (--expression->u.ifstate->refcount == 0) { TAILQ_REMOVE(&state->interface_states, expression->u.ifstate, entries); free(expression->u.ifstate); } break; case IFSD_OPER_EXTERNAL: TAILQ_REMOVE(&expression->u.external->expressions, expression, entries); if (--expression->u.external->refcount == 0) { TAILQ_REMOVE(&state->external_tests, expression->u.external, entries); free(expression->u.external->command); event_del(&expression->u.external->ev); free(expression->u.external); } break; default: if (expression->left != NULL) remove_expression(expression->left, state); if (expression->right != NULL) remove_expression(expression->right, state); break; } free(expression); }