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 opt_complementary = "t+:u+:d+";
564 opts = getopt32(argv, OPTION_STR,
565 &G.iface, &G.script_name, &G.poll_time, &G.delay_up,
566 &G.delay_down, &G.api_mode, &G.extra_arg);
569 applet_name = xasprintf("ifplugd(%s)", G.iface);
571 #if ENABLE_FEATURE_PIDFILE
572 pidfile_name = xasprintf(CONFIG_PID_FILE_PATH "/ifplugd.%s.pid", G.iface);
573 pid_from_pidfile = read_pid(pidfile_name);
575 if (opts & FLAG_KILL) {
576 if (pid_from_pidfile > 0)
577 /* Upstream tool use SIGINT for -k */
578 kill(pid_from_pidfile, SIGINT);
582 if (pid_from_pidfile > 0 && kill(pid_from_pidfile, 0) == 0)
583 bb_error_msg_and_die("daemon already running");
586 api_mode_found = strchr(api_modes, G.api_mode[0]);
588 bb_error_msg_and_die("unknown API mode '%s'", G.api_mode);
589 G.api_method_num = api_mode_found - api_modes;
591 if (!(opts & FLAG_NO_DAEMON))
592 bb_daemonize_or_rexec(DAEMON_CHDIR_ROOT, argv);
594 xmove_fd(xsocket(AF_INET, SOCK_DGRAM, 0), ioctl_fd);
595 if (opts & FLAG_MONITOR) {
596 struct sockaddr_nl addr;
597 int fd = xsocket(PF_NETLINK, SOCK_DGRAM, NETLINK_ROUTE);
599 memset(&addr, 0, sizeof(addr));
600 addr.nl_family = AF_NETLINK;
601 addr.nl_groups = RTMGRP_LINK;
602 addr.nl_pid = getpid();
604 xbind(fd, (struct sockaddr*)&addr, sizeof(addr));
605 xmove_fd(fd, netlink_fd);
608 write_pidfile(pidfile_name);
610 /* this can't be moved before socket creation */
611 if (!(opts & FLAG_NO_SYSLOG)) {
612 openlog(applet_name, 0, LOG_DAEMON);
613 logmode |= LOGMODE_SYSLOG;
620 | (1 << SIGHUP ) /* why we ignore it? */
621 /* | (1 << SIGCHLD) - run_script does not use it anymore */
624 bb_error_msg("started: %s", bb_banner);
626 if (opts & FLAG_MONITOR) {
627 struct ifreq ifrequest;
628 set_ifreq_to_ifname(&ifrequest);
629 G.iface_exists = (network_ioctl(SIOCGIFINDEX, &ifrequest, NULL) == 0);
633 maybe_up_new_iface();
635 iface_status = detect_link();
636 if (iface_status == IFSTATUS_ERR)
638 iface_status_str = strstatus(iface_status);
640 if (opts & FLAG_MONITOR) {
641 bb_error_msg("interface %s",
642 G.iface_exists ? "exists"
643 : "doesn't exist, waiting");
645 /* else we assume it always exists, but don't mislead user
646 * by potentially lying that it really exists */
648 if (G.iface_exists) {
649 bb_error_msg("link is %s", iface_status_str);
652 if ((!(opts & FLAG_NO_STARTUP)
653 && iface_status == IFSTATUS_UP
655 || (opts & FLAG_INITIAL_DOWN)
657 if (run_script(iface_status_str) != 0)
662 netlink_pollfd[0].fd = netlink_fd;
663 netlink_pollfd[0].events = POLLIN;
666 int iface_status_old;
668 switch (bb_got_signal) {
681 if (poll(netlink_pollfd,
682 (opts & FLAG_MONITOR) ? 1 : 0,
688 bb_perror_msg("poll");
692 if ((opts & FLAG_MONITOR)
693 && (netlink_pollfd[0].revents & POLLIN)
695 int iface_exists_old;
697 iface_exists_old = G.iface_exists;
698 G.iface_exists = check_existence_through_netlink();
699 if (G.iface_exists < 0) /* error */
701 if (iface_exists_old != G.iface_exists) {
702 bb_error_msg("interface %sappeared",
703 G.iface_exists ? "" : "dis");
705 maybe_up_new_iface();
709 /* note: if !G.iface_exists, returns DOWN */
710 iface_status_old = iface_status;
711 iface_status = detect_link();
712 if (iface_status == IFSTATUS_ERR) {
713 if (!(opts & FLAG_MONITOR))
715 iface_status = IFSTATUS_DOWN;
717 iface_status_str = strstatus(iface_status);
719 if (iface_status_old != iface_status) {
720 bb_error_msg("link is %s", iface_status_str);
723 /* link restored its old status before
724 * we ran script. don't run the script: */
727 delay_time = monotonic_sec();
728 if (iface_status == IFSTATUS_UP)
729 delay_time += G.delay_up;
730 if (iface_status == IFSTATUS_DOWN)
731 delay_time += G.delay_down;
732 #if 0 /* if you are back in 1970... */
733 if (delay_time == 0) {
741 if (delay_time && (int)(monotonic_sec() - delay_time) >= 0) {
742 if (run_script(iface_status_str) != 0)
749 if (!(opts & FLAG_NO_SHUTDOWN)
750 && (iface_status == IFSTATUS_UP
751 || (iface_status == IFSTATUS_DOWN && delay_time)
754 setenv(IFPLUGD_ENV_PREVIOUS, strstatus(iface_status), 1);
755 setenv(IFPLUGD_ENV_CURRENT, strstatus(-1), 1);
756 run_script("down\0up"); /* reusing string */
760 remove_pidfile(pidfile_name);
761 bb_error_msg_and_die("exiting");