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_prev_status;
97 smallint iface_exists;
99 /* Used in getopt32, must have sizeof == sizeof(int) */
105 const char *api_mode;
106 const char *script_name;
107 const char *extra_arg;
109 smallint (*detect_link_func)(void);
110 smallint (*cached_detect_link_func)(void);
112 #define G (*ptr_to_globals)
113 #define INIT_G() do { \
114 SET_PTR_TO_GLOBALS(xzalloc(sizeof(G))); \
115 G.iface_last_status = -1; \
116 G.iface_exists = 1; \
121 G.script_name = "/etc/ifplugd/ifplugd.action"; \
125 static const char *strstatus(int status)
127 if (status == IFSTATUS_ERR)
129 return "down\0up" + (status * 5);
132 static int run_script(const char *action)
134 char *env_PREVIOUS, *env_CURRENT;
138 bb_error_msg("executing '%s %s %s'", G.script_name, G.iface, action);
140 argv[0] = (char*) G.script_name;
141 argv[1] = (char*) G.iface;
142 argv[2] = (char*) action;
143 argv[3] = (char*) G.extra_arg;
146 env_PREVIOUS = xasprintf("%s=%s", IFPLUGD_ENV_PREVIOUS, strstatus(G.iface_prev_status));
147 putenv(env_PREVIOUS);
148 env_CURRENT = xasprintf("%s=%s", IFPLUGD_ENV_CURRENT, strstatus(G.iface_last_status));
151 /* r < 0 - can't exec, 0 <= r < 0x180 - exited, >=0x180 - killed by sig (r-0x180) */
152 r = spawn_and_wait(argv);
154 unsetenv(IFPLUGD_ENV_PREVIOUS);
155 unsetenv(IFPLUGD_ENV_CURRENT);
159 bb_error_msg("exit code: %d", r & 0xff);
160 return (option_mask32 & FLAG_IGNORE_RETVAL) ? 0 : r;
163 static int network_ioctl(int request, void* data, const char *errmsg)
165 int r = ioctl(ioctl_fd, request, data);
167 bb_perror_msg("%s failed", errmsg);
171 static void set_ifreq_to_ifname(struct ifreq *ifreq)
173 memset(ifreq, 0, sizeof(struct ifreq));
174 strncpy_IFNAMSIZ(ifreq->ifr_name, G.iface);
177 static void up_iface(void)
179 struct ifreq ifrequest;
184 set_ifreq_to_ifname(&ifrequest);
185 if (network_ioctl(SIOCGIFFLAGS, &ifrequest, "getting interface flags") < 0) {
190 if (!(ifrequest.ifr_flags & IFF_UP)) {
191 ifrequest.ifr_flags |= IFF_UP;
192 /* Let user know we mess up with interface */
193 bb_error_msg("upping interface");
194 if (network_ioctl(SIOCSIFFLAGS, &ifrequest, "setting interface flags") < 0)
198 #if 0 /* why do we mess with IP addr? It's not our business */
199 if (network_ioctl(SIOCGIFADDR, &ifrequest, "can't get interface address") < 0) {
200 } else if (ifrequest.ifr_addr.sa_family != AF_INET) {
201 bb_perror_msg("the interface is not IP-based");
203 ((struct sockaddr_in*)(&ifrequest.ifr_addr))->sin_addr.s_addr = INADDR_ANY;
204 network_ioctl(SIOCSIFADDR, &ifrequest, "can't set interface address");
206 network_ioctl(SIOCGIFFLAGS, &ifrequest, "can't get interface flags");
210 static void maybe_up_new_iface(void)
212 if (!(option_mask32 & FLAG_NO_AUTO))
216 struct ifreq ifrequest;
217 struct ethtool_drvinfo driver_info;
219 set_ifreq_to_ifname(&ifrequest);
220 driver_info.cmd = ETHTOOL_GDRVINFO;
221 ifrequest.ifr_data = &driver_info;
222 if (network_ioctl(SIOCETHTOOL, &ifrequest, NULL) == 0) {
223 char buf[sizeof("/xx:xx:xx:xx:xx:xx")];
227 set_ifreq_to_ifname(&ifrequest);
228 if (network_ioctl(SIOCGIFHWADDR, &ifrequest, NULL) == 0) {
229 sprintf(buf, "/%02X:%02X:%02X:%02X:%02X:%02X",
230 (uint8_t)(ifrequest.ifr_hwaddr.sa_data[0]),
231 (uint8_t)(ifrequest.ifr_hwaddr.sa_data[1]),
232 (uint8_t)(ifrequest.ifr_hwaddr.sa_data[2]),
233 (uint8_t)(ifrequest.ifr_hwaddr.sa_data[3]),
234 (uint8_t)(ifrequest.ifr_hwaddr.sa_data[4]),
235 (uint8_t)(ifrequest.ifr_hwaddr.sa_data[5]));
238 bb_error_msg("using interface %s%s with driver<%s> (version: %s)",
239 G.iface, buf, driver_info.driver, driver_info.version);
243 G.cached_detect_link_func = NULL;
246 static smallint detect_link_mii(void)
249 struct mii_ioctl_data *mii = (void *)&ifreq.ifr_data;
251 set_ifreq_to_ifname(&ifreq);
253 if (network_ioctl(SIOCGMIIPHY, &ifreq, "SIOCGMIIPHY") < 0) {
259 if (network_ioctl(SIOCGMIIREG, &ifreq, "SIOCGMIIREG") < 0) {
263 return (mii->val_out & 0x0004) ? IFSTATUS_UP : IFSTATUS_DOWN;
266 static smallint detect_link_priv(void)
269 struct mii_ioctl_data *mii = (void *)&ifreq.ifr_data;
271 set_ifreq_to_ifname(&ifreq);
273 if (network_ioctl(SIOCDEVPRIVATE, &ifreq, "SIOCDEVPRIVATE") < 0) {
279 if (network_ioctl(SIOCDEVPRIVATE+1, &ifreq, "SIOCDEVPRIVATE+1") < 0) {
283 return (mii->val_out & 0x0004) ? IFSTATUS_UP : IFSTATUS_DOWN;
286 static smallint detect_link_ethtool(void)
289 struct ethtool_value edata;
291 set_ifreq_to_ifname(&ifreq);
293 edata.cmd = ETHTOOL_GLINK;
294 ifreq.ifr_data = (void*) &edata;
296 if (network_ioctl(SIOCETHTOOL, &ifreq, "ETHTOOL_GLINK") < 0) {
300 return edata.data ? IFSTATUS_UP : IFSTATUS_DOWN;
303 static smallint detect_link_iff(void)
307 set_ifreq_to_ifname(&ifreq);
309 if (network_ioctl(SIOCGIFFLAGS, &ifreq, "SIOCGIFFLAGS") < 0) {
313 /* If IFF_UP is not set (interface is down), IFF_RUNNING is never set
314 * regardless of link status. Simply continue to report last status -
315 * no point in reporting spurious link downs if interface is disabled
316 * by admin. When/if it will be brought up,
317 * we'll report real link status.
319 if (!(ifreq.ifr_flags & IFF_UP) && G.iface_last_status != IFSTATUS_ERR)
320 return G.iface_last_status;
322 return (ifreq.ifr_flags & IFF_RUNNING) ? IFSTATUS_UP : IFSTATUS_DOWN;
325 static smallint detect_link_wlan(void)
328 struct iwreq iwrequest;
329 uint8_t mac[ETH_ALEN];
331 memset(&iwrequest, 0, sizeof(iwrequest));
332 strncpy_IFNAMSIZ(iwrequest.ifr_ifrn.ifrn_name, G.iface);
334 if (network_ioctl(SIOCGIWAP, &iwrequest, "SIOCGIWAP") < 0) {
338 memcpy(mac, &iwrequest.u.ap_addr.sa_data, ETH_ALEN);
340 if (mac[0] == 0xFF || mac[0] == 0x44 || mac[0] == 0x00) {
341 for (i = 1; i < ETH_ALEN; ++i) {
342 if (mac[i] != mac[0])
345 return IFSTATUS_DOWN;
351 static smallint detect_link_auto(void)
353 static const struct {
355 smallint (*func)(void);
357 { "SIOCETHTOOL" , &detect_link_ethtool },
358 { "SIOCGMIIPHY" , &detect_link_mii },
359 { "SIOCDEVPRIVATE" , &detect_link_priv },
360 { "wireless extension", &detect_link_wlan },
361 { "IFF_RUNNING" , &detect_link_iff },
364 smallint iface_status;
367 if (G.cached_detect_link_func) {
368 iface_status = G.cached_detect_link_func();
369 if (iface_status != IFSTATUS_ERR)
373 sv_logmode = logmode;
374 for (i = 0; i < ARRAY_SIZE(method); i++) {
375 logmode = LOGMODE_NONE;
376 iface_status = method[i].func();
377 logmode = sv_logmode;
378 if (iface_status != IFSTATUS_ERR) {
379 G.cached_detect_link_func = method[i].func;
380 bb_error_msg("using %s detection mode", method[i].name);
387 static smallint detect_link(void)
392 return (option_mask32 & FLAG_MONITOR) ? IFSTATUS_DOWN : IFSTATUS_ERR;
394 /* Some drivers can't detect link status when the interface is down.
395 * I imagine detect_link_iff() is the most vulnerable.
396 * That's why -a "noauto" in an option, not a hardwired behavior.
398 if (!(option_mask32 & FLAG_NO_AUTO))
401 status = G.detect_link_func();
402 if (status == IFSTATUS_ERR) {
403 if (option_mask32 & FLAG_IGNORE_FAIL)
404 status = IFSTATUS_DOWN;
405 if (option_mask32 & FLAG_IGNORE_FAIL_POSITIVE)
406 status = IFSTATUS_UP;
409 if (status == IFSTATUS_ERR
410 && G.detect_link_func == detect_link_auto
412 bb_error_msg("can't detect link status");
415 if (status != G.iface_last_status) {
416 G.iface_prev_status = G.iface_last_status;
417 G.iface_last_status = status;
423 static NOINLINE int check_existence_through_netlink(void)
428 iface_len = strlen(G.iface);
430 struct nlmsghdr *mhdr;
433 bytes = recv(netlink_fd, &replybuf, sizeof(replybuf), MSG_DONTWAIT);
436 return G.iface_exists;
440 bb_perror_msg("netlink: recv");
444 mhdr = (struct nlmsghdr*)replybuf;
446 if (!NLMSG_OK(mhdr, bytes)) {
447 bb_error_msg("netlink packet too small or truncated");
451 if (mhdr->nlmsg_type == RTM_NEWLINK || mhdr->nlmsg_type == RTM_DELLINK) {
455 if (mhdr->nlmsg_len < NLMSG_LENGTH(sizeof(struct ifinfomsg))) {
456 bb_error_msg("netlink packet too small or truncated");
460 attr = IFLA_RTA(NLMSG_DATA(mhdr));
461 attr_len = IFLA_PAYLOAD(mhdr);
463 while (RTA_OK(attr, attr_len)) {
464 if (attr->rta_type == IFLA_IFNAME) {
465 int len = RTA_PAYLOAD(attr);
469 && strncmp(G.iface, RTA_DATA(attr), len) == 0
471 G.iface_exists = (mhdr->nlmsg_type == RTM_NEWLINK);
474 attr = RTA_NEXT(attr, attr_len);
478 mhdr = NLMSG_NEXT(mhdr, bytes);
482 return G.iface_exists;
485 static NOINLINE int netlink_open(void)
488 struct sockaddr_nl addr;
490 fd = xsocket(PF_NETLINK, SOCK_DGRAM, NETLINK_ROUTE);
492 memset(&addr, 0, sizeof(addr));
493 addr.nl_family = AF_NETLINK;
494 addr.nl_groups = RTMGRP_LINK;
495 addr.nl_pid = getpid();
497 xbind(fd, (struct sockaddr*)&addr, sizeof(addr));
502 #if ENABLE_FEATURE_PIDFILE
503 static NOINLINE pid_t read_pid(const char *filename)
508 len = open_read_close(filename, buf, 127);
511 /* returns ULONG_MAX on error => -1 */
512 return bb_strtoul(buf, NULL, 10);
518 int ifplugd_main(int argc, char **argv) MAIN_EXTERNALLY_VISIBLE;
519 int ifplugd_main(int argc UNUSED_PARAM, char **argv)
523 const char *iface_status_str;
524 struct pollfd netlink_pollfd[1];
526 #if ENABLE_FEATURE_PIDFILE
528 pid_t pid_from_pidfile;
533 opt_complementary = "t+:u+:d+";
534 opts = getopt32(argv, OPTION_STR,
535 &G.iface, &G.script_name, &G.poll_time, &G.delay_up,
536 &G.delay_down, &G.api_mode, &G.extra_arg);
539 applet_name = xasprintf("ifplugd(%s)", G.iface);
541 #if ENABLE_FEATURE_PIDFILE
542 pidfile_name = xasprintf(_PATH_VARRUN"ifplugd.%s.pid", G.iface);
543 pid_from_pidfile = read_pid(pidfile_name);
545 if (opts & FLAG_KILL) {
546 if (pid_from_pidfile > 0)
547 kill(pid_from_pidfile, SIGQUIT);
551 if (pid_from_pidfile > 0 && kill(pid_from_pidfile, 0) == 0)
552 bb_error_msg_and_die("daemon already running");
555 switch (G.api_mode[0]) {
557 G.detect_link_func = detect_link_auto;
560 G.detect_link_func = detect_link_ethtool;
563 G.detect_link_func = detect_link_mii;
566 G.detect_link_func = detect_link_priv;
569 G.detect_link_func = detect_link_wlan;
572 G.detect_link_func = detect_link_iff;
575 bb_error_msg_and_die("unknown API mode '%s'", G.api_mode);
578 if (!(opts & FLAG_NO_DAEMON))
579 bb_daemonize_or_rexec(DAEMON_CHDIR_ROOT, argv);
581 xmove_fd(xsocket(AF_INET, SOCK_DGRAM, 0), ioctl_fd);
582 if (opts & FLAG_MONITOR) {
583 xmove_fd(netlink_open(), netlink_fd);
586 write_pidfile(pidfile_name);
588 /* this can't be moved before socket creation */
589 if (!(opts & FLAG_NO_SYSLOG)) {
590 openlog(applet_name, 0, LOG_DAEMON);
591 logmode |= LOGMODE_SYSLOG;
598 | (1 << SIGHUP ) /* why we ignore it? */
599 /* | (1 << SIGCHLD) - run_script does not use it anymore */
602 bb_error_msg("started: %s", bb_banner);
604 if (opts & FLAG_MONITOR) {
605 struct ifreq ifrequest;
606 set_ifreq_to_ifname(&ifrequest);
607 G.iface_exists = (network_ioctl(SIOCGIFINDEX, &ifrequest, NULL) == 0);
611 maybe_up_new_iface();
613 iface_status = detect_link();
614 if (iface_status == IFSTATUS_ERR)
616 iface_status_str = strstatus(iface_status);
618 if (opts & FLAG_MONITOR) {
619 bb_error_msg("interface %s",
620 G.iface_exists ? "exists"
621 : "doesn't exist, waiting");
623 /* else we assume it always exists, but don't mislead user
624 * by potentially lying that it really exists */
626 if (G.iface_exists) {
627 bb_error_msg("link is %s", iface_status_str);
630 if ((!(opts & FLAG_NO_STARTUP)
631 && iface_status == IFSTATUS_UP
633 || (opts & FLAG_INITIAL_DOWN)
635 if (run_script(iface_status_str) != 0)
640 netlink_pollfd[0].fd = netlink_fd;
641 netlink_pollfd[0].events = POLLIN;
644 int iface_status_old;
645 int iface_exists_old;
647 switch (bb_got_signal) {
660 if (poll(netlink_pollfd,
661 (opts & FLAG_MONITOR) ? 1 : 0,
667 bb_perror_msg("poll");
671 iface_status_old = iface_status;
672 iface_exists_old = G.iface_exists;
674 if ((opts & FLAG_MONITOR)
675 && (netlink_pollfd[0].revents & POLLIN)
677 G.iface_exists = check_existence_through_netlink();
678 if (G.iface_exists < 0) /* error */
680 if (iface_exists_old != G.iface_exists) {
681 bb_error_msg("interface %sappeared",
682 G.iface_exists ? "" : "dis");
684 maybe_up_new_iface();
688 /* note: if !G.iface_exists, returns DOWN */
689 iface_status = detect_link();
690 if (iface_status == IFSTATUS_ERR) {
691 if (!(opts & FLAG_MONITOR))
693 iface_status = IFSTATUS_DOWN;
695 iface_status_str = strstatus(iface_status);
697 if (iface_status_old != iface_status) {
698 bb_error_msg("link is %s", iface_status_str);
701 /* link restored its old status before
702 * we run script. don't run the script: */
705 delay_time = monotonic_sec();
706 if (iface_status == IFSTATUS_UP)
707 delay_time += G.delay_up;
708 if (iface_status == IFSTATUS_DOWN)
709 delay_time += G.delay_down;
715 if (delay_time && (int)(monotonic_sec() - delay_time) >= 0) {
717 if (run_script(iface_status_str) != 0)
723 if (!(opts & FLAG_NO_SHUTDOWN)
724 && (iface_status == IFSTATUS_UP
725 || (iface_status == IFSTATUS_DOWN && delay_time)
728 setenv(IFPLUGD_ENV_PREVIOUS, strstatus(iface_status), 1);
729 setenv(IFPLUGD_ENV_CURRENT, strstatus(-1), 1);
730 run_script("down\0up"); /* reusing string */
734 remove_pidfile(pidfile_name);
735 bb_error_msg_and_die("exiting");