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"
15 //usage: "\n -n Don't daemonize"
16 //usage: "\n -s Don't log to syslog"
17 //usage: "\n -i IFACE Interface"
18 //usage: "\n -f/-F Treat link detection error as link down/link up"
19 //usage: "\n (otherwise exit on error)"
20 //usage: "\n -a Don't up interface at each link probe"
21 //usage: "\n -M Monitor creation/destruction of interface"
22 //usage: "\n (otherwise it must exist)"
23 //usage: "\n -r PROG Script to run"
24 //usage: "\n -x ARG Extra argument for script"
25 //usage: "\n -I Don't exit on nonzero exit code from script"
26 //usage: "\n -p Don't run script on daemon startup"
27 //usage: "\n -q Don't run script on daemon quit"
28 //usage: "\n -l Run script on startup even if no cable is detected"
29 //usage: "\n -t SECS Poll time in seconds"
30 //usage: "\n -u SECS Delay before running script after link up"
31 //usage: "\n -d SECS Delay after link down"
32 //usage: "\n -m MODE API mode (mii, priv, ethtool, wlan, iff, auto)"
33 //usage: "\n -k Kill running daemon"
39 #include <linux/mii.h>
40 #include <linux/ethtool.h>
41 #include <net/ethernet.h>
42 #include <linux/netlink.h>
43 #include <linux/rtnetlink.h>
44 #include <linux/sockios.h>
48 #include <linux/wireless.h>
51 From initial port to busybox, removed most of the redundancy by
52 converting implementation of a polymorphic interface to the strict
53 functional style. The main role is run a script when link state
54 changed, other activities like audio signal or detailed reports
55 are on the script itself.
57 One questionable point of the design is netlink usage:
59 We have 1 second timeout by default to poll the link status,
60 it is short enough so that there are no real benefits in
61 using netlink to get "instantaneous" interface creation/deletion
62 notifications. We can check for interface existence by just
63 doing some fast ioctl using its name.
65 Netlink code then can be just dropped (1k or more?)
69 #define IFPLUGD_ENV_PREVIOUS "IFPLUGD_PREVIOUS"
70 #define IFPLUGD_ENV_CURRENT "IFPLUGD_CURRENT"
73 FLAG_NO_AUTO = 1 << 0, // -a, Do not enable interface automatically
74 FLAG_NO_DAEMON = 1 << 1, // -n, Do not daemonize
75 FLAG_NO_SYSLOG = 1 << 2, // -s, Do not use syslog, use stderr instead
76 FLAG_IGNORE_FAIL = 1 << 3, // -f, Ignore detection failure, retry instead (failure is treated as DOWN)
77 FLAG_IGNORE_FAIL_POSITIVE = 1 << 4, // -F, Ignore detection failure, retry instead (failure is treated as UP)
78 FLAG_IFACE = 1 << 5, // -i, Specify ethernet interface
79 FLAG_RUN = 1 << 6, // -r, Specify program to execute
80 FLAG_IGNORE_RETVAL = 1 << 7, // -I, Don't exit on nonzero return value of program executed
81 FLAG_POLL_TIME = 1 << 8, // -t, Specify poll time in seconds
82 FLAG_DELAY_UP = 1 << 9, // -u, Specify delay for configuring interface
83 FLAG_DELAY_DOWN = 1 << 10, // -d, Specify delay for deconfiguring interface
84 FLAG_API_MODE = 1 << 11, // -m, Force API mode (mii, priv, ethtool, wlan, auto)
85 FLAG_NO_STARTUP = 1 << 12, // -p, Don't run script on daemon startup
86 FLAG_NO_SHUTDOWN = 1 << 13, // -q, Don't run script on daemon quit
87 FLAG_INITIAL_DOWN = 1 << 14, // -l, Run "down" script on startup if no cable is detected
88 FLAG_EXTRA_ARG = 1 << 15, // -x, Specify an extra argument for action script
89 FLAG_MONITOR = 1 << 16, // -M, Use interface monitoring
90 #if ENABLE_FEATURE_PIDFILE
91 FLAG_KILL = 1 << 17, // -k, Kill a running daemon
94 #if ENABLE_FEATURE_PIDFILE
95 # define OPTION_STR "+ansfFi:r:It:u:d:m:pqlx:Mk"
97 # define OPTION_STR "+ansfFi:r:It:u:d:m:pqlx:M"
100 enum { // interface status
106 enum { // constant fds
112 smallint iface_last_status;
113 smallint iface_prev_status;
114 smallint iface_exists;
115 smallint api_method_num;
117 /* Used in getopt32, must have sizeof == sizeof(int) */
123 const char *api_mode;
124 const char *script_name;
125 const char *extra_arg;
127 #define G (*ptr_to_globals)
128 #define INIT_G() do { \
129 SET_PTR_TO_GLOBALS(xzalloc(sizeof(G))); \
130 G.iface_last_status = -1; \
131 G.iface_exists = 1; \
136 G.script_name = "/etc/ifplugd/ifplugd.action"; \
140 /* Utility routines */
142 static void set_ifreq_to_ifname(struct ifreq *ifreq)
144 memset(ifreq, 0, sizeof(struct ifreq));
145 strncpy_IFNAMSIZ(ifreq->ifr_name, G.iface);
148 static int network_ioctl(int request, void* data, const char *errmsg)
150 int r = ioctl(ioctl_fd, request, data);
152 bb_perror_msg("%s failed", errmsg);
156 /* Link detection routines and table */
158 static smallint detect_link_mii(void)
160 /* char buffer instead of bona-fide struct avoids aliasing warning */
161 char buf[sizeof(struct ifreq)];
162 struct ifreq *const ifreq = (void *)buf;
164 struct mii_ioctl_data *mii = (void *)&ifreq->ifr_data;
166 set_ifreq_to_ifname(ifreq);
168 if (network_ioctl(SIOCGMIIPHY, ifreq, "SIOCGMIIPHY") < 0) {
174 if (network_ioctl(SIOCGMIIREG, ifreq, "SIOCGMIIREG") < 0) {
178 return (mii->val_out & 0x0004) ? IFSTATUS_UP : IFSTATUS_DOWN;
181 static smallint detect_link_priv(void)
183 /* char buffer instead of bona-fide struct avoids aliasing warning */
184 char buf[sizeof(struct ifreq)];
185 struct ifreq *const ifreq = (void *)buf;
187 struct mii_ioctl_data *mii = (void *)&ifreq->ifr_data;
189 set_ifreq_to_ifname(ifreq);
191 if (network_ioctl(SIOCDEVPRIVATE, ifreq, "SIOCDEVPRIVATE") < 0) {
197 if (network_ioctl(SIOCDEVPRIVATE+1, ifreq, "SIOCDEVPRIVATE+1") < 0) {
201 return (mii->val_out & 0x0004) ? IFSTATUS_UP : IFSTATUS_DOWN;
204 static smallint detect_link_ethtool(void)
207 struct ethtool_value edata;
209 set_ifreq_to_ifname(&ifreq);
211 edata.cmd = ETHTOOL_GLINK;
212 ifreq.ifr_data = (void*) &edata;
214 if (network_ioctl(SIOCETHTOOL, &ifreq, "ETHTOOL_GLINK") < 0) {
218 return edata.data ? IFSTATUS_UP : IFSTATUS_DOWN;
221 static smallint detect_link_iff(void)
225 set_ifreq_to_ifname(&ifreq);
227 if (network_ioctl(SIOCGIFFLAGS, &ifreq, "SIOCGIFFLAGS") < 0) {
231 /* If IFF_UP is not set (interface is down), IFF_RUNNING is never set
232 * regardless of link status. Simply continue to report last status -
233 * no point in reporting spurious link downs if interface is disabled
234 * by admin. When/if it will be brought up,
235 * we'll report real link status.
237 if (!(ifreq.ifr_flags & IFF_UP) && G.iface_last_status != IFSTATUS_ERR)
238 return G.iface_last_status;
240 return (ifreq.ifr_flags & IFF_RUNNING) ? IFSTATUS_UP : IFSTATUS_DOWN;
243 static smallint detect_link_wlan(void)
246 struct iwreq iwrequest;
247 uint8_t mac[ETH_ALEN];
249 memset(&iwrequest, 0, sizeof(iwrequest));
250 strncpy_IFNAMSIZ(iwrequest.ifr_ifrn.ifrn_name, G.iface);
252 if (network_ioctl(SIOCGIWAP, &iwrequest, "SIOCGIWAP") < 0) {
256 memcpy(mac, &iwrequest.u.ap_addr.sa_data, ETH_ALEN);
258 if (mac[0] == 0xFF || mac[0] == 0x44 || mac[0] == 0x00) {
259 for (i = 1; i < ETH_ALEN; ++i) {
260 if (mac[i] != mac[0])
263 return IFSTATUS_DOWN;
278 static const char api_modes[] ALIGN1 = "empwia";
280 static const struct {
282 smallint (*func)(void);
284 { "SIOCETHTOOL" , &detect_link_ethtool },
285 { "SIOCGMIIPHY" , &detect_link_mii },
286 { "SIOCDEVPRIVATE" , &detect_link_priv },
287 { "wireless extension", &detect_link_wlan },
288 { "IFF_RUNNING" , &detect_link_iff },
293 static const char *strstatus(int status)
295 if (status == IFSTATUS_ERR)
297 return "down\0up" + (status * 5);
300 static int run_script(const char *action)
302 char *env_PREVIOUS, *env_CURRENT;
306 bb_error_msg("executing '%s %s %s'", G.script_name, G.iface, action);
308 argv[0] = (char*) G.script_name;
309 argv[1] = (char*) G.iface;
310 argv[2] = (char*) action;
311 argv[3] = (char*) G.extra_arg;
314 env_PREVIOUS = xasprintf("%s=%s", IFPLUGD_ENV_PREVIOUS, strstatus(G.iface_prev_status));
315 putenv(env_PREVIOUS);
316 env_CURRENT = xasprintf("%s=%s", IFPLUGD_ENV_CURRENT, strstatus(G.iface_last_status));
319 /* r < 0 - can't exec, 0 <= r < 0x180 - exited, >=0x180 - killed by sig (r-0x180) */
320 r = spawn_and_wait(argv);
322 unsetenv(IFPLUGD_ENV_PREVIOUS);
323 unsetenv(IFPLUGD_ENV_CURRENT);
327 bb_error_msg("exit code: %d", r & 0xff);
328 return (option_mask32 & FLAG_IGNORE_RETVAL) ? 0 : r;
331 static void up_iface(void)
333 struct ifreq ifrequest;
338 set_ifreq_to_ifname(&ifrequest);
339 if (network_ioctl(SIOCGIFFLAGS, &ifrequest, "getting interface flags") < 0) {
344 if (!(ifrequest.ifr_flags & IFF_UP)) {
345 ifrequest.ifr_flags |= IFF_UP;
346 /* Let user know we mess up with interface */
347 bb_error_msg("upping interface");
348 if (network_ioctl(SIOCSIFFLAGS, &ifrequest, "setting interface flags") < 0)
352 #if 0 /* why do we mess with IP addr? It's not our business */
353 if (network_ioctl(SIOCGIFADDR, &ifrequest, "can't get interface address") < 0) {
354 } else if (ifrequest.ifr_addr.sa_family != AF_INET) {
355 bb_perror_msg("the interface is not IP-based");
357 ((struct sockaddr_in*)(&ifrequest.ifr_addr))->sin_addr.s_addr = INADDR_ANY;
358 network_ioctl(SIOCSIFADDR, &ifrequest, "can't set interface address");
360 network_ioctl(SIOCGIFFLAGS, &ifrequest, "can't get interface flags");
364 static void maybe_up_new_iface(void)
366 if (!(option_mask32 & FLAG_NO_AUTO))
370 struct ifreq ifrequest;
371 struct ethtool_drvinfo driver_info;
373 set_ifreq_to_ifname(&ifrequest);
374 driver_info.cmd = ETHTOOL_GDRVINFO;
375 ifrequest.ifr_data = &driver_info;
376 if (network_ioctl(SIOCETHTOOL, &ifrequest, NULL) == 0) {
377 char buf[sizeof("/xx:xx:xx:xx:xx:xx")];
381 set_ifreq_to_ifname(&ifrequest);
382 if (network_ioctl(SIOCGIFHWADDR, &ifrequest, NULL) == 0) {
383 sprintf(buf, "/%02X:%02X:%02X:%02X:%02X:%02X",
384 (uint8_t)(ifrequest.ifr_hwaddr.sa_data[0]),
385 (uint8_t)(ifrequest.ifr_hwaddr.sa_data[1]),
386 (uint8_t)(ifrequest.ifr_hwaddr.sa_data[2]),
387 (uint8_t)(ifrequest.ifr_hwaddr.sa_data[3]),
388 (uint8_t)(ifrequest.ifr_hwaddr.sa_data[4]),
389 (uint8_t)(ifrequest.ifr_hwaddr.sa_data[5]));
392 bb_error_msg("using interface %s%s with driver<%s> (version: %s)",
393 G.iface, buf, driver_info.driver, driver_info.version);
396 if (G.api_mode[0] == 'a')
397 G.api_method_num = API_AUTO;
400 static smallint detect_link(void)
405 return (option_mask32 & FLAG_MONITOR) ? IFSTATUS_DOWN : IFSTATUS_ERR;
407 /* Some drivers can't detect link status when the interface is down.
408 * I imagine detect_link_iff() is the most vulnerable.
409 * That's why -a "noauto" in an option, not a hardwired behavior.
411 if (!(option_mask32 & FLAG_NO_AUTO))
414 if (G.api_method_num == API_AUTO) {
418 sv_logmode = logmode;
419 for (i = 0; i < ARRAY_SIZE(method_table); i++) {
420 logmode = LOGMODE_NONE;
421 status = method_table[i].func();
422 logmode = sv_logmode;
423 if (status != IFSTATUS_ERR) {
424 G.api_method_num = i;
425 bb_error_msg("using %s detection mode", method_table[i].name);
430 status = method_table[G.api_method_num].func();
433 if (status == IFSTATUS_ERR) {
434 if (option_mask32 & FLAG_IGNORE_FAIL)
435 status = IFSTATUS_DOWN;
436 else if (option_mask32 & FLAG_IGNORE_FAIL_POSITIVE)
437 status = IFSTATUS_UP;
438 else if (G.api_mode[0] == 'a')
439 bb_error_msg("can't detect link status");
442 if (status != G.iface_last_status) {
443 G.iface_prev_status = G.iface_last_status;
444 G.iface_last_status = status;
450 static NOINLINE int check_existence_through_netlink(void)
455 iface_len = strlen(G.iface);
457 struct nlmsghdr *mhdr;
460 bytes = recv(netlink_fd, &replybuf, sizeof(replybuf), MSG_DONTWAIT);
463 return G.iface_exists;
467 bb_perror_msg("netlink: recv");
471 mhdr = (struct nlmsghdr*)replybuf;
473 if (!NLMSG_OK(mhdr, bytes)) {
474 bb_error_msg("netlink packet too small or truncated");
478 if (mhdr->nlmsg_type == RTM_NEWLINK || mhdr->nlmsg_type == RTM_DELLINK) {
482 if (mhdr->nlmsg_len < NLMSG_LENGTH(sizeof(struct ifinfomsg))) {
483 bb_error_msg("netlink packet too small or truncated");
487 attr = IFLA_RTA(NLMSG_DATA(mhdr));
488 attr_len = IFLA_PAYLOAD(mhdr);
490 while (RTA_OK(attr, attr_len)) {
491 if (attr->rta_type == IFLA_IFNAME) {
492 int len = RTA_PAYLOAD(attr);
496 && strncmp(G.iface, RTA_DATA(attr), len) == 0
498 G.iface_exists = (mhdr->nlmsg_type == RTM_NEWLINK);
501 attr = RTA_NEXT(attr, attr_len);
505 mhdr = NLMSG_NEXT(mhdr, bytes);
509 return G.iface_exists;
512 #if ENABLE_FEATURE_PIDFILE
513 static NOINLINE pid_t read_pid(const char *filename)
518 len = open_read_close(filename, buf, 127);
521 /* returns ULONG_MAX on error => -1 */
522 return bb_strtoul(buf, NULL, 10);
528 int ifplugd_main(int argc, char **argv) MAIN_EXTERNALLY_VISIBLE;
529 int ifplugd_main(int argc UNUSED_PARAM, char **argv)
533 const char *iface_status_str;
534 struct pollfd netlink_pollfd[1];
536 const char *api_mode_found;
537 #if ENABLE_FEATURE_PIDFILE
539 pid_t pid_from_pidfile;
544 opt_complementary = "t+:u+:d+";
545 opts = getopt32(argv, OPTION_STR,
546 &G.iface, &G.script_name, &G.poll_time, &G.delay_up,
547 &G.delay_down, &G.api_mode, &G.extra_arg);
550 applet_name = xasprintf("ifplugd(%s)", G.iface);
552 #if ENABLE_FEATURE_PIDFILE
553 pidfile_name = xasprintf(_PATH_VARRUN"ifplugd.%s.pid", G.iface);
554 pid_from_pidfile = read_pid(pidfile_name);
556 if (opts & FLAG_KILL) {
557 if (pid_from_pidfile > 0)
558 kill(pid_from_pidfile, SIGQUIT);
562 if (pid_from_pidfile > 0 && kill(pid_from_pidfile, 0) == 0)
563 bb_error_msg_and_die("daemon already running");
566 api_mode_found = strchr(api_modes, G.api_mode[0]);
568 bb_error_msg_and_die("unknown API mode '%s'", G.api_mode);
569 G.api_method_num = api_mode_found - api_modes;
571 if (!(opts & FLAG_NO_DAEMON))
572 bb_daemonize_or_rexec(DAEMON_CHDIR_ROOT, argv);
574 xmove_fd(xsocket(AF_INET, SOCK_DGRAM, 0), ioctl_fd);
575 if (opts & FLAG_MONITOR) {
576 struct sockaddr_nl addr;
577 int fd = xsocket(PF_NETLINK, SOCK_DGRAM, NETLINK_ROUTE);
579 memset(&addr, 0, sizeof(addr));
580 addr.nl_family = AF_NETLINK;
581 addr.nl_groups = RTMGRP_LINK;
582 addr.nl_pid = getpid();
584 xbind(fd, (struct sockaddr*)&addr, sizeof(addr));
585 xmove_fd(fd, netlink_fd);
588 write_pidfile(pidfile_name);
590 /* this can't be moved before socket creation */
591 if (!(opts & FLAG_NO_SYSLOG)) {
592 openlog(applet_name, 0, LOG_DAEMON);
593 logmode |= LOGMODE_SYSLOG;
600 | (1 << SIGHUP ) /* why we ignore it? */
601 /* | (1 << SIGCHLD) - run_script does not use it anymore */
604 bb_error_msg("started: %s", bb_banner);
606 if (opts & FLAG_MONITOR) {
607 struct ifreq ifrequest;
608 set_ifreq_to_ifname(&ifrequest);
609 G.iface_exists = (network_ioctl(SIOCGIFINDEX, &ifrequest, NULL) == 0);
613 maybe_up_new_iface();
615 iface_status = detect_link();
616 if (iface_status == IFSTATUS_ERR)
618 iface_status_str = strstatus(iface_status);
620 if (opts & FLAG_MONITOR) {
621 bb_error_msg("interface %s",
622 G.iface_exists ? "exists"
623 : "doesn't exist, waiting");
625 /* else we assume it always exists, but don't mislead user
626 * by potentially lying that it really exists */
628 if (G.iface_exists) {
629 bb_error_msg("link is %s", iface_status_str);
632 if ((!(opts & FLAG_NO_STARTUP)
633 && iface_status == IFSTATUS_UP
635 || (opts & FLAG_INITIAL_DOWN)
637 if (run_script(iface_status_str) != 0)
642 netlink_pollfd[0].fd = netlink_fd;
643 netlink_pollfd[0].events = POLLIN;
646 int iface_status_old;
647 int iface_exists_old;
649 switch (bb_got_signal) {
662 if (poll(netlink_pollfd,
663 (opts & FLAG_MONITOR) ? 1 : 0,
669 bb_perror_msg("poll");
673 iface_status_old = iface_status;
674 iface_exists_old = G.iface_exists;
676 if ((opts & FLAG_MONITOR)
677 && (netlink_pollfd[0].revents & POLLIN)
679 G.iface_exists = check_existence_through_netlink();
680 if (G.iface_exists < 0) /* error */
682 if (iface_exists_old != G.iface_exists) {
683 bb_error_msg("interface %sappeared",
684 G.iface_exists ? "" : "dis");
686 maybe_up_new_iface();
690 /* note: if !G.iface_exists, returns DOWN */
691 iface_status = detect_link();
692 if (iface_status == IFSTATUS_ERR) {
693 if (!(opts & FLAG_MONITOR))
695 iface_status = IFSTATUS_DOWN;
697 iface_status_str = strstatus(iface_status);
699 if (iface_status_old != iface_status) {
700 bb_error_msg("link is %s", iface_status_str);
703 /* link restored its old status before
704 * we run script. don't run the script: */
707 delay_time = monotonic_sec();
708 if (iface_status == IFSTATUS_UP)
709 delay_time += G.delay_up;
710 if (iface_status == IFSTATUS_DOWN)
711 delay_time += G.delay_down;
717 if (delay_time && (int)(monotonic_sec() - delay_time) >= 0) {
719 if (run_script(iface_status_str) != 0)
725 if (!(opts & FLAG_NO_SHUTDOWN)
726 && (iface_status == IFSTATUS_UP
727 || (iface_status == IFSTATUS_DOWN && delay_time)
730 setenv(IFPLUGD_ENV_PREVIOUS, strstatus(iface_status), 1);
731 setenv(IFPLUGD_ENV_CURRENT, strstatus(-1), 1);
732 run_script("down\0up"); /* reusing string */
736 remove_pidfile(pidfile_name);
737 bb_error_msg_and_die("exiting");