1 /* vi: set sw=4 ts=4: */
3 * ifplugd for busybox, based on ifplugd 0.28 (written by Lennart Poettering).
5 * Copyright (C) 2009 Maksym Kryzhanovskyy <xmaks@email.cz>
7 * Licensed under GPLv2 or later, see file LICENSE in this tarball for details.
13 #include <linux/mii.h>
14 #include <linux/ethtool.h>
15 #include <net/ethernet.h>
16 #include <linux/netlink.h>
17 #include <linux/rtnetlink.h>
18 #include <linux/sockios.h>
22 #include <linux/wireless.h>
25 From initial port to busybox, removed most of the redundancy by
26 converting implementation of a polymorphic interface to the strict
27 functional style. The main role is run a script when link state
28 changed, other activities like audio signal or detailed reports
29 are on the script itself.
31 One questionable point of the design is netlink usage:
33 We have 1 second timeout by default to poll the link status,
34 it is short enough so that there are no real benefits in
35 using netlink to get "instantaneous" interface creation/deletion
36 notifications. We can check for interface existence by just
37 doing some fast ioctl using its name.
39 Netlink code then can be just dropped (1k or more?)
43 #define IFPLUGD_ENV_PREVIOUS "IFPLUGD_PREVIOUS"
44 #define IFPLUGD_ENV_CURRENT "IFPLUGD_CURRENT"
47 FLAG_NO_AUTO = 1 << 0, // -a, Do not enable interface automatically
48 FLAG_NO_DAEMON = 1 << 1, // -n, Do not daemonize
49 FLAG_NO_SYSLOG = 1 << 2, // -s, Do not use syslog, use stderr instead
50 FLAG_IGNORE_FAIL = 1 << 3, // -f, Ignore detection failure, retry instead (failure is treated as DOWN)
51 FLAG_IGNORE_FAIL_POSITIVE = 1 << 4, // -F, Ignore detection failure, retry instead (failure is treated as UP)
52 FLAG_IFACE = 1 << 5, // -i, Specify ethernet interface
53 FLAG_RUN = 1 << 6, // -r, Specify program to execute
54 FLAG_IGNORE_RETVAL = 1 << 7, // -I, Don't exit on nonzero return value of program executed
55 FLAG_POLL_TIME = 1 << 8, // -t, Specify poll time in seconds
56 FLAG_DELAY_UP = 1 << 9, // -u, Specify delay for configuring interface
57 FLAG_DELAY_DOWN = 1 << 10, // -d, Specify delay for deconfiguring interface
58 FLAG_API_MODE = 1 << 11, // -m, Force API mode (mii, priv, ethtool, wlan, auto)
59 FLAG_NO_STARTUP = 1 << 12, // -p, Don't run script on daemon startup
60 FLAG_NO_SHUTDOWN = 1 << 13, // -q, Don't run script on daemon quit
61 FLAG_INITIAL_DOWN = 1 << 14, // -l, Run "down" script on startup if no cable is detected
62 FLAG_EXTRA_ARG = 1 << 15, // -x, Specify an extra argument for action script
63 FLAG_MONITOR = 1 << 16, // -M, Use interface monitoring
64 #if ENABLE_FEATURE_PIDFILE
65 FLAG_KILL = 1 << 17, // -k, Kill a running daemon
68 #if ENABLE_FEATURE_PIDFILE
69 # define OPTION_STR "+ansfFi:r:It:u:d:m:pqlx:Mk"
71 # define OPTION_STR "+ansfFi:r:It:u:d:m:pqlx:M"
82 static const char api_modes[] ALIGN1 = "aempwi";
84 enum { // interface status
90 enum { // constant fds
96 smallint iface_last_status;
97 smallint iface_prev_status;
98 smallint iface_exists;
99 smallint api_method_num;
101 /* Used in getopt32, must have sizeof == sizeof(int) */
107 const char *api_mode;
108 const char *script_name;
109 const char *extra_arg;
111 smallint (*detect_link_func)(void);
113 #define G (*ptr_to_globals)
114 #define INIT_G() do { \
115 SET_PTR_TO_GLOBALS(xzalloc(sizeof(G))); \
116 G.iface_last_status = -1; \
117 G.iface_exists = 1; \
122 G.script_name = "/etc/ifplugd/ifplugd.action"; \
126 static const char *strstatus(int status)
128 if (status == IFSTATUS_ERR)
130 return "down\0up" + (status * 5);
133 static int run_script(const char *action)
135 char *env_PREVIOUS, *env_CURRENT;
139 bb_error_msg("executing '%s %s %s'", G.script_name, G.iface, action);
141 argv[0] = (char*) G.script_name;
142 argv[1] = (char*) G.iface;
143 argv[2] = (char*) action;
144 argv[3] = (char*) G.extra_arg;
147 env_PREVIOUS = xasprintf("%s=%s", IFPLUGD_ENV_PREVIOUS, strstatus(G.iface_prev_status));
148 putenv(env_PREVIOUS);
149 env_CURRENT = xasprintf("%s=%s", IFPLUGD_ENV_CURRENT, strstatus(G.iface_last_status));
152 /* r < 0 - can't exec, 0 <= r < 0x180 - exited, >=0x180 - killed by sig (r-0x180) */
153 r = spawn_and_wait(argv);
155 unsetenv(IFPLUGD_ENV_PREVIOUS);
156 unsetenv(IFPLUGD_ENV_CURRENT);
160 bb_error_msg("exit code: %d", r & 0xff);
161 return (option_mask32 & FLAG_IGNORE_RETVAL) ? 0 : r;
164 static int network_ioctl(int request, void* data, const char *errmsg)
166 int r = ioctl(ioctl_fd, request, data);
168 bb_perror_msg("%s failed", errmsg);
172 static void set_ifreq_to_ifname(struct ifreq *ifreq)
174 memset(ifreq, 0, sizeof(struct ifreq));
175 strncpy_IFNAMSIZ(ifreq->ifr_name, G.iface);
178 static void up_iface(void)
180 struct ifreq ifrequest;
185 set_ifreq_to_ifname(&ifrequest);
186 if (network_ioctl(SIOCGIFFLAGS, &ifrequest, "getting interface flags") < 0) {
191 if (!(ifrequest.ifr_flags & IFF_UP)) {
192 ifrequest.ifr_flags |= IFF_UP;
193 /* Let user know we mess up with interface */
194 bb_error_msg("upping interface");
195 if (network_ioctl(SIOCSIFFLAGS, &ifrequest, "setting interface flags") < 0)
199 #if 0 /* why do we mess with IP addr? It's not our business */
200 if (network_ioctl(SIOCGIFADDR, &ifrequest, "can't get interface address") < 0) {
201 } else if (ifrequest.ifr_addr.sa_family != AF_INET) {
202 bb_perror_msg("the interface is not IP-based");
204 ((struct sockaddr_in*)(&ifrequest.ifr_addr))->sin_addr.s_addr = INADDR_ANY;
205 network_ioctl(SIOCSIFADDR, &ifrequest, "can't set interface address");
207 network_ioctl(SIOCGIFFLAGS, &ifrequest, "can't get interface flags");
211 static void maybe_up_new_iface(void)
213 if (!(option_mask32 & FLAG_NO_AUTO))
217 struct ifreq ifrequest;
218 struct ethtool_drvinfo driver_info;
220 set_ifreq_to_ifname(&ifrequest);
221 driver_info.cmd = ETHTOOL_GDRVINFO;
222 ifrequest.ifr_data = &driver_info;
223 if (network_ioctl(SIOCETHTOOL, &ifrequest, NULL) == 0) {
224 char buf[sizeof("/xx:xx:xx:xx:xx:xx")];
228 set_ifreq_to_ifname(&ifrequest);
229 if (network_ioctl(SIOCGIFHWADDR, &ifrequest, NULL) == 0) {
230 sprintf(buf, "/%02X:%02X:%02X:%02X:%02X:%02X",
231 (uint8_t)(ifrequest.ifr_hwaddr.sa_data[0]),
232 (uint8_t)(ifrequest.ifr_hwaddr.sa_data[1]),
233 (uint8_t)(ifrequest.ifr_hwaddr.sa_data[2]),
234 (uint8_t)(ifrequest.ifr_hwaddr.sa_data[3]),
235 (uint8_t)(ifrequest.ifr_hwaddr.sa_data[4]),
236 (uint8_t)(ifrequest.ifr_hwaddr.sa_data[5]));
239 bb_error_msg("using interface %s%s with driver<%s> (version: %s)",
240 G.iface, buf, driver_info.driver, driver_info.version);
243 if (G.api_method_num == 0)
244 G.detect_link_func = NULL;
247 static smallint detect_link_mii(void)
250 struct mii_ioctl_data *mii = (void *)&ifreq.ifr_data;
252 set_ifreq_to_ifname(&ifreq);
254 if (network_ioctl(SIOCGMIIPHY, &ifreq, "SIOCGMIIPHY") < 0) {
260 if (network_ioctl(SIOCGMIIREG, &ifreq, "SIOCGMIIREG") < 0) {
264 return (mii->val_out & 0x0004) ? IFSTATUS_UP : IFSTATUS_DOWN;
267 static smallint detect_link_priv(void)
270 struct mii_ioctl_data *mii = (void *)&ifreq.ifr_data;
272 set_ifreq_to_ifname(&ifreq);
274 if (network_ioctl(SIOCDEVPRIVATE, &ifreq, "SIOCDEVPRIVATE") < 0) {
280 if (network_ioctl(SIOCDEVPRIVATE+1, &ifreq, "SIOCDEVPRIVATE+1") < 0) {
284 return (mii->val_out & 0x0004) ? IFSTATUS_UP : IFSTATUS_DOWN;
287 static smallint detect_link_ethtool(void)
290 struct ethtool_value edata;
292 set_ifreq_to_ifname(&ifreq);
294 edata.cmd = ETHTOOL_GLINK;
295 ifreq.ifr_data = (void*) &edata;
297 if (network_ioctl(SIOCETHTOOL, &ifreq, "ETHTOOL_GLINK") < 0) {
301 return edata.data ? IFSTATUS_UP : IFSTATUS_DOWN;
304 static smallint detect_link_iff(void)
308 set_ifreq_to_ifname(&ifreq);
310 if (network_ioctl(SIOCGIFFLAGS, &ifreq, "SIOCGIFFLAGS") < 0) {
314 /* If IFF_UP is not set (interface is down), IFF_RUNNING is never set
315 * regardless of link status. Simply continue to report last status -
316 * no point in reporting spurious link downs if interface is disabled
317 * by admin. When/if it will be brought up,
318 * we'll report real link status.
320 if (!(ifreq.ifr_flags & IFF_UP) && G.iface_last_status != IFSTATUS_ERR)
321 return G.iface_last_status;
323 return (ifreq.ifr_flags & IFF_RUNNING) ? IFSTATUS_UP : IFSTATUS_DOWN;
326 static smallint detect_link_wlan(void)
329 struct iwreq iwrequest;
330 uint8_t mac[ETH_ALEN];
332 memset(&iwrequest, 0, sizeof(iwrequest));
333 strncpy_IFNAMSIZ(iwrequest.ifr_ifrn.ifrn_name, G.iface);
335 if (network_ioctl(SIOCGIWAP, &iwrequest, "SIOCGIWAP") < 0) {
339 memcpy(mac, &iwrequest.u.ap_addr.sa_data, ETH_ALEN);
341 if (mac[0] == 0xFF || mac[0] == 0x44 || mac[0] == 0x00) {
342 for (i = 1; i < ETH_ALEN; ++i) {
343 if (mac[i] != mac[0])
346 return IFSTATUS_DOWN;
352 static smallint detect_link(void)
354 static const struct {
356 smallint (*func)(void);
358 { "SIOCETHTOOL" , &detect_link_ethtool },
359 { "SIOCGMIIPHY" , &detect_link_mii },
360 { "SIOCDEVPRIVATE" , &detect_link_priv },
361 { "wireless extension", &detect_link_wlan },
362 { "IFF_RUNNING" , &detect_link_iff },
367 return (option_mask32 & FLAG_MONITOR) ? IFSTATUS_DOWN : IFSTATUS_ERR;
369 /* Some drivers can't detect link status when the interface is down.
370 * I imagine detect_link_iff() is the most vulnerable.
371 * That's why -a "noauto" in an option, not a hardwired behavior.
373 if (!(option_mask32 & FLAG_NO_AUTO))
376 if (!G.detect_link_func) {
377 if (G.api_method_num == 0) {
381 sv_logmode = logmode;
382 for (i = 0; i < ARRAY_SIZE(method); i++) {
383 logmode = LOGMODE_NONE;
384 status = method[i].func();
385 logmode = sv_logmode;
386 if (status != IFSTATUS_ERR) {
387 G.detect_link_func = method[i].func;
388 bb_error_msg("using %s detection mode", method[i].name);
394 G.detect_link_func = method[G.api_method_num - 1].func;
397 status = G.detect_link_func();
399 if (status == IFSTATUS_ERR) {
400 if (option_mask32 & FLAG_IGNORE_FAIL)
401 status = IFSTATUS_DOWN;
402 else if (option_mask32 & FLAG_IGNORE_FAIL_POSITIVE)
403 status = IFSTATUS_UP;
404 else if (G.api_method_num == 0)
405 bb_error_msg("can't detect link status");
408 if (status != G.iface_last_status) {
409 G.iface_prev_status = G.iface_last_status;
410 G.iface_last_status = status;
416 static NOINLINE int check_existence_through_netlink(void)
421 iface_len = strlen(G.iface);
423 struct nlmsghdr *mhdr;
426 bytes = recv(netlink_fd, &replybuf, sizeof(replybuf), MSG_DONTWAIT);
429 return G.iface_exists;
433 bb_perror_msg("netlink: recv");
437 mhdr = (struct nlmsghdr*)replybuf;
439 if (!NLMSG_OK(mhdr, bytes)) {
440 bb_error_msg("netlink packet too small or truncated");
444 if (mhdr->nlmsg_type == RTM_NEWLINK || mhdr->nlmsg_type == RTM_DELLINK) {
448 if (mhdr->nlmsg_len < NLMSG_LENGTH(sizeof(struct ifinfomsg))) {
449 bb_error_msg("netlink packet too small or truncated");
453 attr = IFLA_RTA(NLMSG_DATA(mhdr));
454 attr_len = IFLA_PAYLOAD(mhdr);
456 while (RTA_OK(attr, attr_len)) {
457 if (attr->rta_type == IFLA_IFNAME) {
458 int len = RTA_PAYLOAD(attr);
462 && strncmp(G.iface, RTA_DATA(attr), len) == 0
464 G.iface_exists = (mhdr->nlmsg_type == RTM_NEWLINK);
467 attr = RTA_NEXT(attr, attr_len);
471 mhdr = NLMSG_NEXT(mhdr, bytes);
475 return G.iface_exists;
478 static NOINLINE int netlink_open(void)
481 struct sockaddr_nl addr;
483 fd = xsocket(PF_NETLINK, SOCK_DGRAM, NETLINK_ROUTE);
485 memset(&addr, 0, sizeof(addr));
486 addr.nl_family = AF_NETLINK;
487 addr.nl_groups = RTMGRP_LINK;
488 addr.nl_pid = getpid();
490 xbind(fd, (struct sockaddr*)&addr, sizeof(addr));
495 #if ENABLE_FEATURE_PIDFILE
496 static NOINLINE pid_t read_pid(const char *filename)
501 len = open_read_close(filename, buf, 127);
504 /* returns ULONG_MAX on error => -1 */
505 return bb_strtoul(buf, NULL, 10);
511 int ifplugd_main(int argc, char **argv) MAIN_EXTERNALLY_VISIBLE;
512 int ifplugd_main(int argc UNUSED_PARAM, char **argv)
516 const char *iface_status_str;
517 struct pollfd netlink_pollfd[1];
519 const char *api_mode_found;
520 #if ENABLE_FEATURE_PIDFILE
522 pid_t pid_from_pidfile;
527 opt_complementary = "t+:u+:d+";
528 opts = getopt32(argv, OPTION_STR,
529 &G.iface, &G.script_name, &G.poll_time, &G.delay_up,
530 &G.delay_down, &G.api_mode, &G.extra_arg);
533 applet_name = xasprintf("ifplugd(%s)", G.iface);
535 #if ENABLE_FEATURE_PIDFILE
536 pidfile_name = xasprintf(_PATH_VARRUN"ifplugd.%s.pid", G.iface);
537 pid_from_pidfile = read_pid(pidfile_name);
539 if (opts & FLAG_KILL) {
540 if (pid_from_pidfile > 0)
541 kill(pid_from_pidfile, SIGQUIT);
545 if (pid_from_pidfile > 0 && kill(pid_from_pidfile, 0) == 0)
546 bb_error_msg_and_die("daemon already running");
548 api_mode_found = strchr(api_modes, G.api_mode[0]);
550 bb_error_msg_and_die("unknown API mode '%s'", G.api_mode);
551 G.api_method_num = api_mode_found - api_modes;
553 if (!(opts & FLAG_NO_DAEMON))
554 bb_daemonize_or_rexec(DAEMON_CHDIR_ROOT, argv);
556 xmove_fd(xsocket(AF_INET, SOCK_DGRAM, 0), ioctl_fd);
557 if (opts & FLAG_MONITOR) {
558 xmove_fd(netlink_open(), netlink_fd);
561 write_pidfile(pidfile_name);
563 /* this can't be moved before socket creation */
564 if (!(opts & FLAG_NO_SYSLOG)) {
565 openlog(applet_name, 0, LOG_DAEMON);
566 logmode |= LOGMODE_SYSLOG;
573 | (1 << SIGHUP ) /* why we ignore it? */
574 /* | (1 << SIGCHLD) - run_script does not use it anymore */
577 bb_error_msg("started: %s", bb_banner);
579 if (opts & FLAG_MONITOR) {
580 struct ifreq ifrequest;
581 set_ifreq_to_ifname(&ifrequest);
582 G.iface_exists = (network_ioctl(SIOCGIFINDEX, &ifrequest, NULL) == 0);
586 maybe_up_new_iface();
588 iface_status = detect_link();
589 if (iface_status == IFSTATUS_ERR)
591 iface_status_str = strstatus(iface_status);
593 if (opts & FLAG_MONITOR) {
594 bb_error_msg("interface %s",
595 G.iface_exists ? "exists"
596 : "doesn't exist, waiting");
598 /* else we assume it always exists, but don't mislead user
599 * by potentially lying that it really exists */
601 if (G.iface_exists) {
602 bb_error_msg("link is %s", iface_status_str);
605 if ((!(opts & FLAG_NO_STARTUP)
606 && iface_status == IFSTATUS_UP
608 || (opts & FLAG_INITIAL_DOWN)
610 if (run_script(iface_status_str) != 0)
615 netlink_pollfd[0].fd = netlink_fd;
616 netlink_pollfd[0].events = POLLIN;
619 int iface_status_old;
620 int iface_exists_old;
622 switch (bb_got_signal) {
635 if (poll(netlink_pollfd,
636 (opts & FLAG_MONITOR) ? 1 : 0,
642 bb_perror_msg("poll");
646 iface_status_old = iface_status;
647 iface_exists_old = G.iface_exists;
649 if ((opts & FLAG_MONITOR)
650 && (netlink_pollfd[0].revents & POLLIN)
652 G.iface_exists = check_existence_through_netlink();
653 if (G.iface_exists < 0) /* error */
655 if (iface_exists_old != G.iface_exists) {
656 bb_error_msg("interface %sappeared",
657 G.iface_exists ? "" : "dis");
659 maybe_up_new_iface();
663 /* note: if !G.iface_exists, returns DOWN */
664 iface_status = detect_link();
665 if (iface_status == IFSTATUS_ERR) {
666 if (!(opts & FLAG_MONITOR))
668 iface_status = IFSTATUS_DOWN;
670 iface_status_str = strstatus(iface_status);
672 if (iface_status_old != iface_status) {
673 bb_error_msg("link is %s", iface_status_str);
676 /* link restored its old status before
677 * we run script. don't run the script: */
680 delay_time = monotonic_sec();
681 if (iface_status == IFSTATUS_UP)
682 delay_time += G.delay_up;
683 if (iface_status == IFSTATUS_DOWN)
684 delay_time += G.delay_down;
690 if (delay_time && (int)(monotonic_sec() - delay_time) >= 0) {
692 if (run_script(iface_status_str) != 0)
698 if (!(opts & FLAG_NO_SHUTDOWN)
699 && (iface_status == IFSTATUS_UP
700 || (iface_status == IFSTATUS_DOWN && delay_time)
703 setenv(IFPLUGD_ENV_PREVIOUS, strstatus(iface_status), 1);
704 setenv(IFPLUGD_ENV_CURRENT, strstatus(-1), 1);
705 run_script("down\0up"); /* reusing string */
709 remove_pidfile(pidfile_name);
710 bb_error_msg_and_die("exiting");