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
41 # include <net/ethernet.h>
43 #include <linux/netlink.h>
44 #include <linux/rtnetlink.h>
45 #include <linux/sockios.h>
49 #include <linux/wireless.h>
52 From initial port to busybox, removed most of the redundancy by
53 converting implementation of a polymorphic interface to the strict
54 functional style. The main role is run a script when link state
55 changed, other activities like audio signal or detailed reports
56 are on the script itself.
58 One questionable point of the design is netlink usage:
60 We have 1 second timeout by default to poll the link status,
61 it is short enough so that there are no real benefits in
62 using netlink to get "instantaneous" interface creation/deletion
63 notifications. We can check for interface existence by just
64 doing some fast ioctl using its name.
66 Netlink code then can be just dropped (1k or more?)
70 #define IFPLUGD_ENV_PREVIOUS "IFPLUGD_PREVIOUS"
71 #define IFPLUGD_ENV_CURRENT "IFPLUGD_CURRENT"
74 FLAG_NO_AUTO = 1 << 0, // -a, Do not enable interface automatically
75 FLAG_NO_DAEMON = 1 << 1, // -n, Do not daemonize
76 FLAG_NO_SYSLOG = 1 << 2, // -s, Do not use syslog, use stderr instead
77 FLAG_IGNORE_FAIL = 1 << 3, // -f, Ignore detection failure, retry instead (failure is treated as DOWN)
78 FLAG_IGNORE_FAIL_POSITIVE = 1 << 4, // -F, Ignore detection failure, retry instead (failure is treated as UP)
79 FLAG_IFACE = 1 << 5, // -i, Specify ethernet interface
80 FLAG_RUN = 1 << 6, // -r, Specify program to execute
81 FLAG_IGNORE_RETVAL = 1 << 7, // -I, Don't exit on nonzero return value of program executed
82 FLAG_POLL_TIME = 1 << 8, // -t, Specify poll time in seconds
83 FLAG_DELAY_UP = 1 << 9, // -u, Specify delay for configuring interface
84 FLAG_DELAY_DOWN = 1 << 10, // -d, Specify delay for deconfiguring interface
85 FLAG_API_MODE = 1 << 11, // -m, Force API mode (mii, priv, ethtool, wlan, auto)
86 FLAG_NO_STARTUP = 1 << 12, // -p, Don't run script on daemon startup
87 FLAG_NO_SHUTDOWN = 1 << 13, // -q, Don't run script on daemon quit
88 FLAG_INITIAL_DOWN = 1 << 14, // -l, Run "down" script on startup if no cable is detected
89 FLAG_EXTRA_ARG = 1 << 15, // -x, Specify an extra argument for action script
90 FLAG_MONITOR = 1 << 16, // -M, Use interface monitoring
91 #if ENABLE_FEATURE_PIDFILE
92 FLAG_KILL = 1 << 17, // -k, Kill a running daemon
95 #if ENABLE_FEATURE_PIDFILE
96 # define OPTION_STR "+ansfFi:r:It:u:d:m:pqlx:Mk"
98 # define OPTION_STR "+ansfFi:r:It:u:d:m:pqlx:M"
101 enum { // interface status
107 enum { // constant fds
113 smallint iface_last_status;
114 smallint iface_prev_status;
115 smallint iface_exists;
116 smallint api_method_num;
118 /* Used in getopt32, must have sizeof == sizeof(int) */
124 const char *api_mode;
125 const char *script_name;
126 const char *extra_arg;
128 #define G (*ptr_to_globals)
129 #define INIT_G() do { \
130 SET_PTR_TO_GLOBALS(xzalloc(sizeof(G))); \
131 G.iface_last_status = -1; \
132 G.iface_exists = 1; \
137 G.script_name = "/etc/ifplugd/ifplugd.action"; \
141 /* Utility routines */
143 static void set_ifreq_to_ifname(struct ifreq *ifreq)
145 memset(ifreq, 0, sizeof(struct ifreq));
146 strncpy_IFNAMSIZ(ifreq->ifr_name, G.iface);
149 static int network_ioctl(int request, void* data, const char *errmsg)
151 int r = ioctl(ioctl_fd, request, data);
153 bb_perror_msg("%s failed", errmsg);
157 /* Link detection routines and table */
159 static smallint detect_link_mii(void)
161 /* char buffer instead of bona-fide struct avoids aliasing warning */
162 char buf[sizeof(struct ifreq)];
163 struct ifreq *const ifreq = (void *)buf;
165 struct mii_ioctl_data *mii = (void *)&ifreq->ifr_data;
167 set_ifreq_to_ifname(ifreq);
169 if (network_ioctl(SIOCGMIIPHY, ifreq, "SIOCGMIIPHY") < 0) {
175 if (network_ioctl(SIOCGMIIREG, ifreq, "SIOCGMIIREG") < 0) {
179 return (mii->val_out & 0x0004) ? IFSTATUS_UP : IFSTATUS_DOWN;
182 static smallint detect_link_priv(void)
184 /* char buffer instead of bona-fide struct avoids aliasing warning */
185 char buf[sizeof(struct ifreq)];
186 struct ifreq *const ifreq = (void *)buf;
188 struct mii_ioctl_data *mii = (void *)&ifreq->ifr_data;
190 set_ifreq_to_ifname(ifreq);
192 if (network_ioctl(SIOCDEVPRIVATE, ifreq, "SIOCDEVPRIVATE") < 0) {
198 if (network_ioctl(SIOCDEVPRIVATE+1, ifreq, "SIOCDEVPRIVATE+1") < 0) {
202 return (mii->val_out & 0x0004) ? IFSTATUS_UP : IFSTATUS_DOWN;
205 static smallint detect_link_ethtool(void)
208 struct ethtool_value edata;
210 set_ifreq_to_ifname(&ifreq);
212 edata.cmd = ETHTOOL_GLINK;
213 ifreq.ifr_data = (void*) &edata;
215 if (network_ioctl(SIOCETHTOOL, &ifreq, "ETHTOOL_GLINK") < 0) {
219 return edata.data ? IFSTATUS_UP : IFSTATUS_DOWN;
222 static smallint detect_link_iff(void)
226 set_ifreq_to_ifname(&ifreq);
228 if (network_ioctl(SIOCGIFFLAGS, &ifreq, "SIOCGIFFLAGS") < 0) {
232 /* If IFF_UP is not set (interface is down), IFF_RUNNING is never set
233 * regardless of link status. Simply continue to report last status -
234 * no point in reporting spurious link downs if interface is disabled
235 * by admin. When/if it will be brought up,
236 * we'll report real link status.
238 if (!(ifreq.ifr_flags & IFF_UP) && G.iface_last_status != IFSTATUS_ERR)
239 return G.iface_last_status;
241 return (ifreq.ifr_flags & IFF_RUNNING) ? IFSTATUS_UP : IFSTATUS_DOWN;
244 static smallint detect_link_wlan(void)
247 struct iwreq iwrequest;
248 uint8_t mac[ETH_ALEN];
250 memset(&iwrequest, 0, sizeof(iwrequest));
251 strncpy_IFNAMSIZ(iwrequest.ifr_ifrn.ifrn_name, G.iface);
253 if (network_ioctl(SIOCGIWAP, &iwrequest, "SIOCGIWAP") < 0) {
257 memcpy(mac, &iwrequest.u.ap_addr.sa_data, ETH_ALEN);
259 if (mac[0] == 0xFF || mac[0] == 0x44 || mac[0] == 0x00) {
260 for (i = 1; i < ETH_ALEN; ++i) {
261 if (mac[i] != mac[0])
264 return IFSTATUS_DOWN;
279 static const char api_modes[] ALIGN1 = "empwia";
281 static const struct {
283 smallint (*func)(void);
285 { "SIOCETHTOOL" , &detect_link_ethtool },
286 { "SIOCGMIIPHY" , &detect_link_mii },
287 { "SIOCDEVPRIVATE" , &detect_link_priv },
288 { "wireless extension", &detect_link_wlan },
289 { "IFF_RUNNING" , &detect_link_iff },
292 static const char *strstatus(int status)
294 if (status == IFSTATUS_ERR)
296 return "down\0up" + (status * 5);
299 static int run_script(const char *action)
301 char *env_PREVIOUS, *env_CURRENT;
305 bb_error_msg("executing '%s %s %s'", G.script_name, G.iface, action);
307 argv[0] = (char*) G.script_name;
308 argv[1] = (char*) G.iface;
309 argv[2] = (char*) action;
310 argv[3] = (char*) G.extra_arg;
313 env_PREVIOUS = xasprintf("%s=%s", IFPLUGD_ENV_PREVIOUS, strstatus(G.iface_prev_status));
314 putenv(env_PREVIOUS);
315 env_CURRENT = xasprintf("%s=%s", IFPLUGD_ENV_CURRENT, strstatus(G.iface_last_status));
318 /* r < 0 - can't exec, 0 <= r < 0x180 - exited, >=0x180 - killed by sig (r-0x180) */
319 r = spawn_and_wait(argv);
321 unsetenv(IFPLUGD_ENV_PREVIOUS);
322 unsetenv(IFPLUGD_ENV_CURRENT);
326 bb_error_msg("exit code: %d", r & 0xff);
327 return (option_mask32 & FLAG_IGNORE_RETVAL) ? 0 : r;
330 static void up_iface(void)
332 struct ifreq ifrequest;
337 set_ifreq_to_ifname(&ifrequest);
338 if (network_ioctl(SIOCGIFFLAGS, &ifrequest, "getting interface flags") < 0) {
343 if (!(ifrequest.ifr_flags & IFF_UP)) {
344 ifrequest.ifr_flags |= IFF_UP;
345 /* Let user know we mess up with interface */
346 bb_error_msg("upping interface");
347 if (network_ioctl(SIOCSIFFLAGS, &ifrequest, "setting interface flags") < 0)
351 #if 0 /* why do we mess with IP addr? It's not our business */
352 if (network_ioctl(SIOCGIFADDR, &ifrequest, "can't get interface address") < 0) {
353 } else if (ifrequest.ifr_addr.sa_family != AF_INET) {
354 bb_perror_msg("the interface is not IP-based");
356 ((struct sockaddr_in*)(&ifrequest.ifr_addr))->sin_addr.s_addr = INADDR_ANY;
357 network_ioctl(SIOCSIFADDR, &ifrequest, "can't set interface address");
359 network_ioctl(SIOCGIFFLAGS, &ifrequest, "can't get interface flags");
363 static void maybe_up_new_iface(void)
365 if (!(option_mask32 & FLAG_NO_AUTO))
369 struct ifreq ifrequest;
370 struct ethtool_drvinfo driver_info;
372 set_ifreq_to_ifname(&ifrequest);
373 driver_info.cmd = ETHTOOL_GDRVINFO;
374 ifrequest.ifr_data = &driver_info;
375 if (network_ioctl(SIOCETHTOOL, &ifrequest, NULL) == 0) {
376 char buf[sizeof("/xx:xx:xx:xx:xx:xx")];
380 set_ifreq_to_ifname(&ifrequest);
381 if (network_ioctl(SIOCGIFHWADDR, &ifrequest, NULL) == 0) {
382 sprintf(buf, "/%02X:%02X:%02X:%02X:%02X:%02X",
383 (uint8_t)(ifrequest.ifr_hwaddr.sa_data[0]),
384 (uint8_t)(ifrequest.ifr_hwaddr.sa_data[1]),
385 (uint8_t)(ifrequest.ifr_hwaddr.sa_data[2]),
386 (uint8_t)(ifrequest.ifr_hwaddr.sa_data[3]),
387 (uint8_t)(ifrequest.ifr_hwaddr.sa_data[4]),
388 (uint8_t)(ifrequest.ifr_hwaddr.sa_data[5]));
391 bb_error_msg("using interface %s%s with driver<%s> (version: %s)",
392 G.iface, buf, driver_info.driver, driver_info.version);
395 if (G.api_mode[0] == 'a')
396 G.api_method_num = API_AUTO;
399 static smallint detect_link(void)
404 return (option_mask32 & FLAG_MONITOR) ? IFSTATUS_DOWN : IFSTATUS_ERR;
406 /* Some drivers can't detect link status when the interface is down.
407 * I imagine detect_link_iff() is the most vulnerable.
408 * That's why -a "noauto" in an option, not a hardwired behavior.
410 if (!(option_mask32 & FLAG_NO_AUTO))
413 if (G.api_method_num == API_AUTO) {
417 sv_logmode = logmode;
418 for (i = 0; i < ARRAY_SIZE(method_table); i++) {
419 logmode = LOGMODE_NONE;
420 status = method_table[i].func();
421 logmode = sv_logmode;
422 if (status != IFSTATUS_ERR) {
423 G.api_method_num = i;
424 bb_error_msg("using %s detection mode", method_table[i].name);
429 status = method_table[G.api_method_num].func();
432 if (status == IFSTATUS_ERR) {
433 if (option_mask32 & FLAG_IGNORE_FAIL)
434 status = IFSTATUS_DOWN;
435 else if (option_mask32 & FLAG_IGNORE_FAIL_POSITIVE)
436 status = IFSTATUS_UP;
437 else if (G.api_mode[0] == 'a')
438 bb_error_msg("can't detect link status");
441 if (status != G.iface_last_status) {
442 G.iface_prev_status = G.iface_last_status;
443 G.iface_last_status = status;
449 static NOINLINE int check_existence_through_netlink(void)
452 /* Buffer was 1K, but on linux-3.9.9 it was reported to be too small.
453 * netlink.h: "limit to 8K to avoid MSG_TRUNC when PAGE_SIZE is very large".
454 * Note: on error returns (-1) we exit, no need to free replybuf.
456 enum { BUF_SIZE = 8 * 1024 };
457 char *replybuf = xmalloc(BUF_SIZE);
459 iface_len = strlen(G.iface);
461 struct nlmsghdr *mhdr;
464 bytes = recv(netlink_fd, replybuf, BUF_SIZE, MSG_DONTWAIT);
470 bb_perror_msg("netlink: recv");
474 mhdr = (struct nlmsghdr*)replybuf;
476 if (!NLMSG_OK(mhdr, bytes)) {
477 bb_error_msg("netlink packet too small or truncated");
481 if (mhdr->nlmsg_type == RTM_NEWLINK || mhdr->nlmsg_type == RTM_DELLINK) {
485 if (mhdr->nlmsg_len < NLMSG_LENGTH(sizeof(struct ifinfomsg))) {
486 bb_error_msg("netlink packet too small or truncated");
490 attr = IFLA_RTA(NLMSG_DATA(mhdr));
491 attr_len = IFLA_PAYLOAD(mhdr);
493 while (RTA_OK(attr, attr_len)) {
494 if (attr->rta_type == IFLA_IFNAME) {
495 int len = RTA_PAYLOAD(attr);
499 && strncmp(G.iface, RTA_DATA(attr), len) == 0
501 G.iface_exists = (mhdr->nlmsg_type == RTM_NEWLINK);
504 attr = RTA_NEXT(attr, attr_len);
508 mhdr = NLMSG_NEXT(mhdr, bytes);
514 return G.iface_exists;
517 #if ENABLE_FEATURE_PIDFILE
518 static NOINLINE pid_t read_pid(const char *filename)
523 len = open_read_close(filename, buf, 127);
526 /* returns ULONG_MAX on error => -1 */
527 return bb_strtoul(buf, NULL, 10);
533 int ifplugd_main(int argc, char **argv) MAIN_EXTERNALLY_VISIBLE;
534 int ifplugd_main(int argc UNUSED_PARAM, char **argv)
538 const char *iface_status_str;
539 struct pollfd netlink_pollfd[1];
541 const char *api_mode_found;
542 #if ENABLE_FEATURE_PIDFILE
544 pid_t pid_from_pidfile;
549 opt_complementary = "t+:u+:d+";
550 opts = getopt32(argv, OPTION_STR,
551 &G.iface, &G.script_name, &G.poll_time, &G.delay_up,
552 &G.delay_down, &G.api_mode, &G.extra_arg);
555 applet_name = xasprintf("ifplugd(%s)", G.iface);
557 #if ENABLE_FEATURE_PIDFILE
558 pidfile_name = xasprintf(CONFIG_PID_FILE_PATH "/ifplugd.%s.pid", G.iface);
559 pid_from_pidfile = read_pid(pidfile_name);
561 if (opts & FLAG_KILL) {
562 if (pid_from_pidfile > 0)
563 /* Upstream tool use SIGINT for -k */
564 kill(pid_from_pidfile, SIGINT);
568 if (pid_from_pidfile > 0 && kill(pid_from_pidfile, 0) == 0)
569 bb_error_msg_and_die("daemon already running");
572 api_mode_found = strchr(api_modes, G.api_mode[0]);
574 bb_error_msg_and_die("unknown API mode '%s'", G.api_mode);
575 G.api_method_num = api_mode_found - api_modes;
577 if (!(opts & FLAG_NO_DAEMON))
578 bb_daemonize_or_rexec(DAEMON_CHDIR_ROOT, argv);
580 xmove_fd(xsocket(AF_INET, SOCK_DGRAM, 0), ioctl_fd);
581 if (opts & FLAG_MONITOR) {
582 struct sockaddr_nl addr;
583 int fd = xsocket(PF_NETLINK, SOCK_DGRAM, NETLINK_ROUTE);
585 memset(&addr, 0, sizeof(addr));
586 addr.nl_family = AF_NETLINK;
587 addr.nl_groups = RTMGRP_LINK;
588 addr.nl_pid = getpid();
590 xbind(fd, (struct sockaddr*)&addr, sizeof(addr));
591 xmove_fd(fd, netlink_fd);
594 write_pidfile(pidfile_name);
596 /* this can't be moved before socket creation */
597 if (!(opts & FLAG_NO_SYSLOG)) {
598 openlog(applet_name, 0, LOG_DAEMON);
599 logmode |= LOGMODE_SYSLOG;
606 | (1 << SIGHUP ) /* why we ignore it? */
607 /* | (1 << SIGCHLD) - run_script does not use it anymore */
610 bb_error_msg("started: %s", bb_banner);
612 if (opts & FLAG_MONITOR) {
613 struct ifreq ifrequest;
614 set_ifreq_to_ifname(&ifrequest);
615 G.iface_exists = (network_ioctl(SIOCGIFINDEX, &ifrequest, NULL) == 0);
619 maybe_up_new_iface();
621 iface_status = detect_link();
622 if (iface_status == IFSTATUS_ERR)
624 iface_status_str = strstatus(iface_status);
626 if (opts & FLAG_MONITOR) {
627 bb_error_msg("interface %s",
628 G.iface_exists ? "exists"
629 : "doesn't exist, waiting");
631 /* else we assume it always exists, but don't mislead user
632 * by potentially lying that it really exists */
634 if (G.iface_exists) {
635 bb_error_msg("link is %s", iface_status_str);
638 if ((!(opts & FLAG_NO_STARTUP)
639 && iface_status == IFSTATUS_UP
641 || (opts & FLAG_INITIAL_DOWN)
643 if (run_script(iface_status_str) != 0)
648 netlink_pollfd[0].fd = netlink_fd;
649 netlink_pollfd[0].events = POLLIN;
652 int iface_status_old;
654 switch (bb_got_signal) {
667 if (poll(netlink_pollfd,
668 (opts & FLAG_MONITOR) ? 1 : 0,
674 bb_perror_msg("poll");
678 if ((opts & FLAG_MONITOR)
679 && (netlink_pollfd[0].revents & POLLIN)
681 int iface_exists_old;
683 iface_exists_old = G.iface_exists;
684 G.iface_exists = check_existence_through_netlink();
685 if (G.iface_exists < 0) /* error */
687 if (iface_exists_old != G.iface_exists) {
688 bb_error_msg("interface %sappeared",
689 G.iface_exists ? "" : "dis");
691 maybe_up_new_iface();
695 /* note: if !G.iface_exists, returns DOWN */
696 iface_status_old = iface_status;
697 iface_status = detect_link();
698 if (iface_status == IFSTATUS_ERR) {
699 if (!(opts & FLAG_MONITOR))
701 iface_status = IFSTATUS_DOWN;
703 iface_status_str = strstatus(iface_status);
705 if (iface_status_old != iface_status) {
706 bb_error_msg("link is %s", iface_status_str);
709 /* link restored its old status before
710 * we ran script. don't run the script: */
713 delay_time = monotonic_sec();
714 if (iface_status == IFSTATUS_UP)
715 delay_time += G.delay_up;
716 if (iface_status == IFSTATUS_DOWN)
717 delay_time += G.delay_down;
718 #if 0 /* if you are back in 1970... */
719 if (delay_time == 0) {
727 if (delay_time && (int)(monotonic_sec() - delay_time) >= 0) {
728 if (run_script(iface_status_str) != 0)
735 if (!(opts & FLAG_NO_SHUTDOWN)
736 && (iface_status == IFSTATUS_UP
737 || (iface_status == IFSTATUS_DOWN && delay_time)
740 setenv(IFPLUGD_ENV_PREVIOUS, strstatus(iface_status), 1);
741 setenv(IFPLUGD_ENV_CURRENT, strstatus(-1), 1);
742 run_script("down\0up"); /* reusing string */
746 remove_pidfile(pidfile_name);
747 bb_error_msg_and_die("exiting");