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.
10 //usage:#define ifplugd_trivial_usage
12 //usage:#define ifplugd_full_usage "\n\n"
13 //usage: "Network interface plug detection daemon\n"
14 //usage: "\n -n Don't daemonize"
15 //usage: "\n -s Don't log to syslog"
16 //usage: "\n -i IFACE Interface"
17 //usage: "\n -f/-F Treat link detection error as link down/link up"
18 //usage: "\n (otherwise exit on error)"
19 //usage: "\n -a Don't up interface at each link probe"
20 //usage: "\n -M Monitor creation/destruction of interface"
21 //usage: "\n (otherwise it must exist)"
22 //usage: "\n -r PROG Script to run"
23 //usage: "\n -x ARG Extra argument for script"
24 //usage: "\n -I Don't exit on nonzero exit code from script"
25 //usage: "\n -p Don't run \"up\" script on startup"
26 //usage: "\n -q Don't run \"down\" script on exit"
27 //usage: "\n -l Always run script on startup"
28 //usage: "\n -t SECS Poll time in seconds"
29 //usage: "\n -u SECS Delay before running script after link up"
30 //usage: "\n -d SECS Delay after link down"
31 //usage: "\n -m MODE API mode (mii, priv, ethtool, wlan, iff, auto)"
32 //usage: "\n -k Kill running daemon"
38 #include <linux/mii.h>
39 #include <linux/ethtool.h>
40 #ifdef HAVE_NET_ETHERNET_H
42 * In file included from /usr/include/net/ethernet.h:10,
43 * from networking/ifplugd.c:41:
44 * /usr/include/netinet/if_ether.h:96: error: redefinition of 'struct ethhdr'
46 * Build succeeds without it on musl. Commented it out.
47 * If on your system you need it, consider removing <linux/ethtool.h>
48 * and copy-pasting its definitions here (<linux/ethtool.h> is what pulls in
49 * conflicting definition of struct ethhdr on musl).
51 /* # include <net/ethernet.h> */
53 #include <linux/netlink.h>
54 #include <linux/rtnetlink.h>
55 #include <linux/sockios.h>
59 #include <linux/wireless.h>
66 From initial port to busybox, removed most of the redundancy by
67 converting implementation of a polymorphic interface to the strict
68 functional style. The main role is run a script when link state
69 changed, other activities like audio signal or detailed reports
70 are on the script itself.
72 One questionable point of the design is netlink usage:
74 We have 1 second timeout by default to poll the link status,
75 it is short enough so that there are no real benefits in
76 using netlink to get "instantaneous" interface creation/deletion
77 notifications. We can check for interface existence by just
78 doing some fast ioctl using its name.
80 Netlink code then can be just dropped (1k or more?)
84 #define IFPLUGD_ENV_PREVIOUS "IFPLUGD_PREVIOUS"
85 #define IFPLUGD_ENV_CURRENT "IFPLUGD_CURRENT"
88 FLAG_NO_AUTO = 1 << 0, // -a, Do not enable interface automatically
89 FLAG_NO_DAEMON = 1 << 1, // -n, Do not daemonize
90 FLAG_NO_SYSLOG = 1 << 2, // -s, Do not use syslog, use stderr instead
91 FLAG_IGNORE_FAIL = 1 << 3, // -f, Ignore detection failure, retry instead (failure is treated as DOWN)
92 FLAG_IGNORE_FAIL_POSITIVE = 1 << 4, // -F, Ignore detection failure, retry instead (failure is treated as UP)
93 FLAG_IFACE = 1 << 5, // -i, Specify ethernet interface
94 FLAG_RUN = 1 << 6, // -r, Specify program to execute
95 FLAG_IGNORE_RETVAL = 1 << 7, // -I, Don't exit on nonzero return value of program executed
96 FLAG_POLL_TIME = 1 << 8, // -t, Specify poll time in seconds
97 FLAG_DELAY_UP = 1 << 9, // -u, Specify delay for configuring interface
98 FLAG_DELAY_DOWN = 1 << 10, // -d, Specify delay for deconfiguring interface
99 FLAG_API_MODE = 1 << 11, // -m, Force API mode (mii, priv, ethtool, wlan, auto)
100 FLAG_NO_STARTUP = 1 << 12, // -p, Don't run script on daemon startup
101 FLAG_NO_SHUTDOWN = 1 << 13, // -q, Don't run script on daemon quit
102 FLAG_INITIAL_DOWN = 1 << 14, // -l, Run "down" script on startup if no cable is detected
103 FLAG_EXTRA_ARG = 1 << 15, // -x, Specify an extra argument for action script
104 FLAG_MONITOR = 1 << 16, // -M, Use interface monitoring
105 #if ENABLE_FEATURE_PIDFILE
106 FLAG_KILL = 1 << 17, // -k, Kill a running daemon
109 #if ENABLE_FEATURE_PIDFILE
110 # define OPTION_STR "+ansfFi:r:It:+u:+d:+m:pqlx:Mk"
112 # define OPTION_STR "+ansfFi:r:It:+u:+d:+m:pqlx:M"
115 enum { // interface status
121 enum { // constant fds
127 smallint iface_last_status;
128 smallint iface_prev_status;
129 smallint iface_exists;
130 smallint api_method_num;
132 /* Used in getopt32, must have sizeof == sizeof(int) */
138 const char *api_mode;
139 const char *script_name;
140 const char *extra_arg;
142 #define G (*ptr_to_globals)
143 #define INIT_G() do { \
144 SET_PTR_TO_GLOBALS(xzalloc(sizeof(G))); \
145 G.iface_last_status = -1; \
146 G.iface_exists = 1; \
151 G.script_name = "/etc/ifplugd/ifplugd.action"; \
155 /* Utility routines */
157 static void set_ifreq_to_ifname(struct ifreq *ifreq)
159 memset(ifreq, 0, sizeof(struct ifreq));
160 strncpy_IFNAMSIZ(ifreq->ifr_name, G.iface);
163 static int network_ioctl(int request, void* data, const char *errmsg)
165 int r = ioctl(ioctl_fd, request, data);
167 bb_perror_msg("%s failed", errmsg);
171 /* Link detection routines and table */
173 static smallint detect_link_mii(void)
175 /* char buffer instead of bona-fide struct avoids aliasing warning */
176 char buf[sizeof(struct ifreq)];
177 struct ifreq *const ifreq = (void *)buf;
179 struct mii_ioctl_data *mii = (void *)&ifreq->ifr_data;
181 set_ifreq_to_ifname(ifreq);
183 if (network_ioctl(SIOCGMIIPHY, ifreq, "SIOCGMIIPHY") < 0) {
189 if (network_ioctl(SIOCGMIIREG, ifreq, "SIOCGMIIREG") < 0) {
193 return (mii->val_out & 0x0004) ? IFSTATUS_UP : IFSTATUS_DOWN;
196 static smallint detect_link_priv(void)
198 /* char buffer instead of bona-fide struct avoids aliasing warning */
199 char buf[sizeof(struct ifreq)];
200 struct ifreq *const ifreq = (void *)buf;
202 struct mii_ioctl_data *mii = (void *)&ifreq->ifr_data;
204 set_ifreq_to_ifname(ifreq);
206 if (network_ioctl(SIOCDEVPRIVATE, ifreq, "SIOCDEVPRIVATE") < 0) {
212 if (network_ioctl(SIOCDEVPRIVATE+1, ifreq, "SIOCDEVPRIVATE+1") < 0) {
216 return (mii->val_out & 0x0004) ? IFSTATUS_UP : IFSTATUS_DOWN;
219 static smallint detect_link_ethtool(void)
222 struct ethtool_value edata;
224 set_ifreq_to_ifname(&ifreq);
226 edata.cmd = ETHTOOL_GLINK;
227 ifreq.ifr_data = (void*) &edata;
229 if (network_ioctl(SIOCETHTOOL, &ifreq, "ETHTOOL_GLINK") < 0) {
233 return edata.data ? IFSTATUS_UP : IFSTATUS_DOWN;
236 static smallint detect_link_iff(void)
240 set_ifreq_to_ifname(&ifreq);
242 if (network_ioctl(SIOCGIFFLAGS, &ifreq, "SIOCGIFFLAGS") < 0) {
246 /* If IFF_UP is not set (interface is down), IFF_RUNNING is never set
247 * regardless of link status. Simply continue to report last status -
248 * no point in reporting spurious link downs if interface is disabled
249 * by admin. When/if it will be brought up,
250 * we'll report real link status.
252 if (!(ifreq.ifr_flags & IFF_UP) && G.iface_last_status != IFSTATUS_ERR)
253 return G.iface_last_status;
255 return (ifreq.ifr_flags & IFF_RUNNING) ? IFSTATUS_UP : IFSTATUS_DOWN;
258 static smallint detect_link_wlan(void)
261 struct iwreq iwrequest;
262 uint8_t mac[ETH_ALEN];
264 memset(&iwrequest, 0, sizeof(iwrequest));
265 strncpy_IFNAMSIZ(iwrequest.ifr_ifrn.ifrn_name, G.iface);
267 if (network_ioctl(SIOCGIWAP, &iwrequest, "SIOCGIWAP") < 0) {
271 memcpy(mac, &iwrequest.u.ap_addr.sa_data, ETH_ALEN);
273 if (mac[0] == 0xFF || mac[0] == 0x44 || mac[0] == 0x00) {
274 for (i = 1; i < ETH_ALEN; ++i) {
275 if (mac[i] != mac[0])
278 return IFSTATUS_DOWN;
293 static const char api_modes[] ALIGN1 = "empwia";
295 static const struct {
297 smallint (*func)(void);
299 { "SIOCETHTOOL" , &detect_link_ethtool },
300 { "SIOCGMIIPHY" , &detect_link_mii },
301 { "SIOCDEVPRIVATE" , &detect_link_priv },
302 { "wireless extension", &detect_link_wlan },
303 { "IFF_RUNNING" , &detect_link_iff },
306 static const char *strstatus(int status)
308 if (status == IFSTATUS_ERR)
310 return "down\0up" + (status * 5);
313 static int run_script(const char *action)
315 char *env_PREVIOUS, *env_CURRENT;
319 bb_error_msg("executing '%s %s %s'", G.script_name, G.iface, action);
321 argv[0] = (char*) G.script_name;
322 argv[1] = (char*) G.iface;
323 argv[2] = (char*) action;
324 argv[3] = (char*) G.extra_arg;
327 env_PREVIOUS = xasprintf("%s=%s", IFPLUGD_ENV_PREVIOUS, strstatus(G.iface_prev_status));
328 putenv(env_PREVIOUS);
329 env_CURRENT = xasprintf("%s=%s", IFPLUGD_ENV_CURRENT, strstatus(G.iface_last_status));
332 /* r < 0 - can't exec, 0 <= r < 0x180 - exited, >=0x180 - killed by sig (r-0x180) */
333 r = spawn_and_wait(argv);
335 unsetenv(IFPLUGD_ENV_PREVIOUS);
336 unsetenv(IFPLUGD_ENV_CURRENT);
340 bb_error_msg("exit code: %d", r & 0xff);
341 return (option_mask32 & FLAG_IGNORE_RETVAL) ? 0 : r;
344 static void up_iface(void)
346 struct ifreq ifrequest;
351 set_ifreq_to_ifname(&ifrequest);
352 if (network_ioctl(SIOCGIFFLAGS, &ifrequest, "getting interface flags") < 0) {
357 if (!(ifrequest.ifr_flags & IFF_UP)) {
358 ifrequest.ifr_flags |= IFF_UP;
359 /* Let user know we mess up with interface */
360 bb_error_msg("upping interface");
361 if (network_ioctl(SIOCSIFFLAGS, &ifrequest, "setting interface flags") < 0) {
369 #if 0 /* why do we mess with IP addr? It's not our business */
370 if (network_ioctl(SIOCGIFADDR, &ifrequest, "can't get interface address") < 0) {
371 } else if (ifrequest.ifr_addr.sa_family != AF_INET) {
372 bb_perror_msg("the interface is not IP-based");
374 ((struct sockaddr_in*)(&ifrequest.ifr_addr))->sin_addr.s_addr = INADDR_ANY;
375 network_ioctl(SIOCSIFADDR, &ifrequest, "can't set interface address");
377 network_ioctl(SIOCGIFFLAGS, &ifrequest, "can't get interface flags");
381 static void maybe_up_new_iface(void)
383 if (!(option_mask32 & FLAG_NO_AUTO))
387 struct ifreq ifrequest;
388 struct ethtool_drvinfo driver_info;
390 set_ifreq_to_ifname(&ifrequest);
391 driver_info.cmd = ETHTOOL_GDRVINFO;
392 ifrequest.ifr_data = &driver_info;
393 if (network_ioctl(SIOCETHTOOL, &ifrequest, NULL) == 0) {
394 char buf[sizeof("/xx:xx:xx:xx:xx:xx")];
398 set_ifreq_to_ifname(&ifrequest);
399 if (network_ioctl(SIOCGIFHWADDR, &ifrequest, NULL) == 0) {
400 sprintf(buf, "/%02X:%02X:%02X:%02X:%02X:%02X",
401 (uint8_t)(ifrequest.ifr_hwaddr.sa_data[0]),
402 (uint8_t)(ifrequest.ifr_hwaddr.sa_data[1]),
403 (uint8_t)(ifrequest.ifr_hwaddr.sa_data[2]),
404 (uint8_t)(ifrequest.ifr_hwaddr.sa_data[3]),
405 (uint8_t)(ifrequest.ifr_hwaddr.sa_data[4]),
406 (uint8_t)(ifrequest.ifr_hwaddr.sa_data[5]));
409 bb_error_msg("using interface %s%s with driver<%s> (version: %s)",
410 G.iface, buf, driver_info.driver, driver_info.version);
413 if (G.api_mode[0] == 'a')
414 G.api_method_num = API_AUTO;
417 static smallint detect_link(void)
422 return (option_mask32 & FLAG_MONITOR) ? IFSTATUS_DOWN : IFSTATUS_ERR;
424 /* Some drivers can't detect link status when the interface is down.
425 * I imagine detect_link_iff() is the most vulnerable.
426 * That's why -a "noauto" in an option, not a hardwired behavior.
428 if (!(option_mask32 & FLAG_NO_AUTO))
431 if (G.api_method_num == API_AUTO) {
435 sv_logmode = logmode;
436 for (i = 0; i < ARRAY_SIZE(method_table); i++) {
437 logmode = LOGMODE_NONE;
438 status = method_table[i].func();
439 logmode = sv_logmode;
440 if (status != IFSTATUS_ERR) {
441 G.api_method_num = i;
442 bb_error_msg("using %s detection mode", method_table[i].name);
447 status = method_table[G.api_method_num].func();
450 if (status == IFSTATUS_ERR) {
451 if (option_mask32 & FLAG_IGNORE_FAIL)
452 status = IFSTATUS_DOWN;
453 else if (option_mask32 & FLAG_IGNORE_FAIL_POSITIVE)
454 status = IFSTATUS_UP;
455 else if (G.api_mode[0] == 'a')
456 bb_error_msg("can't detect link status");
459 if (status != G.iface_last_status) {
460 G.iface_prev_status = G.iface_last_status;
461 G.iface_last_status = status;
467 static NOINLINE int check_existence_through_netlink(void)
470 /* Buffer was 1K, but on linux-3.9.9 it was reported to be too small.
471 * netlink.h: "limit to 8K to avoid MSG_TRUNC when PAGE_SIZE is very large".
472 * Note: on error returns (-1) we exit, no need to free replybuf.
474 enum { BUF_SIZE = 8 * 1024 };
475 char *replybuf = xmalloc(BUF_SIZE);
477 iface_len = strlen(G.iface);
479 struct nlmsghdr *mhdr;
482 bytes = recv(netlink_fd, replybuf, BUF_SIZE, MSG_DONTWAIT);
488 bb_perror_msg("netlink: recv");
492 mhdr = (struct nlmsghdr*)replybuf;
494 if (!NLMSG_OK(mhdr, bytes)) {
495 bb_error_msg("netlink packet too small or truncated");
499 if (mhdr->nlmsg_type == RTM_NEWLINK || mhdr->nlmsg_type == RTM_DELLINK) {
503 if (mhdr->nlmsg_len < NLMSG_LENGTH(sizeof(struct ifinfomsg))) {
504 bb_error_msg("netlink packet too small or truncated");
508 attr = IFLA_RTA(NLMSG_DATA(mhdr));
509 attr_len = IFLA_PAYLOAD(mhdr);
511 while (RTA_OK(attr, attr_len)) {
512 if (attr->rta_type == IFLA_IFNAME) {
513 int len = RTA_PAYLOAD(attr);
517 && strncmp(G.iface, RTA_DATA(attr), len) == 0
519 G.iface_exists = (mhdr->nlmsg_type == RTM_NEWLINK);
522 attr = RTA_NEXT(attr, attr_len);
526 mhdr = NLMSG_NEXT(mhdr, bytes);
532 return G.iface_exists;
535 #if ENABLE_FEATURE_PIDFILE
536 static NOINLINE pid_t read_pid(const char *filename)
541 len = open_read_close(filename, buf, 127);
544 /* returns ULONG_MAX on error => -1 */
545 return bb_strtoul(buf, NULL, 10);
551 int ifplugd_main(int argc, char **argv) MAIN_EXTERNALLY_VISIBLE;
552 int ifplugd_main(int argc UNUSED_PARAM, char **argv)
556 const char *iface_status_str;
557 struct pollfd netlink_pollfd[1];
559 const char *api_mode_found;
560 #if ENABLE_FEATURE_PIDFILE
562 pid_t pid_from_pidfile;
567 opts = getopt32(argv, OPTION_STR,
568 &G.iface, &G.script_name, &G.poll_time, &G.delay_up,
569 &G.delay_down, &G.api_mode, &G.extra_arg);
572 applet_name = xasprintf("ifplugd(%s)", G.iface);
574 #if ENABLE_FEATURE_PIDFILE
575 pidfile_name = xasprintf(CONFIG_PID_FILE_PATH "/ifplugd.%s.pid", G.iface);
576 pid_from_pidfile = read_pid(pidfile_name);
578 if (opts & FLAG_KILL) {
579 if (pid_from_pidfile > 0)
580 /* Upstream tool use SIGINT for -k */
581 kill(pid_from_pidfile, SIGINT);
585 if (pid_from_pidfile > 0 && kill(pid_from_pidfile, 0) == 0)
586 bb_error_msg_and_die("daemon already running");
589 api_mode_found = strchr(api_modes, G.api_mode[0]);
591 bb_error_msg_and_die("unknown API mode '%s'", G.api_mode);
592 G.api_method_num = api_mode_found - api_modes;
594 if (!(opts & FLAG_NO_DAEMON))
595 bb_daemonize_or_rexec(DAEMON_CHDIR_ROOT, argv);
597 xmove_fd(xsocket(AF_INET, SOCK_DGRAM, 0), ioctl_fd);
598 if (opts & FLAG_MONITOR) {
599 struct sockaddr_nl addr;
600 int fd = xsocket(PF_NETLINK, SOCK_DGRAM, NETLINK_ROUTE);
602 memset(&addr, 0, sizeof(addr));
603 addr.nl_family = AF_NETLINK;
604 addr.nl_groups = RTMGRP_LINK;
605 addr.nl_pid = getpid();
607 xbind(fd, (struct sockaddr*)&addr, sizeof(addr));
608 xmove_fd(fd, netlink_fd);
611 write_pidfile(pidfile_name);
613 /* this can't be moved before socket creation */
614 if (!(opts & FLAG_NO_SYSLOG)) {
615 openlog(applet_name, 0, LOG_DAEMON);
616 logmode |= LOGMODE_SYSLOG;
623 | (1 << SIGHUP ) /* why we ignore it? */
624 /* | (1 << SIGCHLD) - run_script does not use it anymore */
627 bb_error_msg("started: %s", bb_banner);
629 if (opts & FLAG_MONITOR) {
630 struct ifreq ifrequest;
631 set_ifreq_to_ifname(&ifrequest);
632 G.iface_exists = (network_ioctl(SIOCGIFINDEX, &ifrequest, NULL) == 0);
636 maybe_up_new_iface();
638 iface_status = detect_link();
639 if (iface_status == IFSTATUS_ERR)
641 iface_status_str = strstatus(iface_status);
643 if (opts & FLAG_MONITOR) {
644 bb_error_msg("interface %s",
645 G.iface_exists ? "exists"
646 : "doesn't exist, waiting");
648 /* else we assume it always exists, but don't mislead user
649 * by potentially lying that it really exists */
651 if (G.iface_exists) {
652 bb_error_msg("link is %s", iface_status_str);
655 if ((!(opts & FLAG_NO_STARTUP)
656 && iface_status == IFSTATUS_UP
658 || (opts & FLAG_INITIAL_DOWN)
660 if (run_script(iface_status_str) != 0)
665 netlink_pollfd[0].fd = netlink_fd;
666 netlink_pollfd[0].events = POLLIN;
669 int iface_status_old;
671 switch (bb_got_signal) {
684 if (poll(netlink_pollfd,
685 (opts & FLAG_MONITOR) ? 1 : 0,
691 bb_perror_msg("poll");
695 if ((opts & FLAG_MONITOR)
696 && (netlink_pollfd[0].revents & POLLIN)
698 int iface_exists_old;
700 iface_exists_old = G.iface_exists;
701 G.iface_exists = check_existence_through_netlink();
702 if (G.iface_exists < 0) /* error */
704 if (iface_exists_old != G.iface_exists) {
705 bb_error_msg("interface %sappeared",
706 G.iface_exists ? "" : "dis");
708 maybe_up_new_iface();
712 /* note: if !G.iface_exists, returns DOWN */
713 iface_status_old = iface_status;
714 iface_status = detect_link();
715 if (iface_status == IFSTATUS_ERR) {
716 if (!(opts & FLAG_MONITOR))
718 iface_status = IFSTATUS_DOWN;
720 iface_status_str = strstatus(iface_status);
722 if (iface_status_old != iface_status) {
723 bb_error_msg("link is %s", iface_status_str);
726 /* link restored its old status before
727 * we ran script. don't run the script: */
730 delay_time = monotonic_sec();
731 if (iface_status == IFSTATUS_UP)
732 delay_time += G.delay_up;
733 if (iface_status == IFSTATUS_DOWN)
734 delay_time += G.delay_down;
735 #if 0 /* if you are back in 1970... */
736 if (delay_time == 0) {
744 if (delay_time && (int)(monotonic_sec() - delay_time) >= 0) {
745 if (run_script(iface_status_str) != 0)
752 if (!(opts & FLAG_NO_SHUTDOWN)
753 && (iface_status == IFSTATUS_UP
754 || (iface_status == IFSTATUS_DOWN && delay_time)
757 setenv(IFPLUGD_ENV_PREVIOUS, strstatus(iface_status), 1);
758 setenv(IFPLUGD_ENV_CURRENT, strstatus(-1), 1);
759 run_script("down\0up"); /* reusing string */
763 remove_pidfile(pidfile_name);
764 bb_error_msg_and_die("exiting");