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 },
294 static const char *strstatus(int status)
296 if (status == IFSTATUS_ERR)
298 return "down\0up" + (status * 5);
301 static int run_script(const char *action)
303 char *env_PREVIOUS, *env_CURRENT;
307 bb_error_msg("executing '%s %s %s'", G.script_name, G.iface, action);
309 argv[0] = (char*) G.script_name;
310 argv[1] = (char*) G.iface;
311 argv[2] = (char*) action;
312 argv[3] = (char*) G.extra_arg;
315 env_PREVIOUS = xasprintf("%s=%s", IFPLUGD_ENV_PREVIOUS, strstatus(G.iface_prev_status));
316 putenv(env_PREVIOUS);
317 env_CURRENT = xasprintf("%s=%s", IFPLUGD_ENV_CURRENT, strstatus(G.iface_last_status));
320 /* r < 0 - can't exec, 0 <= r < 0x180 - exited, >=0x180 - killed by sig (r-0x180) */
321 r = spawn_and_wait(argv);
323 unsetenv(IFPLUGD_ENV_PREVIOUS);
324 unsetenv(IFPLUGD_ENV_CURRENT);
328 bb_error_msg("exit code: %d", r & 0xff);
329 return (option_mask32 & FLAG_IGNORE_RETVAL) ? 0 : r;
332 static void up_iface(void)
334 struct ifreq ifrequest;
339 set_ifreq_to_ifname(&ifrequest);
340 if (network_ioctl(SIOCGIFFLAGS, &ifrequest, "getting interface flags") < 0) {
345 if (!(ifrequest.ifr_flags & IFF_UP)) {
346 ifrequest.ifr_flags |= IFF_UP;
347 /* Let user know we mess up with interface */
348 bb_error_msg("upping interface");
349 if (network_ioctl(SIOCSIFFLAGS, &ifrequest, "setting interface flags") < 0)
353 #if 0 /* why do we mess with IP addr? It's not our business */
354 if (network_ioctl(SIOCGIFADDR, &ifrequest, "can't get interface address") < 0) {
355 } else if (ifrequest.ifr_addr.sa_family != AF_INET) {
356 bb_perror_msg("the interface is not IP-based");
358 ((struct sockaddr_in*)(&ifrequest.ifr_addr))->sin_addr.s_addr = INADDR_ANY;
359 network_ioctl(SIOCSIFADDR, &ifrequest, "can't set interface address");
361 network_ioctl(SIOCGIFFLAGS, &ifrequest, "can't get interface flags");
365 static void maybe_up_new_iface(void)
367 if (!(option_mask32 & FLAG_NO_AUTO))
371 struct ifreq ifrequest;
372 struct ethtool_drvinfo driver_info;
374 set_ifreq_to_ifname(&ifrequest);
375 driver_info.cmd = ETHTOOL_GDRVINFO;
376 ifrequest.ifr_data = &driver_info;
377 if (network_ioctl(SIOCETHTOOL, &ifrequest, NULL) == 0) {
378 char buf[sizeof("/xx:xx:xx:xx:xx:xx")];
382 set_ifreq_to_ifname(&ifrequest);
383 if (network_ioctl(SIOCGIFHWADDR, &ifrequest, NULL) == 0) {
384 sprintf(buf, "/%02X:%02X:%02X:%02X:%02X:%02X",
385 (uint8_t)(ifrequest.ifr_hwaddr.sa_data[0]),
386 (uint8_t)(ifrequest.ifr_hwaddr.sa_data[1]),
387 (uint8_t)(ifrequest.ifr_hwaddr.sa_data[2]),
388 (uint8_t)(ifrequest.ifr_hwaddr.sa_data[3]),
389 (uint8_t)(ifrequest.ifr_hwaddr.sa_data[4]),
390 (uint8_t)(ifrequest.ifr_hwaddr.sa_data[5]));
393 bb_error_msg("using interface %s%s with driver<%s> (version: %s)",
394 G.iface, buf, driver_info.driver, driver_info.version);
397 if (G.api_mode[0] == 'a')
398 G.api_method_num = API_AUTO;
401 static smallint detect_link(void)
406 return (option_mask32 & FLAG_MONITOR) ? IFSTATUS_DOWN : IFSTATUS_ERR;
408 /* Some drivers can't detect link status when the interface is down.
409 * I imagine detect_link_iff() is the most vulnerable.
410 * That's why -a "noauto" in an option, not a hardwired behavior.
412 if (!(option_mask32 & FLAG_NO_AUTO))
415 if (G.api_method_num == API_AUTO) {
419 sv_logmode = logmode;
420 for (i = 0; i < ARRAY_SIZE(method_table); i++) {
421 logmode = LOGMODE_NONE;
422 status = method_table[i].func();
423 logmode = sv_logmode;
424 if (status != IFSTATUS_ERR) {
425 G.api_method_num = i;
426 bb_error_msg("using %s detection mode", method_table[i].name);
431 status = method_table[G.api_method_num].func();
434 if (status == IFSTATUS_ERR) {
435 if (option_mask32 & FLAG_IGNORE_FAIL)
436 status = IFSTATUS_DOWN;
437 else if (option_mask32 & FLAG_IGNORE_FAIL_POSITIVE)
438 status = IFSTATUS_UP;
439 else if (G.api_mode[0] == 'a')
440 bb_error_msg("can't detect link status");
443 if (status != G.iface_last_status) {
444 G.iface_prev_status = G.iface_last_status;
445 G.iface_last_status = status;
451 static NOINLINE int check_existence_through_netlink(void)
454 /* Buffer was 1K, but on linux-3.9.9 it was reported to be too small.
455 * netlink.h: "limit to 8K to avoid MSG_TRUNC when PAGE_SIZE is very large".
456 * Note: on error returns (-1) we exit, no need to free replybuf.
458 enum { BUF_SIZE = 8 * 1024 };
459 char *replybuf = xmalloc(BUF_SIZE);
461 iface_len = strlen(G.iface);
463 struct nlmsghdr *mhdr;
466 bytes = recv(netlink_fd, replybuf, BUF_SIZE, MSG_DONTWAIT);
472 bb_perror_msg("netlink: recv");
476 mhdr = (struct nlmsghdr*)replybuf;
478 if (!NLMSG_OK(mhdr, bytes)) {
479 bb_error_msg("netlink packet too small or truncated");
483 if (mhdr->nlmsg_type == RTM_NEWLINK || mhdr->nlmsg_type == RTM_DELLINK) {
487 if (mhdr->nlmsg_len < NLMSG_LENGTH(sizeof(struct ifinfomsg))) {
488 bb_error_msg("netlink packet too small or truncated");
492 attr = IFLA_RTA(NLMSG_DATA(mhdr));
493 attr_len = IFLA_PAYLOAD(mhdr);
495 while (RTA_OK(attr, attr_len)) {
496 if (attr->rta_type == IFLA_IFNAME) {
497 int len = RTA_PAYLOAD(attr);
501 && strncmp(G.iface, RTA_DATA(attr), len) == 0
503 G.iface_exists = (mhdr->nlmsg_type == RTM_NEWLINK);
506 attr = RTA_NEXT(attr, attr_len);
510 mhdr = NLMSG_NEXT(mhdr, bytes);
516 return G.iface_exists;
519 #if ENABLE_FEATURE_PIDFILE
520 static NOINLINE pid_t read_pid(const char *filename)
525 len = open_read_close(filename, buf, 127);
528 /* returns ULONG_MAX on error => -1 */
529 return bb_strtoul(buf, NULL, 10);
535 int ifplugd_main(int argc, char **argv) MAIN_EXTERNALLY_VISIBLE;
536 int ifplugd_main(int argc UNUSED_PARAM, char **argv)
540 const char *iface_status_str;
541 struct pollfd netlink_pollfd[1];
543 const char *api_mode_found;
544 #if ENABLE_FEATURE_PIDFILE
546 pid_t pid_from_pidfile;
551 opt_complementary = "t+:u+:d+";
552 opts = getopt32(argv, OPTION_STR,
553 &G.iface, &G.script_name, &G.poll_time, &G.delay_up,
554 &G.delay_down, &G.api_mode, &G.extra_arg);
557 applet_name = xasprintf("ifplugd(%s)", G.iface);
559 #if ENABLE_FEATURE_PIDFILE
560 pidfile_name = xasprintf(CONFIG_PID_FILE_PATH "/ifplugd.%s.pid", G.iface);
561 pid_from_pidfile = read_pid(pidfile_name);
563 if (opts & FLAG_KILL) {
564 if (pid_from_pidfile > 0)
565 /* Upstream tool use SIGINT for -k */
566 kill(pid_from_pidfile, SIGINT);
570 if (pid_from_pidfile > 0 && kill(pid_from_pidfile, 0) == 0)
571 bb_error_msg_and_die("daemon already running");
574 api_mode_found = strchr(api_modes, G.api_mode[0]);
576 bb_error_msg_and_die("unknown API mode '%s'", G.api_mode);
577 G.api_method_num = api_mode_found - api_modes;
579 if (!(opts & FLAG_NO_DAEMON))
580 bb_daemonize_or_rexec(DAEMON_CHDIR_ROOT, argv);
582 xmove_fd(xsocket(AF_INET, SOCK_DGRAM, 0), ioctl_fd);
583 if (opts & FLAG_MONITOR) {
584 struct sockaddr_nl addr;
585 int fd = xsocket(PF_NETLINK, SOCK_DGRAM, NETLINK_ROUTE);
587 memset(&addr, 0, sizeof(addr));
588 addr.nl_family = AF_NETLINK;
589 addr.nl_groups = RTMGRP_LINK;
590 addr.nl_pid = getpid();
592 xbind(fd, (struct sockaddr*)&addr, sizeof(addr));
593 xmove_fd(fd, netlink_fd);
596 write_pidfile(pidfile_name);
598 /* this can't be moved before socket creation */
599 if (!(opts & FLAG_NO_SYSLOG)) {
600 openlog(applet_name, 0, LOG_DAEMON);
601 logmode |= LOGMODE_SYSLOG;
608 | (1 << SIGHUP ) /* why we ignore it? */
609 /* | (1 << SIGCHLD) - run_script does not use it anymore */
612 bb_error_msg("started: %s", bb_banner);
614 if (opts & FLAG_MONITOR) {
615 struct ifreq ifrequest;
616 set_ifreq_to_ifname(&ifrequest);
617 G.iface_exists = (network_ioctl(SIOCGIFINDEX, &ifrequest, NULL) == 0);
621 maybe_up_new_iface();
623 iface_status = detect_link();
624 if (iface_status == IFSTATUS_ERR)
626 iface_status_str = strstatus(iface_status);
628 if (opts & FLAG_MONITOR) {
629 bb_error_msg("interface %s",
630 G.iface_exists ? "exists"
631 : "doesn't exist, waiting");
633 /* else we assume it always exists, but don't mislead user
634 * by potentially lying that it really exists */
636 if (G.iface_exists) {
637 bb_error_msg("link is %s", iface_status_str);
640 if ((!(opts & FLAG_NO_STARTUP)
641 && iface_status == IFSTATUS_UP
643 || (opts & FLAG_INITIAL_DOWN)
645 if (run_script(iface_status_str) != 0)
650 netlink_pollfd[0].fd = netlink_fd;
651 netlink_pollfd[0].events = POLLIN;
654 int iface_status_old;
655 int iface_exists_old;
657 switch (bb_got_signal) {
670 if (poll(netlink_pollfd,
671 (opts & FLAG_MONITOR) ? 1 : 0,
677 bb_perror_msg("poll");
681 iface_status_old = iface_status;
682 iface_exists_old = G.iface_exists;
684 if ((opts & FLAG_MONITOR)
685 && (netlink_pollfd[0].revents & POLLIN)
687 G.iface_exists = check_existence_through_netlink();
688 if (G.iface_exists < 0) /* error */
690 if (iface_exists_old != G.iface_exists) {
691 bb_error_msg("interface %sappeared",
692 G.iface_exists ? "" : "dis");
694 maybe_up_new_iface();
698 /* note: if !G.iface_exists, returns DOWN */
699 iface_status = detect_link();
700 if (iface_status == IFSTATUS_ERR) {
701 if (!(opts & FLAG_MONITOR))
703 iface_status = IFSTATUS_DOWN;
705 iface_status_str = strstatus(iface_status);
707 if (iface_status_old != iface_status) {
708 bb_error_msg("link is %s", iface_status_str);
711 /* link restored its old status before
712 * we run script. don't run the script: */
715 delay_time = monotonic_sec();
716 if (iface_status == IFSTATUS_UP)
717 delay_time += G.delay_up;
718 if (iface_status == IFSTATUS_DOWN)
719 delay_time += G.delay_down;
725 if (delay_time && (int)(monotonic_sec() - delay_time) >= 0) {
727 if (run_script(iface_status_str) != 0)
733 if (!(opts & FLAG_NO_SHUTDOWN)
734 && (iface_status == IFSTATUS_UP
735 || (iface_status == IFSTATUS_DOWN && delay_time)
738 setenv(IFPLUGD_ENV_PREVIOUS, strstatus(iface_status), 1);
739 setenv(IFPLUGD_ENV_CURRENT, strstatus(-1), 1);
740 run_script("down\0up"); /* reusing string */
744 remove_pidfile(pidfile_name);
745 bb_error_msg_and_die("exiting");