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 script on daemon startup"
26 //usage: "\n -q Don't run script on daemon quit"
27 //usage: "\n -l Run script on startup even if no cable is detected"
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 #include <net/ethernet.h>
41 #include <linux/netlink.h>
42 #include <linux/rtnetlink.h>
43 #include <linux/sockios.h>
47 #include <linux/wireless.h>
50 From initial port to busybox, removed most of the redundancy by
51 converting implementation of a polymorphic interface to the strict
52 functional style. The main role is run a script when link state
53 changed, other activities like audio signal or detailed reports
54 are on the script itself.
56 One questionable point of the design is netlink usage:
58 We have 1 second timeout by default to poll the link status,
59 it is short enough so that there are no real benefits in
60 using netlink to get "instantaneous" interface creation/deletion
61 notifications. We can check for interface existence by just
62 doing some fast ioctl using its name.
64 Netlink code then can be just dropped (1k or more?)
68 #define IFPLUGD_ENV_PREVIOUS "IFPLUGD_PREVIOUS"
69 #define IFPLUGD_ENV_CURRENT "IFPLUGD_CURRENT"
72 FLAG_NO_AUTO = 1 << 0, // -a, Do not enable interface automatically
73 FLAG_NO_DAEMON = 1 << 1, // -n, Do not daemonize
74 FLAG_NO_SYSLOG = 1 << 2, // -s, Do not use syslog, use stderr instead
75 FLAG_IGNORE_FAIL = 1 << 3, // -f, Ignore detection failure, retry instead (failure is treated as DOWN)
76 FLAG_IGNORE_FAIL_POSITIVE = 1 << 4, // -F, Ignore detection failure, retry instead (failure is treated as UP)
77 FLAG_IFACE = 1 << 5, // -i, Specify ethernet interface
78 FLAG_RUN = 1 << 6, // -r, Specify program to execute
79 FLAG_IGNORE_RETVAL = 1 << 7, // -I, Don't exit on nonzero return value of program executed
80 FLAG_POLL_TIME = 1 << 8, // -t, Specify poll time in seconds
81 FLAG_DELAY_UP = 1 << 9, // -u, Specify delay for configuring interface
82 FLAG_DELAY_DOWN = 1 << 10, // -d, Specify delay for deconfiguring interface
83 FLAG_API_MODE = 1 << 11, // -m, Force API mode (mii, priv, ethtool, wlan, auto)
84 FLAG_NO_STARTUP = 1 << 12, // -p, Don't run script on daemon startup
85 FLAG_NO_SHUTDOWN = 1 << 13, // -q, Don't run script on daemon quit
86 FLAG_INITIAL_DOWN = 1 << 14, // -l, Run "down" script on startup if no cable is detected
87 FLAG_EXTRA_ARG = 1 << 15, // -x, Specify an extra argument for action script
88 FLAG_MONITOR = 1 << 16, // -M, Use interface monitoring
89 #if ENABLE_FEATURE_PIDFILE
90 FLAG_KILL = 1 << 17, // -k, Kill a running daemon
93 #if ENABLE_FEATURE_PIDFILE
94 # define OPTION_STR "+ansfFi:r:It:u:d:m:pqlx:Mk"
96 # define OPTION_STR "+ansfFi:r:It:u:d:m:pqlx:M"
99 enum { // interface status
105 enum { // constant fds
111 smallint iface_last_status;
112 smallint iface_prev_status;
113 smallint iface_exists;
114 smallint api_method_num;
116 /* Used in getopt32, must have sizeof == sizeof(int) */
122 const char *api_mode;
123 const char *script_name;
124 const char *extra_arg;
126 #define G (*ptr_to_globals)
127 #define INIT_G() do { \
128 SET_PTR_TO_GLOBALS(xzalloc(sizeof(G))); \
129 G.iface_last_status = -1; \
130 G.iface_exists = 1; \
135 G.script_name = "/etc/ifplugd/ifplugd.action"; \
139 /* Utility routines */
141 static void set_ifreq_to_ifname(struct ifreq *ifreq)
143 memset(ifreq, 0, sizeof(struct ifreq));
144 strncpy_IFNAMSIZ(ifreq->ifr_name, G.iface);
147 static int network_ioctl(int request, void* data, const char *errmsg)
149 int r = ioctl(ioctl_fd, request, data);
151 bb_perror_msg("%s failed", errmsg);
155 /* Link detection routines and table */
157 static smallint detect_link_mii(void)
159 /* char buffer instead of bona-fide struct avoids aliasing warning */
160 char buf[sizeof(struct ifreq)];
161 struct ifreq *const ifreq = (void *)buf;
163 struct mii_ioctl_data *mii = (void *)&ifreq->ifr_data;
165 set_ifreq_to_ifname(ifreq);
167 if (network_ioctl(SIOCGMIIPHY, ifreq, "SIOCGMIIPHY") < 0) {
173 if (network_ioctl(SIOCGMIIREG, ifreq, "SIOCGMIIREG") < 0) {
177 return (mii->val_out & 0x0004) ? IFSTATUS_UP : IFSTATUS_DOWN;
180 static smallint detect_link_priv(void)
182 /* char buffer instead of bona-fide struct avoids aliasing warning */
183 char buf[sizeof(struct ifreq)];
184 struct ifreq *const ifreq = (void *)buf;
186 struct mii_ioctl_data *mii = (void *)&ifreq->ifr_data;
188 set_ifreq_to_ifname(ifreq);
190 if (network_ioctl(SIOCDEVPRIVATE, ifreq, "SIOCDEVPRIVATE") < 0) {
196 if (network_ioctl(SIOCDEVPRIVATE+1, ifreq, "SIOCDEVPRIVATE+1") < 0) {
200 return (mii->val_out & 0x0004) ? IFSTATUS_UP : IFSTATUS_DOWN;
203 static smallint detect_link_ethtool(void)
206 struct ethtool_value edata;
208 set_ifreq_to_ifname(&ifreq);
210 edata.cmd = ETHTOOL_GLINK;
211 ifreq.ifr_data = (void*) &edata;
213 if (network_ioctl(SIOCETHTOOL, &ifreq, "ETHTOOL_GLINK") < 0) {
217 return edata.data ? IFSTATUS_UP : IFSTATUS_DOWN;
220 static smallint detect_link_iff(void)
224 set_ifreq_to_ifname(&ifreq);
226 if (network_ioctl(SIOCGIFFLAGS, &ifreq, "SIOCGIFFLAGS") < 0) {
230 /* If IFF_UP is not set (interface is down), IFF_RUNNING is never set
231 * regardless of link status. Simply continue to report last status -
232 * no point in reporting spurious link downs if interface is disabled
233 * by admin. When/if it will be brought up,
234 * we'll report real link status.
236 if (!(ifreq.ifr_flags & IFF_UP) && G.iface_last_status != IFSTATUS_ERR)
237 return G.iface_last_status;
239 return (ifreq.ifr_flags & IFF_RUNNING) ? IFSTATUS_UP : IFSTATUS_DOWN;
242 static smallint detect_link_wlan(void)
245 struct iwreq iwrequest;
246 uint8_t mac[ETH_ALEN];
248 memset(&iwrequest, 0, sizeof(iwrequest));
249 strncpy_IFNAMSIZ(iwrequest.ifr_ifrn.ifrn_name, G.iface);
251 if (network_ioctl(SIOCGIWAP, &iwrequest, "SIOCGIWAP") < 0) {
255 memcpy(mac, &iwrequest.u.ap_addr.sa_data, ETH_ALEN);
257 if (mac[0] == 0xFF || mac[0] == 0x44 || mac[0] == 0x00) {
258 for (i = 1; i < ETH_ALEN; ++i) {
259 if (mac[i] != mac[0])
262 return IFSTATUS_DOWN;
277 static const char api_modes[] ALIGN1 = "empwia";
279 static const struct {
281 smallint (*func)(void);
283 { "SIOCETHTOOL" , &detect_link_ethtool },
284 { "SIOCGMIIPHY" , &detect_link_mii },
285 { "SIOCDEVPRIVATE" , &detect_link_priv },
286 { "wireless extension", &detect_link_wlan },
287 { "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)
454 iface_len = strlen(G.iface);
456 struct nlmsghdr *mhdr;
459 bytes = recv(netlink_fd, &replybuf, sizeof(replybuf), MSG_DONTWAIT);
462 return G.iface_exists;
466 bb_perror_msg("netlink: recv");
470 mhdr = (struct nlmsghdr*)replybuf;
472 if (!NLMSG_OK(mhdr, bytes)) {
473 bb_error_msg("netlink packet too small or truncated");
477 if (mhdr->nlmsg_type == RTM_NEWLINK || mhdr->nlmsg_type == RTM_DELLINK) {
481 if (mhdr->nlmsg_len < NLMSG_LENGTH(sizeof(struct ifinfomsg))) {
482 bb_error_msg("netlink packet too small or truncated");
486 attr = IFLA_RTA(NLMSG_DATA(mhdr));
487 attr_len = IFLA_PAYLOAD(mhdr);
489 while (RTA_OK(attr, attr_len)) {
490 if (attr->rta_type == IFLA_IFNAME) {
491 int len = RTA_PAYLOAD(attr);
495 && strncmp(G.iface, RTA_DATA(attr), len) == 0
497 G.iface_exists = (mhdr->nlmsg_type == RTM_NEWLINK);
500 attr = RTA_NEXT(attr, attr_len);
504 mhdr = NLMSG_NEXT(mhdr, bytes);
508 return G.iface_exists;
511 #if ENABLE_FEATURE_PIDFILE
512 static NOINLINE pid_t read_pid(const char *filename)
517 len = open_read_close(filename, buf, 127);
520 /* returns ULONG_MAX on error => -1 */
521 return bb_strtoul(buf, NULL, 10);
527 int ifplugd_main(int argc, char **argv) MAIN_EXTERNALLY_VISIBLE;
528 int ifplugd_main(int argc UNUSED_PARAM, char **argv)
532 const char *iface_status_str;
533 struct pollfd netlink_pollfd[1];
535 const char *api_mode_found;
536 #if ENABLE_FEATURE_PIDFILE
538 pid_t pid_from_pidfile;
543 opt_complementary = "t+:u+:d+";
544 opts = getopt32(argv, OPTION_STR,
545 &G.iface, &G.script_name, &G.poll_time, &G.delay_up,
546 &G.delay_down, &G.api_mode, &G.extra_arg);
549 applet_name = xasprintf("ifplugd(%s)", G.iface);
551 #if ENABLE_FEATURE_PIDFILE
552 pidfile_name = xasprintf(_PATH_VARRUN"ifplugd.%s.pid", G.iface);
553 pid_from_pidfile = read_pid(pidfile_name);
555 if (opts & FLAG_KILL) {
556 if (pid_from_pidfile > 0)
557 kill(pid_from_pidfile, SIGQUIT);
561 if (pid_from_pidfile > 0 && kill(pid_from_pidfile, 0) == 0)
562 bb_error_msg_and_die("daemon already running");
565 api_mode_found = strchr(api_modes, G.api_mode[0]);
567 bb_error_msg_and_die("unknown API mode '%s'", G.api_mode);
568 G.api_method_num = api_mode_found - api_modes;
570 if (!(opts & FLAG_NO_DAEMON))
571 bb_daemonize_or_rexec(DAEMON_CHDIR_ROOT, argv);
573 xmove_fd(xsocket(AF_INET, SOCK_DGRAM, 0), ioctl_fd);
574 if (opts & FLAG_MONITOR) {
575 struct sockaddr_nl addr;
576 int fd = xsocket(PF_NETLINK, SOCK_DGRAM, NETLINK_ROUTE);
578 memset(&addr, 0, sizeof(addr));
579 addr.nl_family = AF_NETLINK;
580 addr.nl_groups = RTMGRP_LINK;
581 addr.nl_pid = getpid();
583 xbind(fd, (struct sockaddr*)&addr, sizeof(addr));
584 xmove_fd(fd, netlink_fd);
587 write_pidfile(pidfile_name);
589 /* this can't be moved before socket creation */
590 if (!(opts & FLAG_NO_SYSLOG)) {
591 openlog(applet_name, 0, LOG_DAEMON);
592 logmode |= LOGMODE_SYSLOG;
599 | (1 << SIGHUP ) /* why we ignore it? */
600 /* | (1 << SIGCHLD) - run_script does not use it anymore */
603 bb_error_msg("started: %s", bb_banner);
605 if (opts & FLAG_MONITOR) {
606 struct ifreq ifrequest;
607 set_ifreq_to_ifname(&ifrequest);
608 G.iface_exists = (network_ioctl(SIOCGIFINDEX, &ifrequest, NULL) == 0);
612 maybe_up_new_iface();
614 iface_status = detect_link();
615 if (iface_status == IFSTATUS_ERR)
617 iface_status_str = strstatus(iface_status);
619 if (opts & FLAG_MONITOR) {
620 bb_error_msg("interface %s",
621 G.iface_exists ? "exists"
622 : "doesn't exist, waiting");
624 /* else we assume it always exists, but don't mislead user
625 * by potentially lying that it really exists */
627 if (G.iface_exists) {
628 bb_error_msg("link is %s", iface_status_str);
631 if ((!(opts & FLAG_NO_STARTUP)
632 && iface_status == IFSTATUS_UP
634 || (opts & FLAG_INITIAL_DOWN)
636 if (run_script(iface_status_str) != 0)
641 netlink_pollfd[0].fd = netlink_fd;
642 netlink_pollfd[0].events = POLLIN;
645 int iface_status_old;
646 int iface_exists_old;
648 switch (bb_got_signal) {
661 if (poll(netlink_pollfd,
662 (opts & FLAG_MONITOR) ? 1 : 0,
668 bb_perror_msg("poll");
672 iface_status_old = iface_status;
673 iface_exists_old = G.iface_exists;
675 if ((opts & FLAG_MONITOR)
676 && (netlink_pollfd[0].revents & POLLIN)
678 G.iface_exists = check_existence_through_netlink();
679 if (G.iface_exists < 0) /* error */
681 if (iface_exists_old != G.iface_exists) {
682 bb_error_msg("interface %sappeared",
683 G.iface_exists ? "" : "dis");
685 maybe_up_new_iface();
689 /* note: if !G.iface_exists, returns DOWN */
690 iface_status = detect_link();
691 if (iface_status == IFSTATUS_ERR) {
692 if (!(opts & FLAG_MONITOR))
694 iface_status = IFSTATUS_DOWN;
696 iface_status_str = strstatus(iface_status);
698 if (iface_status_old != iface_status) {
699 bb_error_msg("link is %s", iface_status_str);
702 /* link restored its old status before
703 * we run script. don't run the script: */
706 delay_time = monotonic_sec();
707 if (iface_status == IFSTATUS_UP)
708 delay_time += G.delay_up;
709 if (iface_status == IFSTATUS_DOWN)
710 delay_time += G.delay_down;
716 if (delay_time && (int)(monotonic_sec() - delay_time) >= 0) {
718 if (run_script(iface_status_str) != 0)
724 if (!(opts & FLAG_NO_SHUTDOWN)
725 && (iface_status == IFSTATUS_UP
726 || (iface_status == IFSTATUS_DOWN && delay_time)
729 setenv(IFPLUGD_ENV_PREVIOUS, strstatus(iface_status), 1);
730 setenv(IFPLUGD_ENV_CURRENT, strstatus(-1), 1);
731 run_script("down\0up"); /* reusing string */
735 remove_pidfile(pidfile_name);
736 bb_error_msg_and_die("exiting");