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 tarball for details.
13 #include <linux/mii.h>
14 #include <linux/ethtool.h>
15 #include <net/ethernet.h>
16 #include <linux/netlink.h>
17 #include <linux/rtnetlink.h>
18 #include <linux/sockios.h>
22 #include <linux/wireless.h>
25 From initial port to busybox, removed most of the redundancy by
26 converting implementation of a polymorphic interface to the strict
27 functional style. The main role is run a script when link state
28 changed, other activities like audio signal or detailed reports
29 are on the script itself.
31 One questionable point of the design is netlink usage:
33 We have 1 second timeout by default to poll the link status,
34 it is short enough so that there are no real benefits in
35 using netlink to get "instantaneous" interface creation/deletion
36 notifications. We can check for interface existence by just
37 doing some fast ioctl using its name.
39 Netlink code then can be just dropped (1k or more?)
43 #define IFPLUGD_ENV_PREVIOUS "IFPLUGD_PREVIOUS"
44 #define IFPLUGD_ENV_CURRENT "IFPLUGD_CURRENT"
47 FLAG_NO_AUTO = 1 << 0, // -a, Do not enable interface automatically
48 FLAG_NO_DAEMON = 1 << 1, // -n, Do not daemonize
49 FLAG_NO_SYSLOG = 1 << 2, // -s, Do not use syslog, use stderr instead
50 FLAG_IGNORE_FAIL = 1 << 3, // -f, Ignore detection failure, retry instead (failure is treated as DOWN)
51 FLAG_IGNORE_FAIL_POSITIVE = 1 << 4, // -F, Ignore detection failure, retry instead (failure is treated as UP)
52 FLAG_IFACE = 1 << 5, // -i, Specify ethernet interface
53 FLAG_RUN = 1 << 6, // -r, Specify program to execute
54 FLAG_IGNORE_RETVAL = 1 << 7, // -I, Don't exit on nonzero return value of program executed
55 FLAG_POLL_TIME = 1 << 8, // -t, Specify poll time in seconds
56 FLAG_DELAY_UP = 1 << 9, // -u, Specify delay for configuring interface
57 FLAG_DELAY_DOWN = 1 << 10, // -d, Specify delay for deconfiguring interface
58 FLAG_API_MODE = 1 << 11, // -m, Force API mode (mii, priv, ethtool, wlan, auto)
59 FLAG_NO_STARTUP = 1 << 12, // -p, Don't run script on daemon startup
60 FLAG_NO_SHUTDOWN = 1 << 13, // -q, Don't run script on daemon quit
61 FLAG_INITIAL_DOWN = 1 << 14, // -l, Run "down" script on startup if no cable is detected
62 FLAG_EXTRA_ARG = 1 << 15, // -x, Specify an extra argument for action script
63 FLAG_MONITOR = 1 << 16, // -M, Use interface monitoring
64 #if ENABLE_FEATURE_PIDFILE
65 FLAG_KILL = 1 << 17, // -k, Kill a running daemon
68 #if ENABLE_FEATURE_PIDFILE
69 # define OPTION_STR "+ansfFi:r:It:u:d:m:pqlx:Mk"
71 # define OPTION_STR "+ansfFi:r:It:u:d:m:pqlx:M"
83 enum { // interface status
89 enum { // constant fds
95 smallint iface_last_status;
96 smallint iface_exists;
98 /* Used in getopt32, must have sizeof == sizeof(int) */
104 const char *api_mode;
105 const char *script_name;
106 const char *extra_arg;
108 smallint (*detect_link_func)(void);
109 smallint (*cached_detect_link_func)(void);
111 #define G (*ptr_to_globals)
112 #define INIT_G() do { \
113 SET_PTR_TO_GLOBALS(xzalloc(sizeof(G))); \
114 G.iface_last_status = -1; \
115 G.iface_exists = 1; \
120 G.script_name = "/etc/ifplugd/ifplugd.action"; \
124 static int run_script(const char *action)
129 bb_error_msg("executing '%s %s %s'", G.script_name, G.iface, action);
133 argv[0] = (char*) G.script_name;
134 argv[1] = (char*) G.iface;
135 argv[2] = (char*) action;
136 argv[3] = (char*) G.extra_arg;
139 /* r < 0 - can't exec, 0 <= r < 0x180 - exited, >=0x180 - killed by sig (r-0x180) */
140 r = spawn_and_wait(argv);
142 bb_error_msg("exit code: %d", r & 0xff);
143 return (option_mask32 & FLAG_IGNORE_RETVAL) ? 0 : r;
147 struct fd_pair pipe_pair;
151 xpiped_pair(pipe_pair);
155 bb_perror_msg("fork");
161 xmove_fd(pipe_pair.wr, 1);
163 if (pipe_pair.rd > 2)
166 // umask(0022); // Set up a sane umask
168 execlp(G.script_name, G.script_name, G.iface, action, G.extra_arg, NULL);
176 if (bb_got_signal && bb_got_signal != SIGCHLD) {
177 bb_error_msg("killing child");
183 r = read(pipe_pair.rd, &buf[i], 1);
185 if (buf[i] == '\n' || i == sizeof(buf)-2 || r != 1) {
186 if (r == 1 && buf[i] != '\n')
192 bb_error_msg("client: %s", buf);
207 if (!WIFEXITED(r) || WEXITSTATUS(r) != 0) {
208 bb_error_msg("program execution failed, return value is %i",
210 return option_mask32 & FLAG_IGNORE_RETVAL ? 0 : WEXITSTATUS(r);
212 bb_error_msg("program executed successfully");
217 static int network_ioctl(int request, void* data, const char *errmsg)
219 int r = ioctl(ioctl_fd, request, data);
221 bb_perror_msg("%s failed", errmsg);
225 static void set_ifreq_to_ifname(struct ifreq *ifreq)
227 memset(ifreq, 0, sizeof(struct ifreq));
228 strncpy_IFNAMSIZ(ifreq->ifr_name, G.iface);
231 static const char *strstatus(int status)
233 if (status == IFSTATUS_ERR)
235 return "down\0up" + (status * 5);
238 static void up_iface(void)
240 struct ifreq ifrequest;
245 set_ifreq_to_ifname(&ifrequest);
246 if (network_ioctl(SIOCGIFFLAGS, &ifrequest, "getting interface flags") < 0) {
251 if (!(ifrequest.ifr_flags & IFF_UP)) {
252 ifrequest.ifr_flags |= IFF_UP;
253 /* Let user know we mess up with interface */
254 bb_error_msg("upping interface");
255 if (network_ioctl(SIOCSIFFLAGS, &ifrequest, "setting interface flags") < 0)
259 #if 0 /* why do we mess with IP addr? It's not our business */
260 if (network_ioctl(SIOCGIFADDR, &ifrequest, "can't get interface address") < 0) {
261 } else if (ifrequest.ifr_addr.sa_family != AF_INET) {
262 bb_perror_msg("the interface is not IP-based");
264 ((struct sockaddr_in*)(&ifrequest.ifr_addr))->sin_addr.s_addr = INADDR_ANY;
265 network_ioctl(SIOCSIFADDR, &ifrequest, "can't set interface address");
267 network_ioctl(SIOCGIFFLAGS, &ifrequest, "can't get interface flags");
271 static void maybe_up_new_iface(void)
273 if (!(option_mask32 & FLAG_NO_AUTO))
277 struct ifreq ifrequest;
278 struct ethtool_drvinfo driver_info;
280 set_ifreq_to_ifname(&ifrequest);
281 driver_info.cmd = ETHTOOL_GDRVINFO;
282 ifrequest.ifr_data = &driver_info;
283 if (network_ioctl(SIOCETHTOOL, &ifrequest, NULL) == 0) {
284 char buf[sizeof("/xx:xx:xx:xx:xx:xx")];
288 set_ifreq_to_ifname(&ifrequest);
289 if (network_ioctl(SIOCGIFHWADDR, &ifrequest, NULL) == 0) {
290 sprintf(buf, "/%02X:%02X:%02X:%02X:%02X:%02X",
291 (uint8_t)(ifrequest.ifr_hwaddr.sa_data[0]),
292 (uint8_t)(ifrequest.ifr_hwaddr.sa_data[1]),
293 (uint8_t)(ifrequest.ifr_hwaddr.sa_data[2]),
294 (uint8_t)(ifrequest.ifr_hwaddr.sa_data[3]),
295 (uint8_t)(ifrequest.ifr_hwaddr.sa_data[4]),
296 (uint8_t)(ifrequest.ifr_hwaddr.sa_data[5]));
299 bb_error_msg("using interface %s%s with driver<%s> (version: %s)",
300 G.iface, buf, driver_info.driver, driver_info.version);
304 G.cached_detect_link_func = NULL;
307 static smallint detect_link_mii(void)
310 struct mii_ioctl_data *mii = (void *)&ifreq.ifr_data;
312 set_ifreq_to_ifname(&ifreq);
314 if (network_ioctl(SIOCGMIIPHY, &ifreq, "SIOCGMIIPHY") < 0) {
320 if (network_ioctl(SIOCGMIIREG, &ifreq, "SIOCGMIIREG") < 0) {
324 return (mii->val_out & 0x0004) ? IFSTATUS_UP : IFSTATUS_DOWN;
327 static smallint detect_link_priv(void)
330 struct mii_ioctl_data *mii = (void *)&ifreq.ifr_data;
332 set_ifreq_to_ifname(&ifreq);
334 if (network_ioctl(SIOCDEVPRIVATE, &ifreq, "SIOCDEVPRIVATE") < 0) {
340 if (network_ioctl(SIOCDEVPRIVATE+1, &ifreq, "SIOCDEVPRIVATE+1") < 0) {
344 return (mii->val_out & 0x0004) ? IFSTATUS_UP : IFSTATUS_DOWN;
347 static smallint detect_link_ethtool(void)
350 struct ethtool_value edata;
352 set_ifreq_to_ifname(&ifreq);
354 edata.cmd = ETHTOOL_GLINK;
355 ifreq.ifr_data = (void*) &edata;
357 if (network_ioctl(SIOCETHTOOL, &ifreq, "ETHTOOL_GLINK") < 0) {
361 return edata.data ? IFSTATUS_UP : IFSTATUS_DOWN;
364 static smallint detect_link_iff(void)
368 set_ifreq_to_ifname(&ifreq);
370 if (network_ioctl(SIOCGIFFLAGS, &ifreq, "SIOCGIFFLAGS") < 0) {
374 /* If IFF_UP is not set (interface is down), IFF_RUNNING is never set
375 * regardless of link status. Simply continue to report last status -
376 * no point in reporting spurious link downs if interface is disabled
377 * by admin. When/if it will be brought up,
378 * we'll report real link status.
380 if (!(ifreq.ifr_flags & IFF_UP) && G.iface_last_status != IFSTATUS_ERR)
381 return G.iface_last_status;
383 return (ifreq.ifr_flags & IFF_RUNNING) ? IFSTATUS_UP : IFSTATUS_DOWN;
386 static smallint detect_link_wlan(void)
389 struct iwreq iwrequest;
390 uint8_t mac[ETH_ALEN];
392 memset(&iwrequest, 0, sizeof(iwrequest));
393 strncpy_IFNAMSIZ(iwrequest.ifr_ifrn.ifrn_name, G.iface);
395 if (network_ioctl(SIOCGIWAP, &iwrequest, "SIOCGIWAP") < 0) {
399 memcpy(mac, &iwrequest.u.ap_addr.sa_data, ETH_ALEN);
401 if (mac[0] == 0xFF || mac[0] == 0x44 || mac[0] == 0x00) {
402 for (i = 1; i < ETH_ALEN; ++i) {
403 if (mac[i] != mac[0])
406 return IFSTATUS_DOWN;
412 static smallint detect_link_auto(void)
414 static const struct {
416 smallint (*func)(void);
418 { "SIOCETHTOOL" , &detect_link_ethtool },
419 { "SIOCGMIIPHY" , &detect_link_mii },
420 { "SIOCDEVPRIVATE" , &detect_link_priv },
421 { "wireless extension", &detect_link_wlan },
422 { "IFF_RUNNING" , &detect_link_iff },
425 smallint iface_status;
428 if (G.cached_detect_link_func) {
429 iface_status = G.cached_detect_link_func();
430 if (iface_status != IFSTATUS_ERR)
434 sv_logmode = logmode;
435 for (i = 0; i < ARRAY_SIZE(method); i++) {
436 logmode = LOGMODE_NONE;
437 iface_status = method[i].func();
438 logmode = sv_logmode;
439 if (iface_status != IFSTATUS_ERR) {
440 G.cached_detect_link_func = method[i].func;
441 bb_error_msg("using %s detection mode", method[i].name);
448 static smallint detect_link(void)
453 return (option_mask32 & FLAG_MONITOR) ? IFSTATUS_DOWN : IFSTATUS_ERR;
455 /* Some drivers can't detect link status when the interface is down.
456 * I imagine detect_link_iff() is the most vulnerable.
457 * That's why -a "noauto" in an option, not a hardwired behavior.
459 if (!(option_mask32 & FLAG_NO_AUTO))
462 status = G.detect_link_func();
463 if (status == IFSTATUS_ERR) {
464 if (option_mask32 & FLAG_IGNORE_FAIL)
465 status = IFSTATUS_DOWN;
466 if (option_mask32 & FLAG_IGNORE_FAIL_POSITIVE)
467 status = IFSTATUS_UP;
470 if (status == IFSTATUS_ERR
471 && G.detect_link_func == detect_link_auto
473 bb_error_msg("can't detect link status");
476 if (status != G.iface_last_status) {
477 //TODO: is it safe to repeatedly do this?
478 setenv(IFPLUGD_ENV_PREVIOUS, strstatus(G.iface_last_status), 1);
479 setenv(IFPLUGD_ENV_CURRENT, strstatus(status), 1);
480 G.iface_last_status = status;
486 static NOINLINE int check_existence_through_netlink(void)
491 struct nlmsghdr *mhdr;
494 bytes = recv(netlink_fd, &replybuf, sizeof(replybuf), MSG_DONTWAIT);
497 return G.iface_exists;
501 bb_perror_msg("netlink: recv");
505 mhdr = (struct nlmsghdr*)replybuf;
507 if (!NLMSG_OK(mhdr, bytes)) {
508 bb_error_msg("netlink packet too small or truncated");
512 if (mhdr->nlmsg_type == RTM_NEWLINK || mhdr->nlmsg_type == RTM_DELLINK) {
516 if (mhdr->nlmsg_len < NLMSG_LENGTH(sizeof(struct ifinfomsg))) {
517 bb_error_msg("netlink packet too small or truncated");
521 attr = IFLA_RTA(NLMSG_DATA(mhdr));
522 attr_len = IFLA_PAYLOAD(mhdr);
524 while (RTA_OK(attr, attr_len)) {
525 if (attr->rta_type == IFLA_IFNAME) {
526 int len = RTA_PAYLOAD(attr);
530 if (strncmp(G.iface, RTA_DATA(attr), len) == 0) {
531 G.iface_exists = (mhdr->nlmsg_type == RTM_NEWLINK);
534 attr = RTA_NEXT(attr, attr_len);
538 mhdr = NLMSG_NEXT(mhdr, bytes);
542 return G.iface_exists;
545 static NOINLINE int netlink_open(void)
548 struct sockaddr_nl addr;
550 fd = xsocket(PF_NETLINK, SOCK_DGRAM, NETLINK_ROUTE);
552 memset(&addr, 0, sizeof(addr));
553 addr.nl_family = AF_NETLINK;
554 addr.nl_groups = RTMGRP_LINK;
555 addr.nl_pid = getpid();
557 xbind(fd, (struct sockaddr*)&addr, sizeof(addr));
562 #if ENABLE_FEATURE_PIDFILE
563 static NOINLINE pid_t read_pid(const char *filename)
568 len = open_read_close(filename, buf, 127);
571 /* returns ULONG_MAX on error => -1 */
572 return bb_strtoul(buf, NULL, 10);
578 int ifplugd_main(int argc, char **argv) MAIN_EXTERNALLY_VISIBLE;
579 int ifplugd_main(int argc UNUSED_PARAM, char **argv)
583 const char *iface_status_str;
584 struct pollfd netlink_pollfd[1];
586 #if ENABLE_FEATURE_PIDFILE
588 pid_t pid_from_pidfile;
593 opt_complementary = "t+:u+:d+";
594 opts = getopt32(argv, OPTION_STR,
595 &G.iface, &G.script_name, &G.poll_time, &G.delay_up,
596 &G.delay_down, &G.api_mode, &G.extra_arg);
599 applet_name = xasprintf("ifplugd(%s)", G.iface);
601 #if ENABLE_FEATURE_PIDFILE
602 pidfile_name = xasprintf(_PATH_VARRUN"ifplugd.%s.pid", G.iface);
603 pid_from_pidfile = read_pid(pidfile_name);
605 if (opts & FLAG_KILL) {
606 if (pid_from_pidfile > 0)
607 kill(pid_from_pidfile, SIGQUIT);
611 if (pid_from_pidfile > 0 && kill(pid_from_pidfile, 0) == 0)
612 bb_error_msg_and_die("daemon already running");
615 switch (G.api_mode[0]) {
617 G.detect_link_func = detect_link_auto;
620 G.detect_link_func = detect_link_ethtool;
623 G.detect_link_func = detect_link_mii;
626 G.detect_link_func = detect_link_priv;
629 G.detect_link_func = detect_link_wlan;
632 G.detect_link_func = detect_link_iff;
635 bb_error_msg_and_die("unknown API mode '%s'", G.api_mode);
638 if (!(opts & FLAG_NO_DAEMON))
639 bb_daemonize_or_rexec(DAEMON_CHDIR_ROOT, argv);
641 xmove_fd(xsocket(AF_INET, SOCK_DGRAM, 0), ioctl_fd);
642 if (opts & FLAG_MONITOR) {
643 xmove_fd(netlink_open(), netlink_fd);
646 write_pidfile(pidfile_name);
648 /* this can't be moved before socket creation */
649 if (!(opts & FLAG_NO_SYSLOG)) {
650 openlog(applet_name, 0, LOG_DAEMON);
651 logmode |= LOGMODE_SYSLOG;
658 | (1 << SIGHUP ) /* why we ignore it? */
659 /* | (1 << SIGCHLD) - run_script does not use it anymore */
662 bb_error_msg("started: %s", bb_banner);
664 if (opts & FLAG_MONITOR) {
665 struct ifreq ifrequest;
666 set_ifreq_to_ifname(&ifrequest);
667 G.iface_exists = (network_ioctl(SIOCGIFINDEX, &ifrequest, NULL) == 0);
671 maybe_up_new_iface();
673 iface_status = detect_link();
674 if (iface_status == IFSTATUS_ERR)
676 iface_status_str = strstatus(iface_status);
678 if (opts & FLAG_MONITOR) {
679 bb_error_msg("interface %s",
680 G.iface_exists ? "exists"
681 : "doesn't exist, waiting");
683 /* else we assume it always exists, but don't mislead user
684 * by potentially lying that it really exists */
686 if (G.iface_exists) {
687 bb_error_msg("link is %s", iface_status_str);
690 if ((!(opts & FLAG_NO_STARTUP)
691 && iface_status == IFSTATUS_UP
693 || (opts & FLAG_INITIAL_DOWN)
695 if (run_script(iface_status_str) != 0)
700 netlink_pollfd[0].fd = netlink_fd;
701 netlink_pollfd[0].events = POLLIN;
704 int iface_status_old;
705 int iface_exists_old;
707 switch (bb_got_signal) {
720 if (poll(netlink_pollfd,
721 (opts & FLAG_MONITOR) ? 1 : 0,
727 bb_perror_msg("poll");
731 iface_status_old = iface_status;
732 iface_exists_old = G.iface_exists;
734 if ((opts & FLAG_MONITOR)
735 && (netlink_pollfd[0].revents & POLLIN)
737 G.iface_exists = check_existence_through_netlink();
738 if (G.iface_exists < 0) /* error */
740 if (iface_exists_old != G.iface_exists) {
741 bb_error_msg("interface %sappeared",
742 G.iface_exists ? "" : "dis");
744 maybe_up_new_iface();
748 /* note: if !G.iface_exists, returns DOWN */
749 iface_status = detect_link();
750 if (iface_status == IFSTATUS_ERR) {
751 if (!(opts & FLAG_MONITOR))
753 iface_status = IFSTATUS_DOWN;
755 iface_status_str = strstatus(iface_status);
757 if (iface_status_old != iface_status) {
758 bb_error_msg("link is %s", iface_status_str);
761 /* link restored its old status before
762 * we run script. don't run the script: */
765 delay_time = monotonic_sec();
766 if (iface_status == IFSTATUS_UP)
767 delay_time += G.delay_up;
768 if (iface_status == IFSTATUS_DOWN)
769 delay_time += G.delay_down;
775 if (delay_time && (int)(monotonic_sec() - delay_time) >= 0) {
777 if (run_script(iface_status_str) != 0)
783 if (!(opts & FLAG_NO_SHUTDOWN)
784 && (iface_status == IFSTATUS_UP
785 || (iface_status == IFSTATUS_DOWN && delay_time)
788 setenv(IFPLUGD_ENV_PREVIOUS, strstatus(iface_status), 1);
789 setenv(IFPLUGD_ENV_CURRENT, strstatus(-1), 1);
790 run_script("down\0up"); /* reusing string */
794 remove_pidfile(pidfile_name);
795 bb_error_msg_and_die("exiting");