Commits

jsgf committed c22334b

Further updates to deal with initial state of each
controlled interface.

Comments (0)

Files changed (5)

docs/StateMachine.sxd

Binary file added.
 #define INFOHASHSZ	16	/* must be a power of 2 */
 static struct if_info *if_info[INFOHASHSZ];
 
-static const char *statename(enum ifstate s)
+static const char *
+statename(enum ifstate s)
 {
     switch(s) {
 #define S(x)	case ST_##x: return #x
     }
 }
 
-static const char *flags_str(char *buf, unsigned int fl)
+static const char *
+flags_str(char *buf, unsigned int fl)
 {
     static struct flag {
 	const char *name;
     return buf;
 }
 
-/* if_info state machine transitions caused by interface flag changes */
-void ifsm_flagchange(struct if_info *info, unsigned int newflags)
+void
+for_each_iface(int (*func)(struct if_info *))
+{
+    for(int i = 0; i < INFOHASHSZ; i++) {
+	for(struct if_info *info = if_info[i]; info != NULL; info = info->next) {
+	    if ((*func)(info))
+		return;
+	}
+    }
+}
+
+/* Reevaluate the state machine based on the current state and flag settings */
+void 
+ifsm_flagpoll(struct if_info *info)
+{
+    enum ifstate state = info->state;
+
+    switch(info->state) {
+    case ST_DOWN:
+	if ((info->flags & (IFF_UP|IFF_RUNNING)) == 0)
+	    break;
+	/* FALLTHROUGH */
+    case ST_INACTIVE:
+	if (!(info->flags & IFF_UP)) {
+	    assert(info->worker == -1);
+	    info->worker = run_netplug_bg(info->name, "probe");
+	    info->state = ST_PROBING;
+	}
+	if (info->flags & IFF_RUNNING) {
+	    assert(info->worker == -1);
+	    info->worker = run_netplug_bg(info->name, "in");
+	    info->state = ST_INNING;
+	}
+	break;
+
+    case ST_PROBING:
+    case ST_PROBING_UP:
+    case ST_WAIT_IN:
+    case ST_DOWNANDOUT:
+	break;
+
+    case ST_INNING:
+	if (!(info->flags & IFF_RUNNING))
+	    info->state = ST_WAIT_IN;
+	break;
+
+    case ST_ACTIVE:
+	if (!(info->flags & IFF_RUNNING)) {
+	    assert(info->worker == -1);
+	    info->worker = run_netplug_bg(info->name, "out");
+	    info->state = ST_OUTING;
+	}
+	break;
+
+    case ST_OUTING:
+	if (!(info->flags & IFF_UP))
+	    info->state = ST_DOWNANDOUT;
+
+    case ST_INSANE:
+	break;
+    }
+
+    if (info->state != state)
+	do_log(LOG_DEBUG, "ifsm_flagpoll %s: moved from state %s to %s", 
+	       info->name, statename(state), statename(info->state));
+}
+
+/* if_info state machine transitions caused by interface flag changes (edge triggered) */
+void
+ifsm_flagchange(struct if_info *info, unsigned int newflags)
 {
     unsigned int changed = (info->flags ^ newflags) & (IFF_RUNNING | IFF_UP);
 
 	}
     }
 
-    /* XXX hack - kick start things by accouting for the initial
-       probe */
-    if (info->state == ST_DOWN && (newflags & IFF_UP))
-	info->state = ST_INACTIVE;
-
     if (changed & IFF_RUNNING) {
 	switch(info->state) {
 	case ST_INACTIVE:
 	}
     }
 
