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 source tree.
9 //config:config IFPLUGD
10 //config: bool "ifplugd"
12 //config: select PLATFORM_LINUX
14 //config: Network interface plug detection daemon.
16 //applet:IF_IFPLUGD(APPLET(ifplugd, BB_DIR_USR_SBIN, BB_SUID_DROP))
18 //kbuild:lib-$(CONFIG_IFPLUGD) += ifplugd.o
20 //usage:#define ifplugd_trivial_usage
22 //usage:#define ifplugd_full_usage "\n\n"
23 //usage: "Network interface plug detection daemon\n"
24 //usage: "\n -n Don't daemonize"
25 //usage: "\n -s Don't log to syslog"
26 //usage: "\n -i IFACE Interface"
27 //usage: "\n -f/-F Treat link detection error as link down/link up"
28 //usage: "\n (otherwise exit on error)"
29 //usage: "\n -a Don't up interface at each link probe"
30 //usage: "\n -M Monitor creation/destruction of interface"
31 //usage: "\n (otherwise it must exist)"
32 //usage: "\n -r PROG Script to run"
33 //usage: "\n -x ARG Extra argument for script"
34 //usage: "\n -I Don't exit on nonzero exit code from script"
35 //usage: "\n -p Don't run \"up\" script on startup"
36 //usage: "\n -q Don't run \"down\" script on exit"
37 //usage: "\n -l Always run script on startup"
38 //usage: "\n -t SECS Poll time in seconds"
39 //usage: "\n -u SECS Delay before running script after link up"
40 //usage: "\n -d SECS Delay after link down"
41 //usage: "\n -m MODE API mode (mii, priv, ethtool, wlan, iff, auto)"
42 //usage: "\n -k Kill running daemon"
48 #include <linux/mii.h>
49 #include <linux/ethtool.h>
50 #ifdef HAVE_NET_ETHERNET_H
52 * In file included from /usr/include/net/ethernet.h:10,
53 * from networking/ifplugd.c:41:
54 * /usr/include/netinet/if_ether.h:96: error: redefinition of 'struct ethhdr'
56 * Build succeeds without it on musl. Commented it out.
57 * If on your system you need it, consider removing <linux/ethtool.h>
58 * and copy-pasting its definitions here (<linux/ethtool.h> is what pulls in
59 * conflicting definition of struct ethhdr on musl).
61 /* # include <net/ethernet.h> */
63 #include <linux/netlink.h>
64 #include <linux/rtnetlink.h>
65 #include <linux/sockios.h>
69 #include <linux/wireless.h>
76 From initial port to busybox, removed most of the redundancy by
77 converting implementation of a polymorphic interface to the strict
78 functional style. The main role is run a script when link state
79 changed, other activities like audio signal or detailed reports
80 are on the script itself.
82 One questionable point of the design is netlink usage:
84 We have 1 second timeout by default to poll the link status,
85 it is short enough so that there are no real benefits in
86 using netlink to get "instantaneous" interface creation/deletion
87 notifications. We can check for interface existence by just
88 doing some fast ioctl using its name.
90 Netlink code then can be just dropped (1k or more?)
94 #define IFPLUGD_ENV_PREVIOUS "IFPLUGD_PREVIOUS"
95 #define IFPLUGD_ENV_CURRENT "IFPLUGD_CURRENT"
98 FLAG_NO_AUTO = 1 << 0, // -a, Do not enable interface automatically
99 FLAG_NO_DAEMON = 1 << 1, // -n, Do not daemonize
100 FLAG_NO_SYSLOG = 1 << 2, // -s, Do not use syslog, use stderr instead
101 FLAG_IGNORE_FAIL = 1 << 3, // -f, Ignore detection failure, retry instead (failure is treated as DOWN)
102 FLAG_IGNORE_FAIL_POSITIVE = 1 << 4, // -F, Ignore detection failure, retry instead (failure is treated as UP)
103 FLAG_IFACE = 1 << 5, // -i, Specify ethernet interface
104 FLAG_RUN = 1 << 6, // -r, Specify program to execute
105 FLAG_IGNORE_RETVAL = 1 << 7, // -I, Don't exit on nonzero return value of program executed
106 FLAG_POLL_TIME = 1 << 8, // -t, Specify poll time in seconds
107 FLAG_DELAY_UP = 1 << 9, // -u, Specify delay for configuring interface
108 FLAG_DELAY_DOWN = 1 << 10, // -d, Specify delay for deconfiguring interface
109 FLAG_API_MODE = 1 << 11, // -m, Force API mode (mii, priv, ethtool, wlan, auto)
110 FLAG_NO_STARTUP = 1 << 12, // -p, Don't run script on daemon startup
111 FLAG_NO_SHUTDOWN = 1 << 13, // -q, Don't run script on daemon quit
112 FLAG_INITIAL_DOWN = 1 << 14, // -l, Run "down" script on startup if no cable is detected
113 FLAG_EXTRA_ARG = 1 << 15, // -x, Specify an extra argument for action script
114 FLAG_MONITOR = 1 << 16, // -M, Use interface monitoring
115 #if ENABLE_FEATURE_PIDFILE
116 FLAG_KILL = 1 << 17, // -k, Kill a running daemon
119 #if ENABLE_FEATURE_PIDFILE
120 # define OPTION_STR "+ansfFi:r:It:+u:+d:+m:pqlx:Mk"
122 # define OPTION_STR "+ansfFi:r:It:+u:+d:+m:pqlx:M"
125 enum { // interface status
131 enum { // constant fds
137 smallint iface_last_status;
138 smallint iface_prev_status;
139 smallint iface_exists;
140 smallint api_method_num;
142 /* Used in getopt32, must have sizeof == sizeof(int) */
148 const char *api_mode;
149 const char *script_name;
150 const char *extra_arg;
152 #define G (*ptr_to_globals)
153 #define INIT_G() do { \
154 SET_PTR_TO_GLOBALS(xzalloc(sizeof(G))); \
155 G.iface_last_status = -1; \
156 G.iface_exists = 1; \
161 G.script_name = "/etc/ifplugd/ifplugd.action"; \
165 /* Utility routines */
167 static void set_ifreq_to_ifname(struct ifreq *ifreq)
169 memset(ifreq, 0, sizeof(struct ifreq));
170 strncpy_IFNAMSIZ(ifreq->ifr_name, G.iface);
173 static int network_ioctl(int request, void* data, const char *errmsg)
175 int r = ioctl(ioctl_fd, request, data);
177 bb_perror_msg("%s failed", errmsg);
181 /* Link detection routines and table */
183 static smallint detect_link_mii(void)
185 /* char buffer instead of bona-fide struct avoids aliasing warning */
186 char buf[sizeof(struct ifreq)];
187 struct ifreq *const ifreq = (void *)buf;
189 struct mii_ioctl_data *mii = (void *)&ifreq->ifr_data;
191 set_ifreq_to_ifname(ifreq);
193 if (network_ioctl(SIOCGMIIPHY, ifreq, "SIOCGMIIPHY") < 0) {
199 if (network_ioctl(SIOCGMIIREG, ifreq, "SIOCGMIIREG") < 0) {
203 return (mii->val_out & 0x0004) ? IFSTATUS_UP : IFSTATUS_DOWN;
206 static smallint detect_link_priv(void)
208 /* char buffer instead of bona-fide struct avoids aliasing warning */
209 char buf[sizeof(struct ifreq)];
210 struct ifreq *const ifreq = (void *)buf;
212 struct mii_ioctl_data *mii = (void *)&ifreq->ifr_data;
214 set_ifreq_to_ifname(ifreq);
216 if (network_ioctl(SIOCDEVPRIVATE, ifreq, "SIOCDEVPRIVATE") < 0) {
222 if (network_ioctl(SIOCDEVPRIVATE+1, ifreq, "SIOCDEVPRIVATE+1") < 0) {
226 return (mii->val_out & 0x0004) ? IFSTATUS_UP : IFSTATUS_DOWN;
229 static smallint detect_link_ethtool(void)
232 struct ethtool_value edata;
234 set_ifreq_to_ifname(&ifreq);
236 edata.cmd = ETHTOOL_GLINK;
237 ifreq.ifr_data = (void*) &edata;
239 if (network_ioctl(SIOCETHTOOL, &ifreq, "ETHTOOL_GLINK") < 0) {
243 return edata.data ? IFSTATUS_UP : IFSTATUS_DOWN;
246 static smallint detect_link_iff(void)
250 set_ifreq_to_ifname(&ifreq);
252 if (network_ioctl(SIOCGIFFLAGS, &ifreq, "SIOCGIFFLAGS") < 0) {
256 /* If IFF_UP is not set (interface is down), IFF_RUNNING is never set
257 * regardless of link status. Simply continue to report last status -
258 * no point in reporting spurious link downs if interface is disabled
259 * by admin. When/if it will be brought up,
260 * we'll report real link status.
262 if (!(ifreq.ifr_flags & IFF_UP) && G.iface_last_status != IFSTATUS_ERR)
263 return G.iface_last_status;
265 return (ifreq.ifr_flags & IFF_RUNNING) ? IFSTATUS_UP : IFSTATUS_DOWN;
268 static smallint detect_link_wlan(void)
271 struct iwreq iwrequest;
272 uint8_t mac[ETH_ALEN];
274 memset(&iwrequest, 0, sizeof(iwrequest));
275 strncpy_IFNAMSIZ(iwrequest.ifr_ifrn.ifrn_name, G.iface);
277 if (network_ioctl(SIOCGIWAP, &iwrequest, "SIOCGIWAP") < 0) {
281 memcpy(mac, &iwrequest.u.ap_addr.sa_data, ETH_ALEN);
283 if (mac[0] == 0xFF || mac[0] == 0x44 || mac[0] == 0x00) {
284 for (i = 1; i < ETH_ALEN; ++i) {
285 if (mac[i] != mac[0])
288 return IFSTATUS_DOWN;
303 static const char api_modes[] ALIGN1 = "empwia";
305 static const struct {
307 smallint (*func)(void);
309 { "SIOCETHTOOL" , &detect_link_ethtool },
310 { "SIOCGMIIPHY" , &detect_link_mii },
311 { "SIOCDEVPRIVATE" , &detect_link_priv },
312 { "wireless extension", &detect_link_wlan },
313 { "IFF_RUNNING" , &detect_link_iff },
316 static const char *strstatus(int status)
318 if (status == IFSTATUS_ERR)
320 return "down\0up" + (status * 5);
323 static int run_script(const char *action)
325 char *env_PREVIOUS, *env_CURRENT;
329 bb_error_msg("executing '%s %s %s'", G.script_name, G.iface, action);
331 argv[0] = (char*) G.script_name;
332 argv[1] = (char*) G.iface;
333 argv[2] = (char*) action;
334 argv[3] = (char*) G.extra_arg;
337 env_PREVIOUS = xasprintf("%s=%s", IFPLUGD_ENV_PREVIOUS, strstatus(G.iface_prev_status));
338 putenv(env_PREVIOUS);
339 env_CURRENT = xasprintf("%s=%s", IFPLUGD_ENV_CURRENT, strstatus(G.iface_last_status));
342 /* r < 0 - can't exec, 0 <= r < 0x180 - exited, >=0x180 - killed by sig (r-0x180) */
343 r = spawn_and_wait(argv);
345 unsetenv(IFPLUGD_ENV_PREVIOUS);
346 unsetenv(IFPLUGD_ENV_CURRENT);
350 bb_error_msg("exit code: %d", r & 0xff);
351 return (option_mask32 & FLAG_IGNORE_RETVAL) ? 0 : r;
354 static void up_iface(void)
356 struct ifreq ifrequest;
361 set_ifreq_to_ifname(&ifrequest);
362 if (network_ioctl(SIOCGIFFLAGS, &ifrequest, "getting interface flags") < 0) {
367 if (!(ifrequest.ifr_flags & IFF_UP)) {
368 ifrequest.ifr_flags |= IFF_UP;
369 /* Let user know we mess up with interface */
370 bb_error_msg("upping interface");
371 if (network_ioctl(SIOCSIFFLAGS, &ifrequest, "setting interface flags") < 0) {
379 #if 0 /* why do we mess with IP addr? It's not our business */
380 if (network_ioctl(SIOCGIFADDR, &ifrequest, "can't get interface address") < 0) {
381 } else if (ifrequest.ifr_addr.sa_family != AF_INET) {
382 bb_perror_msg("the interface is not IP-based");
384 ((struct sockaddr_in*)(&ifrequest.ifr_addr))->sin_addr.s_addr = INADDR_ANY;
385 network_ioctl(SIOCSIFADDR, &ifrequest, "can't set interface address");
387 network_ioctl(SIOCGIFFLAGS, &ifrequest, "can't get interface flags");
391 static void maybe_up_new_iface(void)
393 if (!(option_mask32 & FLAG_NO_AUTO))
397 struct ifreq ifrequest;
398 struct ethtool_drvinfo driver_info;
400 set_ifreq_to_ifname(&ifrequest);
401 driver_info.cmd = ETHTOOL_GDRVINFO;
402 ifrequest.ifr_data = &driver_info;
403 if (network_ioctl(SIOCETHTOOL, &ifrequest, NULL) == 0) {
404 char buf[sizeof("/xx:xx:xx:xx:xx:xx")];
408 set_ifreq_to_ifname(&ifrequest);
409 if (network_ioctl(SIOCGIFHWADDR, &ifrequest, NULL) == 0) {
410 sprintf(buf, "/%02X:%02X:%02X:%02X:%02X:%02X",
411 (uint8_t)(ifrequest.ifr_hwaddr.sa_data[0]),
412 (uint8_t)(ifrequest.ifr_hwaddr.sa_data[1]),
413 (uint8_t)(ifrequest.ifr_hwaddr.sa_data[2]),
414 (uint8_t)(ifrequest.ifr_hwaddr.sa_data[3]),
415 (uint8_t)(ifrequest.ifr_hwaddr.sa_data[4]),
416 (uint8_t)(ifrequest.ifr_hwaddr.sa_data[5]));
419 bb_error_msg("using interface %s%s with driver<%s> (version: %s)",
420 G.iface, buf, driver_info.driver, driver_info.version);
423 if (G.api_mode[0] == 'a')
424 G.api_method_num = API_AUTO;
427 static smallint detect_link(void)
432 return (option_mask32 & FLAG_MONITOR) ? IFSTATUS_DOWN : IFSTATUS_ERR;
434 /* Some drivers can't detect link status when the interface is down.
435 * I imagine detect_link_iff() is the most vulnerable.
436 * That's why -a "noauto" in an option, not a hardwired behavior.
438 if (!(option_mask32 & FLAG_NO_AUTO))
441 if (G.api_method_num == API_AUTO) {
445 sv_logmode = logmode;
446 for (i = 0; i < ARRAY_SIZE(method_table); i++) {
447 logmode = LOGMODE_NONE;
448 status = method_table[i].func();
449 logmode = sv_logmode;
450 if (status != IFSTATUS_ERR) {
451 G.api_method_num = i;
452 bb_error_msg("using %s detection mode", method_table[i].name);
457 status = method_table[G.api_method_num].func();
460 if (status == IFSTATUS_ERR) {
461 if (option_mask32 & FLAG_IGNORE_FAIL)
462 status = IFSTATUS_DOWN;
463 else if (option_mask32 & FLAG_IGNORE_FAIL_POSITIVE)
464 status = IFSTATUS_UP;
465 else if (G.api_mode[0] == 'a')
466 bb_error_msg("can't detect link status");
469 if (status != G.iface_last_status) {
470 G.iface_prev_status = G.iface_last_status;
471 G.iface_last_status = status;
477 static NOINLINE int check_existence_through_netlink(void)
480 /* Buffer was 1K, but on linux-3.9.9 it was reported to be too small.
481 * netlink.h: "limit to 8K to avoid MSG_TRUNC when PAGE_SIZE is very large".
482 * Note: on error returns (-1) we exit, no need to free replybuf.
484 enum { BUF_SIZE = 8 * 1024 };
485 char *replybuf = xmalloc(BUF_SIZE);
487 iface_len = strlen(G.iface);
489 struct nlmsghdr *mhdr;
492 bytes = recv(netlink_fd, replybuf, BUF_SIZE, MSG_DONTWAIT);
498 bb_perror_msg("netlink: recv");
502 mhdr = (struct nlmsghdr*)replybuf;
504 if (!NLMSG_OK(mhdr, bytes)) {
505 bb_error_msg("netlink packet too small or truncated");
509 if (mhdr->nlmsg_type == RTM_NEWLINK || mhdr->nlmsg_type == RTM_DELLINK) {
513 if (mhdr->nlmsg_len < NLMSG_LENGTH(sizeof(struct ifinfomsg))) {
514 bb_error_msg("netlink packet too small or truncated");
518 attr = IFLA_RTA(NLMSG_DATA(mhdr));
519 attr_len = IFLA_PAYLOAD(mhdr);
521 while (RTA_OK(attr, attr_len)) {
522 if (attr->rta_type == IFLA_IFNAME) {
523 int len = RTA_PAYLOAD(attr);
527 && strncmp(G.iface, RTA_DATA(attr), len) == 0
529 G.iface_exists = (mhdr->nlmsg_type == RTM_NEWLINK);
532 attr = RTA_NEXT(attr, attr_len);
536 mhdr = NLMSG_NEXT(mhdr, bytes);
542 return G.iface_exists;
545 #if ENABLE_FEATURE_PIDFILE
546 static NOINLINE pid_t read_pid(const char *filename)
551 len = open_read_close(filename, buf, 127);
554 /* returns ULONG_MAX on error => -1 */
555 return bb_strtoul(buf, NULL, 10);
561 int ifplugd_main(int argc, char **argv) MAIN_EXTERNALLY_VISIBLE;
562 int ifplugd_main(int argc UNUSED_PARAM, char **argv)
566 const char *iface_status_str;
567 struct pollfd netlink_pollfd[1];
569 const char *api_mode_found;
570 #if ENABLE_FEATURE_PIDFILE
572 pid_t pid_from_pidfile;
577 opts = getopt32(argv, OPTION_STR,
578 &G.iface, &G.script_name, &G.poll_time, &G.delay_up,
579 &G.delay_down, &G.api_mode, &G.extra_arg);
582 applet_name = xasprintf("ifplugd(%s)", G.iface);
584 #if ENABLE_FEATURE_PIDFILE
585 pidfile_name = xasprintf(CONFIG_PID_FILE_PATH "/ifplugd.%s.pid", G.iface);
586 pid_from_pidfile = read_pid(pidfile_name);
588 if (opts & FLAG_KILL) {
589 if (pid_from_pidfile > 0)
590 /* Upstream tool use SIGINT for -k */
591 kill(pid_from_pidfile, SIGINT);
595 if (pid_from_pidfile > 0 && kill(pid_from_pidfile, 0) == 0)
596 bb_error_msg_and_die("daemon already running");
599 api_mode_found = strchr(api_modes, G.api_mode[0]);
601 bb_error_msg_and_die("unknown API mode '%s'", G.api_mode);
602 G.api_method_num = api_mode_found - api_modes;
604 if (!(opts & FLAG_NO_DAEMON))
605 bb_daemonize_or_rexec(DAEMON_CHDIR_ROOT, argv);
607 xmove_fd(xsocket(AF_INET, SOCK_DGRAM, 0), ioctl_fd);
608 if (opts & FLAG_MONITOR) {
609 struct sockaddr_nl addr;
610 int fd = xsocket(PF_NETLINK, SOCK_DGRAM, NETLINK_ROUTE);
612 memset(&addr, 0, sizeof(addr));
613 addr.nl_family = AF_NETLINK;
614 addr.nl_groups = RTMGRP_LINK;
615 addr.nl_pid = getpid();
617 xbind(fd, (struct sockaddr*)&addr, sizeof(addr));
618 xmove_fd(fd, netlink_fd);
621 write_pidfile(pidfile_name);
623 /* this can't be moved before socket creation */
624 if (!(opts & FLAG_NO_SYSLOG)) {
625 openlog(applet_name, 0, LOG_DAEMON);
626 logmode |= LOGMODE_SYSLOG;
633 | (1 << SIGHUP ) /* why we ignore it? */
634 /* | (1 << SIGCHLD) - run_script does not use it anymore */
637 bb_error_msg("started: %s", bb_banner);
639 if (opts & FLAG_MONITOR) {
640 struct ifreq ifrequest;
641 set_ifreq_to_ifname(&ifrequest);
642 G.iface_exists = (network_ioctl(SIOCGIFINDEX, &ifrequest, NULL) == 0);
646 maybe_up_new_iface();
648 iface_status = detect_link();
649 if (iface_status == IFSTATUS_ERR)
651 iface_status_str = strstatus(iface_status);
653 if (opts & FLAG_MONITOR) {
654 bb_error_msg("interface %s",
655 G.iface_exists ? "exists"
656 : "doesn't exist, waiting");
658 /* else we assume it always exists, but don't mislead user
659 * by potentially lying that it really exists */
661 if (G.iface_exists) {
662 bb_error_msg("link is %s", iface_status_str);
665 if ((!(opts & FLAG_NO_STARTUP)
666 && iface_status == IFSTATUS_UP
668 || (opts & FLAG_INITIAL_DOWN)
670 if (run_script(iface_status_str) != 0)
675 netlink_pollfd[0].fd = netlink_fd;
676 netlink_pollfd[0].events = POLLIN;
679 int iface_status_old;
681 switch (bb_got_signal) {
694 if (poll(netlink_pollfd,
695 (opts & FLAG_MONITOR) ? 1 : 0,
701 bb_perror_msg("poll");
705 if ((opts & FLAG_MONITOR)
706 && (netlink_pollfd[0].revents & POLLIN)
708 int iface_exists_old;
710 iface_exists_old = G.iface_exists;
711 G.iface_exists = check_existence_through_netlink();
712 if (G.iface_exists < 0) /* error */
714 if (iface_exists_old != G.iface_exists) {
715 bb_error_msg("interface %sappeared",
716 G.iface_exists ? "" : "dis");
718 maybe_up_new_iface();
722 /* note: if !G.iface_exists, returns DOWN */
723 iface_status_old = iface_status;
724 iface_status = detect_link();
725 if (iface_status == IFSTATUS_ERR) {
726 if (!(opts & FLAG_MONITOR))
728 iface_status = IFSTATUS_DOWN;
730 iface_status_str = strstatus(iface_status);
732 if (iface_status_old != iface_status) {
733 bb_error_msg("link is %s", iface_status_str);
736 /* link restored its old status before
737 * we ran script. don't run the script: */
740 delay_time = monotonic_sec();
741 if (iface_status == IFSTATUS_UP)
742 delay_time += G.delay_up;
743 if (iface_status == IFSTATUS_DOWN)
744 delay_time += G.delay_down;
745 #if 0 /* if you are back in 1970... */
746 if (delay_time == 0) {
754 if (delay_time && (int)(monotonic_sec() - delay_time) >= 0) {
755 if (run_script(iface_status_str) != 0)
762 if (!(opts & FLAG_NO_SHUTDOWN)
763 && (iface_status == IFSTATUS_UP
764 || (iface_status == IFSTATUS_DOWN && delay_time)
767 setenv(IFPLUGD_ENV_PREVIOUS, strstatus(iface_status), 1);
768 setenv(IFPLUGD_ENV_CURRENT, strstatus(-1), 1);
769 run_script("down\0up"); /* reusing string */
773 remove_pidfile(pidfile_name);
774 bb_error_msg_and_die("exiting");