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 (9.9 kb)"
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 bb_unsetenv_and_free(env_PREVIOUS);
346 bb_unsetenv_and_free(env_CURRENT);
348 bb_error_msg("exit code: %d", r & 0xff);
349 return (option_mask32 & FLAG_IGNORE_RETVAL) ? 0 : r;
352 static void up_iface(void)
354 struct ifreq ifrequest;
359 set_ifreq_to_ifname(&ifrequest);
360 if (network_ioctl(SIOCGIFFLAGS, &ifrequest, "getting interface flags") < 0) {
365 if (!(ifrequest.ifr_flags & IFF_UP)) {
366 ifrequest.ifr_flags |= IFF_UP;
367 /* Let user know we mess up with interface */
368 bb_error_msg("upping interface");
369 if (network_ioctl(SIOCSIFFLAGS, &ifrequest, "setting interface flags") < 0) {
377 #if 0 /* why do we mess with IP addr? It's not our business */
378 if (network_ioctl(SIOCGIFADDR, &ifrequest, "can't get interface address") < 0) {
379 } else if (ifrequest.ifr_addr.sa_family != AF_INET) {
380 bb_perror_msg("the interface is not IP-based");
382 ((struct sockaddr_in*)(&ifrequest.ifr_addr))->sin_addr.s_addr = INADDR_ANY;
383 network_ioctl(SIOCSIFADDR, &ifrequest, "can't set interface address");
385 network_ioctl(SIOCGIFFLAGS, &ifrequest, "can't get interface flags");
389 static void maybe_up_new_iface(void)
391 if (!(option_mask32 & FLAG_NO_AUTO))
395 struct ifreq ifrequest;
396 struct ethtool_drvinfo driver_info;
398 set_ifreq_to_ifname(&ifrequest);
399 driver_info.cmd = ETHTOOL_GDRVINFO;
400 ifrequest.ifr_data = &driver_info;
401 if (network_ioctl(SIOCETHTOOL, &ifrequest, NULL) == 0) {
402 char buf[sizeof("/xx:xx:xx:xx:xx:xx")];
406 set_ifreq_to_ifname(&ifrequest);
407 if (network_ioctl(SIOCGIFHWADDR, &ifrequest, NULL) == 0) {
408 sprintf(buf, "/%02X:%02X:%02X:%02X:%02X:%02X",
409 (uint8_t)(ifrequest.ifr_hwaddr.sa_data[0]),
410 (uint8_t)(ifrequest.ifr_hwaddr.sa_data[1]),
411 (uint8_t)(ifrequest.ifr_hwaddr.sa_data[2]),
412 (uint8_t)(ifrequest.ifr_hwaddr.sa_data[3]),
413 (uint8_t)(ifrequest.ifr_hwaddr.sa_data[4]),
414 (uint8_t)(ifrequest.ifr_hwaddr.sa_data[5]));
417 bb_error_msg("using interface %s%s with driver<%s> (version: %s)",
418 G.iface, buf, driver_info.driver, driver_info.version);
421 if (G.api_mode[0] == 'a')
422 G.api_method_num = API_AUTO;
425 static smallint detect_link(void)
430 return (option_mask32 & FLAG_MONITOR) ? IFSTATUS_DOWN : IFSTATUS_ERR;
432 /* Some drivers can't detect link status when the interface is down.
433 * I imagine detect_link_iff() is the most vulnerable.
434 * That's why -a "noauto" in an option, not a hardwired behavior.
436 if (!(option_mask32 & FLAG_NO_AUTO))
439 if (G.api_method_num == API_AUTO) {
443 sv_logmode = logmode;
444 for (i = 0; i < ARRAY_SIZE(method_table); i++) {
445 logmode = LOGMODE_NONE;
446 status = method_table[i].func();
447 logmode = sv_logmode;
448 if (status != IFSTATUS_ERR) {
449 G.api_method_num = i;
450 bb_error_msg("using %s detection mode", method_table[i].name);
455 status = method_table[G.api_method_num].func();
458 if (status == IFSTATUS_ERR) {
459 if (option_mask32 & FLAG_IGNORE_FAIL)
460 status = IFSTATUS_DOWN;
461 else if (option_mask32 & FLAG_IGNORE_FAIL_POSITIVE)
462 status = IFSTATUS_UP;
463 else if (G.api_mode[0] == 'a')
464 bb_error_msg("can't detect link status");
467 if (status != G.iface_last_status) {
468 G.iface_prev_status = G.iface_last_status;
469 G.iface_last_status = status;
475 static NOINLINE int check_existence_through_netlink(void)
478 /* Buffer was 1K, but on linux-3.9.9 it was reported to be too small.
479 * netlink.h: "limit to 8K to avoid MSG_TRUNC when PAGE_SIZE is very large".
480 * Note: on error returns (-1) we exit, no need to free replybuf.
482 enum { BUF_SIZE = 8 * 1024 };
483 char *replybuf = xmalloc(BUF_SIZE);
485 iface_len = strlen(G.iface);
487 struct nlmsghdr *mhdr;
490 bytes = recv(netlink_fd, replybuf, BUF_SIZE, MSG_DONTWAIT);
496 bb_perror_msg("netlink: recv");
500 mhdr = (struct nlmsghdr*)replybuf;
502 if (!NLMSG_OK(mhdr, bytes)) {
503 bb_error_msg("netlink packet too small or truncated");
507 if (mhdr->nlmsg_type == RTM_NEWLINK || mhdr->nlmsg_type == RTM_DELLINK) {
511 if (mhdr->nlmsg_len < NLMSG_LENGTH(sizeof(struct ifinfomsg))) {
512 bb_error_msg("netlink packet too small or truncated");
516 attr = IFLA_RTA(NLMSG_DATA(mhdr));
517 attr_len = IFLA_PAYLOAD(mhdr);
519 while (RTA_OK(attr, attr_len)) {
520 if (attr->rta_type == IFLA_IFNAME) {
521 int len = RTA_PAYLOAD(attr);
525 && strncmp(G.iface, RTA_DATA(attr), len) == 0
527 G.iface_exists = (mhdr->nlmsg_type == RTM_NEWLINK);
530 attr = RTA_NEXT(attr, attr_len);
534 mhdr = NLMSG_NEXT(mhdr, bytes);
540 return G.iface_exists;
543 #if ENABLE_FEATURE_PIDFILE
544 static NOINLINE pid_t read_pid(const char *filename)
549 len = open_read_close(filename, buf, 127);
552 /* returns ULONG_MAX on error => -1 */
553 return bb_strtoul(buf, NULL, 10);
559 int ifplugd_main(int argc, char **argv) MAIN_EXTERNALLY_VISIBLE;
560 int ifplugd_main(int argc UNUSED_PARAM, char **argv)
564 const char *iface_status_str;
565 struct pollfd netlink_pollfd[1];
567 const char *api_mode_found;
568 #if ENABLE_FEATURE_PIDFILE
570 pid_t pid_from_pidfile;
575 opts = getopt32(argv, OPTION_STR,
576 &G.iface, &G.script_name, &G.poll_time, &G.delay_up,
577 &G.delay_down, &G.api_mode, &G.extra_arg);
580 applet_name = xasprintf("ifplugd(%s)", G.iface);
582 #if ENABLE_FEATURE_PIDFILE
583 pidfile_name = xasprintf(CONFIG_PID_FILE_PATH "/ifplugd.%s.pid", G.iface);
584 pid_from_pidfile = read_pid(pidfile_name);
586 if (opts & FLAG_KILL) {
587 if (pid_from_pidfile > 0)
588 /* Upstream tool use SIGINT for -k */
589 kill(pid_from_pidfile, SIGINT);
593 if (pid_from_pidfile > 0 && kill(pid_from_pidfile, 0) == 0)
594 bb_error_msg_and_die("daemon already running");
597 api_mode_found = strchr(api_modes, G.api_mode[0]);
599 bb_error_msg_and_die("unknown API mode '%s'", G.api_mode);
600 G.api_method_num = api_mode_found - api_modes;
602 if (!(opts & FLAG_NO_DAEMON))
603 bb_daemonize_or_rexec(DAEMON_CHDIR_ROOT, argv);
605 xmove_fd(xsocket(AF_INET, SOCK_DGRAM, 0), ioctl_fd);
606 if (opts & FLAG_MONITOR) {
607 struct sockaddr_nl addr;
608 int fd = xsocket(PF_NETLINK, SOCK_DGRAM, NETLINK_ROUTE);
610 memset(&addr, 0, sizeof(addr));
611 addr.nl_family = AF_NETLINK;
612 addr.nl_groups = RTMGRP_LINK;
613 addr.nl_pid = getpid();
615 xbind(fd, (struct sockaddr*)&addr, sizeof(addr));
616 xmove_fd(fd, netlink_fd);
619 write_pidfile(pidfile_name);
621 /* this can't be moved before socket creation */
622 if (!(opts & FLAG_NO_SYSLOG)) {
623 openlog(applet_name, 0, LOG_DAEMON);
624 logmode |= LOGMODE_SYSLOG;
631 | (1 << SIGHUP ) /* why we ignore it? */
632 /* | (1 << SIGCHLD) - run_script does not use it anymore */
635 bb_error_msg("started: %s", bb_banner);
637 if (opts & FLAG_MONITOR) {
638 struct ifreq ifrequest;
639 set_ifreq_to_ifname(&ifrequest);
640 G.iface_exists = (network_ioctl(SIOCGIFINDEX, &ifrequest, NULL) == 0);
644 maybe_up_new_iface();
646 iface_status = detect_link();
647 if (iface_status == IFSTATUS_ERR)
649 iface_status_str = strstatus(iface_status);
651 if (opts & FLAG_MONITOR) {
652 bb_error_msg("interface %s",
653 G.iface_exists ? "exists"
654 : "doesn't exist, waiting");
656 /* else we assume it always exists, but don't mislead user
657 * by potentially lying that it really exists */
659 if (G.iface_exists) {
660 bb_error_msg("link is %s", iface_status_str);
663 if ((!(opts & FLAG_NO_STARTUP)
664 && iface_status == IFSTATUS_UP
666 || (opts & FLAG_INITIAL_DOWN)
668 if (run_script(iface_status_str) != 0)
673 netlink_pollfd[0].fd = netlink_fd;
674 netlink_pollfd[0].events = POLLIN;
677 int iface_status_old;
679 switch (bb_got_signal) {
692 if (poll(netlink_pollfd,
693 (opts & FLAG_MONITOR) ? 1 : 0,
699 bb_perror_msg("poll");
703 if ((opts & FLAG_MONITOR)
704 && (netlink_pollfd[0].revents & POLLIN)
706 int iface_exists_old;
708 iface_exists_old = G.iface_exists;
709 G.iface_exists = check_existence_through_netlink();
710 if (G.iface_exists < 0) /* error */
712 if (iface_exists_old != G.iface_exists) {
713 bb_error_msg("interface %sappeared",
714 G.iface_exists ? "" : "dis");
716 maybe_up_new_iface();
720 /* note: if !G.iface_exists, returns DOWN */
721 iface_status_old = iface_status;
722 iface_status = detect_link();
723 if (iface_status == IFSTATUS_ERR) {
724 if (!(opts & FLAG_MONITOR))
726 iface_status = IFSTATUS_DOWN;
728 iface_status_str = strstatus(iface_status);
730 if (iface_status_old != iface_status) {
731 bb_error_msg("link is %s", iface_status_str);
734 /* link restored its old status before
735 * we ran script. don't run the script: */
738 delay_time = monotonic_sec();
739 if (iface_status == IFSTATUS_UP)
740 delay_time += G.delay_up;
741 if (iface_status == IFSTATUS_DOWN)
742 delay_time += G.delay_down;
743 #if 0 /* if you are back in 1970... */
744 if (delay_time == 0) {
752 if (delay_time && (int)(monotonic_sec() - delay_time) >= 0) {
753 if (run_script(iface_status_str) != 0)
760 if (!(opts & FLAG_NO_SHUTDOWN)
761 && (iface_status == IFSTATUS_UP
762 || (iface_status == IFSTATUS_DOWN && delay_time)
765 setenv(IFPLUGD_ENV_PREVIOUS, strstatus(iface_status), 1);
766 setenv(IFPLUGD_ENV_CURRENT, strstatus(-1), 1);
767 run_script("down\0up"); /* reusing string */
771 remove_pidfile(pidfile_name);
772 bb_error_msg_and_die("exiting");