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)
365 #if 0 /* why do we mess with IP addr? It's not our business */
366 if (network_ioctl(SIOCGIFADDR, &ifrequest, "can't get interface address") < 0) {
367 } else if (ifrequest.ifr_addr.sa_family != AF_INET) {
368 bb_perror_msg("the interface is not IP-based");
370 ((struct sockaddr_in*)(&ifrequest.ifr_addr))->sin_addr.s_addr = INADDR_ANY;
371 network_ioctl(SIOCSIFADDR, &ifrequest, "can't set interface address");
373 network_ioctl(SIOCGIFFLAGS, &ifrequest, "can't get interface flags");
377 static void maybe_up_new_iface(void)
379 if (!(option_mask32 & FLAG_NO_AUTO))
383 struct ifreq ifrequest;
384 struct ethtool_drvinfo driver_info;
386 set_ifreq_to_ifname(&ifrequest);
387 driver_info.cmd = ETHTOOL_GDRVINFO;
388 ifrequest.ifr_data = &driver_info;
389 if (network_ioctl(SIOCETHTOOL, &ifrequest, NULL) == 0) {
390 char buf[sizeof("/xx:xx:xx:xx:xx:xx")];
394 set_ifreq_to_ifname(&ifrequest);
395 if (network_ioctl(SIOCGIFHWADDR, &ifrequest, NULL) == 0) {
396 sprintf(buf, "/%02X:%02X:%02X:%02X:%02X:%02X",
397 (uint8_t)(ifrequest.ifr_hwaddr.sa_data[0]),
398 (uint8_t)(ifrequest.ifr_hwaddr.sa_data[1]),
399 (uint8_t)(ifrequest.ifr_hwaddr.sa_data[2]),
400 (uint8_t)(ifrequest.ifr_hwaddr.sa_data[3]),
401 (uint8_t)(ifrequest.ifr_hwaddr.sa_data[4]),
402 (uint8_t)(ifrequest.ifr_hwaddr.sa_data[5]));
405 bb_error_msg("using interface %s%s with driver<%s> (version: %s)",
406 G.iface, buf, driver_info.driver, driver_info.version);
409 if (G.api_mode[0] == 'a')
410 G.api_method_num = API_AUTO;
413 static smallint detect_link(void)
418 return (option_mask32 & FLAG_MONITOR) ? IFSTATUS_DOWN : IFSTATUS_ERR;
420 /* Some drivers can't detect link status when the interface is down.
421 * I imagine detect_link_iff() is the most vulnerable.
422 * That's why -a "noauto" in an option, not a hardwired behavior.
424 if (!(option_mask32 & FLAG_NO_AUTO))
427 if (G.api_method_num == API_AUTO) {
431 sv_logmode = logmode;
432 for (i = 0; i < ARRAY_SIZE(method_table); i++) {
433 logmode = LOGMODE_NONE;
434 status = method_table[i].func();
435 logmode = sv_logmode;
436 if (status != IFSTATUS_ERR) {
437 G.api_method_num = i;
438 bb_error_msg("using %s detection mode", method_table[i].name);
443 status = method_table[G.api_method_num].func();
446 if (status == IFSTATUS_ERR) {
447 if (option_mask32 & FLAG_IGNORE_FAIL)
448 status = IFSTATUS_DOWN;
449 else if (option_mask32 & FLAG_IGNORE_FAIL_POSITIVE)
450 status = IFSTATUS_UP;
451 else if (G.api_mode[0] == 'a')
452 bb_error_msg("can't detect link status");
455 if (status != G.iface_last_status) {
456 G.iface_prev_status = G.iface_last_status;
457 G.iface_last_status = status;
463 static NOINLINE int check_existence_through_netlink(void)
466 /* Buffer was 1K, but on linux-3.9.9 it was reported to be too small.
467 * netlink.h: "limit to 8K to avoid MSG_TRUNC when PAGE_SIZE is very large".
468 * Note: on error returns (-1) we exit, no need to free replybuf.
470 enum { BUF_SIZE = 8 * 1024 };
471 char *replybuf = xmalloc(BUF_SIZE);
473 iface_len = strlen(G.iface);
475 struct nlmsghdr *mhdr;
478 bytes = recv(netlink_fd, replybuf, BUF_SIZE, MSG_DONTWAIT);
484 bb_perror_msg("netlink: recv");
488 mhdr = (struct nlmsghdr*)replybuf;
490 if (!NLMSG_OK(mhdr, bytes)) {
491 bb_error_msg("netlink packet too small or truncated");
495 if (mhdr->nlmsg_type == RTM_NEWLINK || mhdr->nlmsg_type == RTM_DELLINK) {
499 if (mhdr->nlmsg_len < NLMSG_LENGTH(sizeof(struct ifinfomsg))) {
500 bb_error_msg("netlink packet too small or truncated");
504 attr = IFLA_RTA(NLMSG_DATA(mhdr));
505 attr_len = IFLA_PAYLOAD(mhdr);
507 while (RTA_OK(attr, attr_len)) {
508 if (attr->rta_type == IFLA_IFNAME) {
509 int len = RTA_PAYLOAD(attr);
513 && strncmp(G.iface, RTA_DATA(attr), len) == 0
515 G.iface_exists = (mhdr->nlmsg_type == RTM_NEWLINK);
518 attr = RTA_NEXT(attr, attr_len);
522 mhdr = NLMSG_NEXT(mhdr, bytes);
528 return G.iface_exists;
531 #if ENABLE_FEATURE_PIDFILE
532 static NOINLINE pid_t read_pid(const char *filename)
537 len = open_read_close(filename, buf, 127);
540 /* returns ULONG_MAX on error => -1 */
541 return bb_strtoul(buf, NULL, 10);
547 int ifplugd_main(int argc, char **argv) MAIN_EXTERNALLY_VISIBLE;
548 int ifplugd_main(int argc UNUSED_PARAM, char **argv)
552 const char *iface_status_str;
553 struct pollfd netlink_pollfd[1];
555 const char *api_mode_found;
556 #if ENABLE_FEATURE_PIDFILE
558 pid_t pid_from_pidfile;
563 opts = getopt32(argv, OPTION_STR,
564 &G.iface, &G.script_name, &G.poll_time, &G.delay_up,
565 &G.delay_down, &G.api_mode, &G.extra_arg);
568 applet_name = xasprintf("ifplugd(%s)", G.iface);
570 #if ENABLE_FEATURE_PIDFILE
571 pidfile_name = xasprintf(CONFIG_PID_FILE_PATH "/ifplugd.%s.pid", G.iface);
572 pid_from_pidfile = read_pid(pidfile_name);
574 if (opts & FLAG_KILL) {
575 if (pid_from_pidfile > 0)
576 /* Upstream tool use SIGINT for -k */
577 kill(pid_from_pidfile, SIGINT);
581 if (pid_from_pidfile > 0 && kill(pid_from_pidfile, 0) == 0)
582 bb_error_msg_and_die("daemon already running");
585 api_mode_found = strchr(api_modes, G.api_mode[0]);
587 bb_error_msg_and_die("unknown API mode '%s'", G.api_mode);
588 G.api_method_num = api_mode_found - api_modes;
590 if (!(opts & FLAG_NO_DAEMON))
591 bb_daemonize_or_rexec(DAEMON_CHDIR_ROOT, argv);
593 xmove_fd(xsocket(AF_INET, SOCK_DGRAM, 0), ioctl_fd);
594 if (opts & FLAG_MONITOR) {
595 struct sockaddr_nl addr;
596 int fd = xsocket(PF_NETLINK, SOCK_DGRAM, NETLINK_ROUTE);
598 memset(&addr, 0, sizeof(addr));
599 addr.nl_family = AF_NETLINK;
600 addr.nl_groups = RTMGRP_LINK;
601 addr.nl_pid = getpid();
603 xbind(fd, (struct sockaddr*)&addr, sizeof(addr));
604 xmove_fd(fd, netlink_fd);
607 write_pidfile(pidfile_name);
609 /* this can't be moved before socket creation */
610 if (!(opts & FLAG_NO_SYSLOG)) {
611 openlog(applet_name, 0, LOG_DAEMON);
612 logmode |= LOGMODE_SYSLOG;
619 | (1 << SIGHUP ) /* why we ignore it? */
620 /* | (1 << SIGCHLD) - run_script does not use it anymore */
623 bb_error_msg("started: %s", bb_banner);
625 if (opts & FLAG_MONITOR) {
626 struct ifreq ifrequest;
627 set_ifreq_to_ifname(&ifrequest);
628 G.iface_exists = (network_ioctl(SIOCGIFINDEX, &ifrequest, NULL) == 0);
632 maybe_up_new_iface();
634 iface_status = detect_link();
635 if (iface_status == IFSTATUS_ERR)
637 iface_status_str = strstatus(iface_status);
639 if (opts & FLAG_MONITOR) {
640 bb_error_msg("interface %s",
641 G.iface_exists ? "exists"
642 : "doesn't exist, waiting");
644 /* else we assume it always exists, but don't mislead user
645 * by potentially lying that it really exists */
647 if (G.iface_exists) {
648 bb_error_msg("link is %s", iface_status_str);
651 if ((!(opts & FLAG_NO_STARTUP)
652 && iface_status == IFSTATUS_UP
654 || (opts & FLAG_INITIAL_DOWN)
656 if (run_script(iface_status_str) != 0)
661 netlink_pollfd[0].fd = netlink_fd;
662 netlink_pollfd[0].events = POLLIN;
665 int iface_status_old;
667 switch (bb_got_signal) {
680 if (poll(netlink_pollfd,
681 (opts & FLAG_MONITOR) ? 1 : 0,
687 bb_perror_msg("poll");
691 if ((opts & FLAG_MONITOR)
692 && (netlink_pollfd[0].revents & POLLIN)
694 int iface_exists_old;
696 iface_exists_old = G.iface_exists;
697 G.iface_exists = check_existence_through_netlink();
698 if (G.iface_exists < 0) /* error */
700 if (iface_exists_old != G.iface_exists) {
701 bb_error_msg("interface %sappeared",
702 G.iface_exists ? "" : "dis");
704 maybe_up_new_iface();
708 /* note: if !G.iface_exists, returns DOWN */
709 iface_status_old = iface_status;
710 iface_status = detect_link();
711 if (iface_status == IFSTATUS_ERR) {
712 if (!(opts & FLAG_MONITOR))
714 iface_status = IFSTATUS_DOWN;
716 iface_status_str = strstatus(iface_status);
718 if (iface_status_old != iface_status) {
719 bb_error_msg("link is %s", iface_status_str);
722 /* link restored its old status before
723 * we ran script. don't run the script: */
726 delay_time = monotonic_sec();
727 if (iface_status == IFSTATUS_UP)
728 delay_time += G.delay_up;
729 if (iface_status == IFSTATUS_DOWN)
730 delay_time += G.delay_down;
731 #if 0 /* if you are back in 1970... */
732 if (delay_time == 0) {
740 if (delay_time && (int)(monotonic_sec() - delay_time) >= 0) {
741 if (run_script(iface_status_str) != 0)
748 if (!(opts & FLAG_NO_SHUTDOWN)
749 && (iface_status == IFSTATUS_UP
750 || (iface_status == IFSTATUS_DOWN && delay_time)
753 setenv(IFPLUGD_ENV_PREVIOUS, strstatus(iface_status), 1);
754 setenv(IFPLUGD_ENV_CURRENT, strstatus(-1), 1);
755 run_script("down\0up"); /* reusing string */
759 remove_pidfile(pidfile_name);
760 bb_error_msg_and_die("exiting");