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.
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"
74 enum { // interface status
80 enum { // constant fds
86 smallint iface_last_status;
87 smallint iface_prev_status;
88 smallint iface_exists;
89 smallint api_method_num;
91 /* Used in getopt32, must have sizeof == sizeof(int) */
98 const char *script_name;
99 const char *extra_arg;
101 #define G (*ptr_to_globals)
102 #define INIT_G() do { \
103 SET_PTR_TO_GLOBALS(xzalloc(sizeof(G))); \
104 G.iface_last_status = -1; \
105 G.iface_exists = 1; \
110 G.script_name = "/etc/ifplugd/ifplugd.action"; \
114 /* Utility routines */
116 static void set_ifreq_to_ifname(struct ifreq *ifreq)
118 memset(ifreq, 0, sizeof(struct ifreq));
119 strncpy_IFNAMSIZ(ifreq->ifr_name, G.iface);
122 static int network_ioctl(int request, void* data, const char *errmsg)
124 int r = ioctl(ioctl_fd, request, data);
126 bb_perror_msg("%s failed", errmsg);
130 /* Link detection routines and table */
132 static smallint detect_link_mii(void)
134 /* char buffer instead of bona-fide struct avoids aliasing warning */
135 char buf[sizeof(struct ifreq)];
136 struct ifreq *const ifreq = (void *)buf;
138 struct mii_ioctl_data *mii = (void *)&ifreq->ifr_data;
140 set_ifreq_to_ifname(ifreq);
142 if (network_ioctl(SIOCGMIIPHY, ifreq, "SIOCGMIIPHY") < 0) {
148 if (network_ioctl(SIOCGMIIREG, ifreq, "SIOCGMIIREG") < 0) {
152 return (mii->val_out & 0x0004) ? IFSTATUS_UP : IFSTATUS_DOWN;
155 static smallint detect_link_priv(void)
157 /* char buffer instead of bona-fide struct avoids aliasing warning */
158 char buf[sizeof(struct ifreq)];
159 struct ifreq *const ifreq = (void *)buf;
161 struct mii_ioctl_data *mii = (void *)&ifreq->ifr_data;
163 set_ifreq_to_ifname(ifreq);
165 if (network_ioctl(SIOCDEVPRIVATE, ifreq, "SIOCDEVPRIVATE") < 0) {
171 if (network_ioctl(SIOCDEVPRIVATE+1, ifreq, "SIOCDEVPRIVATE+1") < 0) {
175 return (mii->val_out & 0x0004) ? IFSTATUS_UP : IFSTATUS_DOWN;
178 static smallint detect_link_ethtool(void)
181 struct ethtool_value edata;
183 set_ifreq_to_ifname(&ifreq);
185 edata.cmd = ETHTOOL_GLINK;
186 ifreq.ifr_data = (void*) &edata;
188 if (network_ioctl(SIOCETHTOOL, &ifreq, "ETHTOOL_GLINK") < 0) {
192 return edata.data ? IFSTATUS_UP : IFSTATUS_DOWN;
195 static smallint detect_link_iff(void)
199 set_ifreq_to_ifname(&ifreq);
201 if (network_ioctl(SIOCGIFFLAGS, &ifreq, "SIOCGIFFLAGS") < 0) {
205 /* If IFF_UP is not set (interface is down), IFF_RUNNING is never set
206 * regardless of link status. Simply continue to report last status -
207 * no point in reporting spurious link downs if interface is disabled
208 * by admin. When/if it will be brought up,
209 * we'll report real link status.
211 if (!(ifreq.ifr_flags & IFF_UP) && G.iface_last_status != IFSTATUS_ERR)
212 return G.iface_last_status;
214 return (ifreq.ifr_flags & IFF_RUNNING) ? IFSTATUS_UP : IFSTATUS_DOWN;
217 static smallint detect_link_wlan(void)
220 struct iwreq iwrequest;
221 uint8_t mac[ETH_ALEN];
223 memset(&iwrequest, 0, sizeof(iwrequest));
224 strncpy_IFNAMSIZ(iwrequest.ifr_ifrn.ifrn_name, G.iface);
226 if (network_ioctl(SIOCGIWAP, &iwrequest, "SIOCGIWAP") < 0) {
230 memcpy(mac, &iwrequest.u.ap_addr.sa_data, ETH_ALEN);
232 if (mac[0] == 0xFF || mac[0] == 0x44 || mac[0] == 0x00) {
233 for (i = 1; i < ETH_ALEN; ++i) {
234 if (mac[i] != mac[0])
237 return IFSTATUS_DOWN;
252 static const char api_modes[] ALIGN1 = "empwia";
254 static const struct {
256 smallint (*func)(void);
258 { "SIOCETHTOOL" , &detect_link_ethtool },
259 { "SIOCGMIIPHY" , &detect_link_mii },
260 { "SIOCDEVPRIVATE" , &detect_link_priv },
261 { "wireless extension", &detect_link_wlan },
262 { "IFF_RUNNING" , &detect_link_iff },
267 static const char *strstatus(int status)
269 if (status == IFSTATUS_ERR)
271 return "down\0up" + (status * 5);
274 static int run_script(const char *action)
276 char *env_PREVIOUS, *env_CURRENT;
280 bb_error_msg("executing '%s %s %s'", G.script_name, G.iface, action);
282 argv[0] = (char*) G.script_name;
283 argv[1] = (char*) G.iface;
284 argv[2] = (char*) action;
285 argv[3] = (char*) G.extra_arg;
288 env_PREVIOUS = xasprintf("%s=%s", IFPLUGD_ENV_PREVIOUS, strstatus(G.iface_prev_status));
289 putenv(env_PREVIOUS);
290 env_CURRENT = xasprintf("%s=%s", IFPLUGD_ENV_CURRENT, strstatus(G.iface_last_status));
293 /* r < 0 - can't exec, 0 <= r < 0x180 - exited, >=0x180 - killed by sig (r-0x180) */
294 r = spawn_and_wait(argv);
296 unsetenv(IFPLUGD_ENV_PREVIOUS);
297 unsetenv(IFPLUGD_ENV_CURRENT);
301 bb_error_msg("exit code: %d", r & 0xff);
302 return (option_mask32 & FLAG_IGNORE_RETVAL) ? 0 : r;
305 static void up_iface(void)
307 struct ifreq ifrequest;
312 set_ifreq_to_ifname(&ifrequest);
313 if (network_ioctl(SIOCGIFFLAGS, &ifrequest, "getting interface flags") < 0) {
318 if (!(ifrequest.ifr_flags & IFF_UP)) {
319 ifrequest.ifr_flags |= IFF_UP;
320 /* Let user know we mess up with interface */
321 bb_error_msg("upping interface");
322 if (network_ioctl(SIOCSIFFLAGS, &ifrequest, "setting interface flags") < 0)
326 #if 0 /* why do we mess with IP addr? It's not our business */
327 if (network_ioctl(SIOCGIFADDR, &ifrequest, "can't get interface address") < 0) {
328 } else if (ifrequest.ifr_addr.sa_family != AF_INET) {
329 bb_perror_msg("the interface is not IP-based");
331 ((struct sockaddr_in*)(&ifrequest.ifr_addr))->sin_addr.s_addr = INADDR_ANY;
332 network_ioctl(SIOCSIFADDR, &ifrequest, "can't set interface address");
334 network_ioctl(SIOCGIFFLAGS, &ifrequest, "can't get interface flags");
338 static void maybe_up_new_iface(void)
340 if (!(option_mask32 & FLAG_NO_AUTO))
344 struct ifreq ifrequest;
345 struct ethtool_drvinfo driver_info;
347 set_ifreq_to_ifname(&ifrequest);
348 driver_info.cmd = ETHTOOL_GDRVINFO;
349 ifrequest.ifr_data = &driver_info;
350 if (network_ioctl(SIOCETHTOOL, &ifrequest, NULL) == 0) {
351 char buf[sizeof("/xx:xx:xx:xx:xx:xx")];
355 set_ifreq_to_ifname(&ifrequest);
356 if (network_ioctl(SIOCGIFHWADDR, &ifrequest, NULL) == 0) {
357 sprintf(buf, "/%02X:%02X:%02X:%02X:%02X:%02X",
358 (uint8_t)(ifrequest.ifr_hwaddr.sa_data[0]),
359 (uint8_t)(ifrequest.ifr_hwaddr.sa_data[1]),
360 (uint8_t)(ifrequest.ifr_hwaddr.sa_data[2]),
361 (uint8_t)(ifrequest.ifr_hwaddr.sa_data[3]),
362 (uint8_t)(ifrequest.ifr_hwaddr.sa_data[4]),
363 (uint8_t)(ifrequest.ifr_hwaddr.sa_data[5]));
366 bb_error_msg("using interface %s%s with driver<%s> (version: %s)",
367 G.iface, buf, driver_info.driver, driver_info.version);
370 if (G.api_mode[0] == 'a')
371 G.api_method_num = API_AUTO;
374 static smallint detect_link(void)
379 return (option_mask32 & FLAG_MONITOR) ? IFSTATUS_DOWN : IFSTATUS_ERR;
381 /* Some drivers can't detect link status when the interface is down.
382 * I imagine detect_link_iff() is the most vulnerable.
383 * That's why -a "noauto" in an option, not a hardwired behavior.
385 if (!(option_mask32 & FLAG_NO_AUTO))
388 if (G.api_method_num == API_AUTO) {
392 sv_logmode = logmode;
393 for (i = 0; i < ARRAY_SIZE(method_table); i++) {
394 logmode = LOGMODE_NONE;
395 status = method_table[i].func();
396 logmode = sv_logmode;
397 if (status != IFSTATUS_ERR) {
398 G.api_method_num = i;
399 bb_error_msg("using %s detection mode", method_table[i].name);
404 status = method_table[G.api_method_num].func();
407 if (status == IFSTATUS_ERR) {
408 if (option_mask32 & FLAG_IGNORE_FAIL)
409 status = IFSTATUS_DOWN;
410 else if (option_mask32 & FLAG_IGNORE_FAIL_POSITIVE)
411 status = IFSTATUS_UP;
412 else if (G.api_mode[0] == 'a')
413 bb_error_msg("can't detect link status");
416 if (status != G.iface_last_status) {
417 G.iface_prev_status = G.iface_last_status;
418 G.iface_last_status = status;
424 static NOINLINE int check_existence_through_netlink(void)
429 iface_len = strlen(G.iface);
431 struct nlmsghdr *mhdr;
434 bytes = recv(netlink_fd, &replybuf, sizeof(replybuf), MSG_DONTWAIT);
437 return G.iface_exists;
441 bb_perror_msg("netlink: recv");
445 mhdr = (struct nlmsghdr*)replybuf;
447 if (!NLMSG_OK(mhdr, bytes)) {
448 bb_error_msg("netlink packet too small or truncated");
452 if (mhdr->nlmsg_type == RTM_NEWLINK || mhdr->nlmsg_type == RTM_DELLINK) {
456 if (mhdr->nlmsg_len < NLMSG_LENGTH(sizeof(struct ifinfomsg))) {
457 bb_error_msg("netlink packet too small or truncated");
461 attr = IFLA_RTA(NLMSG_DATA(mhdr));
462 attr_len = IFLA_PAYLOAD(mhdr);
464 while (RTA_OK(attr, attr_len)) {
465 if (attr->rta_type == IFLA_IFNAME) {
466 int len = RTA_PAYLOAD(attr);
470 && strncmp(G.iface, RTA_DATA(attr), len) == 0
472 G.iface_exists = (mhdr->nlmsg_type == RTM_NEWLINK);
475 attr = RTA_NEXT(attr, attr_len);
479 mhdr = NLMSG_NEXT(mhdr, bytes);
483 return G.iface_exists;
486 #if ENABLE_FEATURE_PIDFILE
487 static NOINLINE pid_t read_pid(const char *filename)
492 len = open_read_close(filename, buf, 127);
495 /* returns ULONG_MAX on error => -1 */
496 return bb_strtoul(buf, NULL, 10);
502 int ifplugd_main(int argc, char **argv) MAIN_EXTERNALLY_VISIBLE;
503 int ifplugd_main(int argc UNUSED_PARAM, char **argv)
507 const char *iface_status_str;
508 struct pollfd netlink_pollfd[1];
510 const char *api_mode_found;
511 #if ENABLE_FEATURE_PIDFILE
513 pid_t pid_from_pidfile;
518 opt_complementary = "t+:u+:d+";
519 opts = getopt32(argv, OPTION_STR,
520 &G.iface, &G.script_name, &G.poll_time, &G.delay_up,
521 &G.delay_down, &G.api_mode, &G.extra_arg);
524 applet_name = xasprintf("ifplugd(%s)", G.iface);
526 #if ENABLE_FEATURE_PIDFILE
527 pidfile_name = xasprintf(_PATH_VARRUN"ifplugd.%s.pid", G.iface);
528 pid_from_pidfile = read_pid(pidfile_name);
530 if (opts & FLAG_KILL) {
531 if (pid_from_pidfile > 0)
532 kill(pid_from_pidfile, SIGQUIT);
536 if (pid_from_pidfile > 0 && kill(pid_from_pidfile, 0) == 0)
537 bb_error_msg_and_die("daemon already running");
540 api_mode_found = strchr(api_modes, G.api_mode[0]);
542 bb_error_msg_and_die("unknown API mode '%s'", G.api_mode);
543 G.api_method_num = api_mode_found - api_modes;
545 if (!(opts & FLAG_NO_DAEMON))
546 bb_daemonize_or_rexec(DAEMON_CHDIR_ROOT, argv);
548 xmove_fd(xsocket(AF_INET, SOCK_DGRAM, 0), ioctl_fd);
549 if (opts & FLAG_MONITOR) {
550 struct sockaddr_nl addr;
551 int fd = xsocket(PF_NETLINK, SOCK_DGRAM, NETLINK_ROUTE);
553 memset(&addr, 0, sizeof(addr));
554 addr.nl_family = AF_NETLINK;
555 addr.nl_groups = RTMGRP_LINK;
556 addr.nl_pid = getpid();
558 xbind(fd, (struct sockaddr*)&addr, sizeof(addr));
559 xmove_fd(fd, netlink_fd);
562 write_pidfile(pidfile_name);
564 /* this can't be moved before socket creation */
565 if (!(opts & FLAG_NO_SYSLOG)) {
566 openlog(applet_name, 0, LOG_DAEMON);
567 logmode |= LOGMODE_SYSLOG;
574 | (1 << SIGHUP ) /* why we ignore it? */
575 /* | (1 << SIGCHLD) - run_script does not use it anymore */
578 bb_error_msg("started: %s", bb_banner);
580 if (opts & FLAG_MONITOR) {
581 struct ifreq ifrequest;
582 set_ifreq_to_ifname(&ifrequest);
583 G.iface_exists = (network_ioctl(SIOCGIFINDEX, &ifrequest, NULL) == 0);
587 maybe_up_new_iface();
589 iface_status = detect_link();
590 if (iface_status == IFSTATUS_ERR)
592 iface_status_str = strstatus(iface_status);
594 if (opts & FLAG_MONITOR) {
595 bb_error_msg("interface %s",
596 G.iface_exists ? "exists"
597 : "doesn't exist, waiting");
599 /* else we assume it always exists, but don't mislead user
600 * by potentially lying that it really exists */
602 if (G.iface_exists) {
603 bb_error_msg("link is %s", iface_status_str);
606 if ((!(opts & FLAG_NO_STARTUP)
607 && iface_status == IFSTATUS_UP
609 || (opts & FLAG_INITIAL_DOWN)
611 if (run_script(iface_status_str) != 0)
616 netlink_pollfd[0].fd = netlink_fd;
617 netlink_pollfd[0].events = POLLIN;
620 int iface_status_old;
621 int iface_exists_old;
623 switch (bb_got_signal) {
636 if (poll(netlink_pollfd,
637 (opts & FLAG_MONITOR) ? 1 : 0,
643 bb_perror_msg("poll");
647 iface_status_old = iface_status;
648 iface_exists_old = G.iface_exists;
650 if ((opts & FLAG_MONITOR)
651 && (netlink_pollfd[0].revents & POLLIN)
653 G.iface_exists = check_existence_through_netlink();
654 if (G.iface_exists < 0) /* error */
656 if (iface_exists_old != G.iface_exists) {
657 bb_error_msg("interface %sappeared",
658 G.iface_exists ? "" : "dis");
660 maybe_up_new_iface();
664 /* note: if !G.iface_exists, returns DOWN */
665 iface_status = detect_link();
666 if (iface_status == IFSTATUS_ERR) {
667 if (!(opts & FLAG_MONITOR))
669 iface_status = IFSTATUS_DOWN;
671 iface_status_str = strstatus(iface_status);
673 if (iface_status_old != iface_status) {
674 bb_error_msg("link is %s", iface_status_str);
677 /* link restored its old status before
678 * we run script. don't run the script: */
681 delay_time = monotonic_sec();
682 if (iface_status == IFSTATUS_UP)
683 delay_time += G.delay_up;
684 if (iface_status == IFSTATUS_DOWN)
685 delay_time += G.delay_down;
691 if (delay_time && (int)(monotonic_sec() - delay_time) >= 0) {
693 if (run_script(iface_status_str) != 0)
699 if (!(opts & FLAG_NO_SHUTDOWN)
700 && (iface_status == IFSTATUS_UP
701 || (iface_status == IFSTATUS_DOWN && delay_time)
704 setenv(IFPLUGD_ENV_PREVIOUS, strstatus(iface_status), 1);
705 setenv(IFPLUGD_ENV_CURRENT, strstatus(-1), 1);
706 run_script("down\0up"); /* reusing string */
710 remove_pidfile(pidfile_name);
711 bb_error_msg_and_die("exiting");