-    do_log(LOG_INFO, "%s: moved to state %s; worker %d", 
+    do_log(LOG_DEBUG, "%s: moved to state %s; worker %d", 
 	   info->name, statename(info->state), info->worker);
     info->flags = newflags;
     info->lastchange = time(0);
     struct if_info *info;
     assert(WIFEXITED(exitstatus) || WIFSIGNALED(exitstatus));
 
-    for(int i = 0; i < INFOHASHSZ; i++) {
-	for(info = if_info[i]; info != NULL; info = info->next)
-	    if (info->worker == pid)
-		break;
-	if (info != NULL)
-	    break;
+    int find_pid(struct if_info *i) {
+	if (i->worker == pid) {
+	    info = i;
+	    return 1;
+	}
+	return 0;
     }
 
+    info = NULL;
+    for_each_iface(find_pid);
+
     if (info == NULL) {
 	do_log(LOG_INFO, "Unexpected child %d exited with status %d",
 	       pid, exitstatus);
 	return;
     }
 
-    do_log(LOG_INFO, "%s: state %s  pid %d exited status %d",
+    do_log(LOG_INFO, "%s: state %s pid %d exited status %d",
 	   info->name, statename(info->state), pid, exitstatus);
 
     info->worker = -1;
 
     switch(info->state) {
     case ST_PROBING:
-	/* XXX should we expect interface to be up by now?  No, not
-	   necessarily: netlink is not synchronized with process
-	   exit. */
-	if (exitok)
-	    info->state = ST_INACTIVE;
-	else {
-	    info->state = ST_DOWN;
+	/* If we're still in PROBING state, then it means that the
+	   interface flags have not come up, even though the script
+	   finished.  Go back to DOWN and wait for the UP flag
+	   setting. */
+	if (!exitok)
             do_log(LOG_WARNING, "Could not bring %s back up", info->name);
-	}
+
+	info->state = ST_DOWN;
 	break;
 
     case ST_PROBING_UP:
 	exit(1);
     }
 
-    do_log(LOG_INFO, "%s: moved to state %s", info->name, statename(info->state));
+    do_log(LOG_DEBUG, "%s: moved to state %s", info->name, statename(info->state));
 }
 
 void
     va_list ap;
     va_start(ap, fmt);
 
+    if (pri == LOG_DEBUG && !debug)
+	return;
+
     if (use_syslog) {
         vsyslog(pri, fmt, ap);
     } else {
 
 #define _GNU_SOURCE
 #include <net/if.h>
+#include <netinet/in.h>
 #include <stdio.h>
 #include <stdlib.h>
 #include <syslog.h>
 #include <assert.h>
 #include <wait.h>
 #include <errno.h>
+#include <string.h>
+#include <sys/ioctl.h>
 
 #include "netplug.h"
 
     char *name = RTA_DATA(attrs[IFLA_IFNAME]);
 
     if (!if_match(name)) {
-	char *name = RTA_DATA(attrs[IFLA_IFNAME]);
-
-	if (!if_match(name)) {
-	    do_log(LOG_INFO, "%s: ignoring event", name);
-	    return 0;
-	}
+	do_log(LOG_INFO, "%s: ignoring event", name);
+	return 0;
     }
 
     struct if_info *i = if_info_get_interface(hdr, attrs);
 	write(child_handler_pipe[1], &ce, sizeof(ce));
 }
 
+/* Poll the existing interface state, so we can catch any state
+   changes for which we may not have neen a netlink message. */
+static void
+poll_interfaces(void)
+{
+    static int sockfd = -1;
+
+    if (sockfd == -1) {
+	sockfd = socket(PF_INET, SOCK_DGRAM, IPPROTO_IP);
+	if (sockfd == -1) {
+	    do_log(LOG_ERR, "can't create interface socket: %m");
+	    exit(1);
+	}
+    }
+
+    int pollflags(struct if_info *info) {
+	struct ifreq ifr;
+	
+	if (!if_match(info->name))
+	    return 0;
+
+	memcpy(ifr.ifr_name, info->name, sizeof(ifr.ifr_name));
+	if (ioctl(sockfd, SIOCGIFFLAGS, &ifr) < 0)
+	    do_log(LOG_ERR, "%s: can't get flags: %m", info->name);
+	else {
+	    ifsm_flagchange(info, ifr.ifr_flags);
+	    ifsm_flagpoll(info);
+	}
+
+	return 0;
+    }
+
+    for_each_iface(pollflags);
+}
+
+int debug = 0;
+
 int
 main(int argc, char *argv[])
 {
     int probe = 1;
     int c;
 
-    while ((c = getopt(argc, argv, "FPc:hi:p:")) != EOF) {
+    while ((c = getopt(argc, argv, "DFPc:hi:p:")) != EOF) {
         switch (c) {
+	case 'D':
+	    debug = 1;
+	    break;
         case 'F':
             foreground = 1;
             break;
 	exit(1);
     }
 
+    if (fcntl(child_handler_pipe[0], F_SETFL, O_NONBLOCK) == -1) {
+	do_log(LOG_ERR, "can't set pipe non-blocking: %m");
+	exit(1);
+    }
+    
     struct sigaction sa;
     sa.sa_sigaction = child_handler;
     sa.sa_flags = SA_RESTART | SA_SIGINFO;
 	exit(1);
     }
 
-    if (fcntl(child_handler_pipe[0], F_SETFL, O_NONBLOCK) == -1) {
-	do_log(LOG_ERR, "can't set pipe non-blocking: %m");
-	exit(1);
-    }
-    
     struct pollfd fds[] = {
 	{ fd, POLLIN, 0 },
 	{ child_handler_pipe[0], POLLIN, 0 },
     };
 
+    {
+	/* Run over each of the interfaces we know and care about, and
+	   make sure the state machine has done the appropriate thing
+	   for their current state. */
+	int poll_flags(struct if_info *i) {
+	    if (if_match(i->name))
+		ifsm_flagpoll(i);
+	    return 0;
+	}
+	for_each_iface(poll_flags);
+    }
+
     for(;;) {
-	int ret = poll(fds, sizeof(fds)/sizeof(fds[0]), -1);
+	int ret;
+
+	/* Make sure we don't miss anything interesting */
+	poll_interfaces();
+
+	ret = poll(fds, sizeof(fds)/sizeof(fds[0]), -1);
 
 	if (ret == -1) {
 	    if (errno == EINTR)
 int try_probe(char *iface);
 void probe_interfaces(void);
 
+extern int debug;
 
 /* netlink interfacing */
 
                                          struct rtattr *attrs[]);
 int if_info_save_interface(struct nlmsghdr *hdr, void *arg);
 void parse_rtattrs(struct rtattr *tb[], int max, struct rtattr *rta, int len);
+void for_each_iface(int (*func)(struct if_info *));
 
+void ifsm_flagpoll(struct if_info *info);
 void ifsm_flagchange(struct if_info *info, unsigned int newflags);
 void ifsm_scriptdone(pid_t pid, int exitstatus);