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 iface_len = strlen(G.iface);
493 struct nlmsghdr *mhdr;
496 bytes = recv(netlink_fd, &replybuf, sizeof(replybuf), MSG_DONTWAIT);
499 return G.iface_exists;
503 bb_perror_msg("netlink: recv");
507 mhdr = (struct nlmsghdr*)replybuf;
509 if (!NLMSG_OK(mhdr, bytes)) {
510 bb_error_msg("netlink packet too small or truncated");
514 if (mhdr->nlmsg_type == RTM_NEWLINK || mhdr->nlmsg_type == RTM_DELLINK) {
518 if (mhdr->nlmsg_len < NLMSG_LENGTH(sizeof(struct ifinfomsg))) {
519 bb_error_msg("netlink packet too small or truncated");
523 attr = IFLA_RTA(NLMSG_DATA(mhdr));
524 attr_len = IFLA_PAYLOAD(mhdr);
526 while (RTA_OK(attr, attr_len)) {
527 if (attr->rta_type == IFLA_IFNAME) {
528 int len = RTA_PAYLOAD(attr);
532 && strncmp(G.iface, RTA_DATA(attr), len) == 0
534 G.iface_exists = (mhdr->nlmsg_type == RTM_NEWLINK);
537 attr = RTA_NEXT(attr, attr_len);
541 mhdr = NLMSG_NEXT(mhdr, bytes);
545 return G.iface_exists;
548 static NOINLINE int netlink_open(void)
551 struct sockaddr_nl addr;
553 fd = xsocket(PF_NETLINK, SOCK_DGRAM, NETLINK_ROUTE);
555 memset(&addr, 0, sizeof(addr));
556 addr.nl_family = AF_NETLINK;
557 addr.nl_groups = RTMGRP_LINK;
558 addr.nl_pid = getpid();
560 xbind(fd, (struct sockaddr*)&addr, sizeof(addr));
565 #if ENABLE_FEATURE_PIDFILE
566 static NOINLINE pid_t read_pid(const char *filename)
571 len = open_read_close(filename, buf, 127);
574 /* returns ULONG_MAX on error => -1 */
575 return bb_strtoul(buf, NULL, 10);
581 int ifplugd_main(int argc, char **argv) MAIN_EXTERNALLY_VISIBLE;
582 int ifplugd_main(int argc UNUSED_PARAM, char **argv)
586 const char *iface_status_str;
587 struct pollfd netlink_pollfd[1];
589 #if ENABLE_FEATURE_PIDFILE
591 pid_t pid_from_pidfile;
596 opt_complementary = "t+:u+:d+";
597 opts = getopt32(argv, OPTION_STR,
598 &G.iface, &G.script_name, &G.poll_time, &G.delay_up,
599 &G.delay_down, &G.api_mode, &G.extra_arg);
602 applet_name = xasprintf("ifplugd(%s)", G.iface);
604 #if ENABLE_FEATURE_PIDFILE
605 pidfile_name = xasprintf(_PATH_VARRUN"ifplugd.%s.pid", G.iface);
606 pid_from_pidfile = read_pid(pidfile_name);
608 if (opts & FLAG_KILL) {
609 if (pid_from_pidfile > 0)
610 kill(pid_from_pidfile, SIGQUIT);
614 if (pid_from_pidfile > 0 && kill(pid_from_pidfile, 0) == 0)
615 bb_error_msg_and_die("daemon already running");
618 switch (G.api_mode[0]) {
620 G.detect_link_func = detect_link_auto;
623 G.detect_link_func = detect_link_ethtool;
626 G.detect_link_func = detect_link_mii;
629 G.detect_link_func = detect_link_priv;
632 G.detect_link_func = detect_link_wlan;
635 G.detect_link_func = detect_link_iff;
638 bb_error_msg_and_die("unknown API mode '%s'", G.api_mode);
641 if (!(opts & FLAG_NO_DAEMON))
642 bb_daemonize_or_rexec(DAEMON_CHDIR_ROOT, argv);
644 xmove_fd(xsocket(AF_INET, SOCK_DGRAM, 0), ioctl_fd);
645 if (opts & FLAG_MONITOR) {
646 xmove_fd(netlink_open(), netlink_fd);
649 write_pidfile(pidfile_name);
651 /* this can't be moved before socket creation */
652 if (!(opts & FLAG_NO_SYSLOG)) {
653 openlog(applet_name, 0, LOG_DAEMON);
654 logmode |= LOGMODE_SYSLOG;
661 | (1 << SIGHUP ) /* why we ignore it? */
662 /* | (1 << SIGCHLD) - run_script does not use it anymore */
665 bb_error_msg("started: %s", bb_banner);
667 if (opts & FLAG_MONITOR) {
668 struct ifreq ifrequest;
669 set_ifreq_to_ifname(&ifrequest);
670 G.iface_exists = (network_ioctl(SIOCGIFINDEX, &ifrequest, NULL) == 0);
674 maybe_up_new_iface();
676 iface_status = detect_link();
677 if (iface_status == IFSTATUS_ERR)
679 iface_status_str = strstatus(iface_status);
681 if (opts & FLAG_MONITOR) {
682 bb_error_msg("interface %s",
683 G.iface_exists ? "exists"
684 : "doesn't exist, waiting");
686 /* else we assume it always exists, but don't mislead user
687 * by potentially lying that it really exists */
689 if (G.iface_exists) {
690 bb_error_msg("link is %s", iface_status_str);
693 if ((!(opts & FLAG_NO_STARTUP)
694 && iface_status == IFSTATUS_UP
696 || (opts & FLAG_INITIAL_DOWN)
698 if (run_script(iface_status_str) != 0)
703 netlink_pollfd[0].fd = netlink_fd;
704 netlink_pollfd[0].events = POLLIN;
707 int iface_status_old;
708 int iface_exists_old;
710 switch (bb_got_signal) {
723 if (poll(netlink_pollfd,
724 (opts & FLAG_MONITOR) ? 1 : 0,
730 bb_perror_msg("poll");
734 iface_status_old = iface_status;
735 iface_exists_old = G.iface_exists;
737 if ((opts & FLAG_MONITOR)
738 && (netlink_pollfd[0].revents & POLLIN)
740 G.iface_exists = check_existence_through_netlink();
741 if (G.iface_exists < 0) /* error */
743 if (iface_exists_old != G.iface_exists) {
744 bb_error_msg("interface %sappeared",
745 G.iface_exists ? "" : "dis");
747 maybe_up_new_iface();
751 /* note: if !G.iface_exists, returns DOWN */
752 iface_status = detect_link();
753 if (iface_status == IFSTATUS_ERR) {
754 if (!(opts & FLAG_MONITOR))
756 iface_status = IFSTATUS_DOWN;
758 iface_status_str = strstatus(iface_status);
760 if (iface_status_old != iface_status) {
761 bb_error_msg("link is %s", iface_status_str);
764 /* link restored its old status before
765 * we run script. don't run the script: */
768 delay_time = monotonic_sec();
769 if (iface_status == IFSTATUS_UP)
770 delay_time += G.delay_up;
771 if (iface_status == IFSTATUS_DOWN)
772 delay_time += G.delay_down;
778 if (delay_time && (int)(monotonic_sec() - delay_time) >= 0) {
780 if (run_script(iface_status_str) != 0)
786 if (!(opts & FLAG_NO_SHUTDOWN)
787 && (iface_status == IFSTATUS_UP
788 || (iface_status == IFSTATUS_DOWN && delay_time)
791 setenv(IFPLUGD_ENV_PREVIOUS, strstatus(iface_status), 1);
792 setenv(IFPLUGD_ENV_CURRENT, strstatus(-1), 1);
793 run_script("down\0up"); /* reusing string */
797 remove_pidfile(pidfile_name);
798 bb_error_msg_and_die("exiting");