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)
456 iface_len = strlen(G.iface);
458 struct nlmsghdr *mhdr;
461 bytes = recv(netlink_fd, &replybuf, sizeof(replybuf), MSG_DONTWAIT);
464 return G.iface_exists;
468 bb_perror_msg("netlink: recv");
472 mhdr = (struct nlmsghdr*)replybuf;
474 if (!NLMSG_OK(mhdr, bytes)) {
475 bb_error_msg("netlink packet too small or truncated");
479 if (mhdr->nlmsg_type == RTM_NEWLINK || mhdr->nlmsg_type == RTM_DELLINK) {
483 if (mhdr->nlmsg_len < NLMSG_LENGTH(sizeof(struct ifinfomsg))) {
484 bb_error_msg("netlink packet too small or truncated");
488 attr = IFLA_RTA(NLMSG_DATA(mhdr));
489 attr_len = IFLA_PAYLOAD(mhdr);
491 while (RTA_OK(attr, attr_len)) {
492 if (attr->rta_type == IFLA_IFNAME) {
493 int len = RTA_PAYLOAD(attr);
497 && strncmp(G.iface, RTA_DATA(attr), len) == 0
499 G.iface_exists = (mhdr->nlmsg_type == RTM_NEWLINK);
502 attr = RTA_NEXT(attr, attr_len);
506 mhdr = NLMSG_NEXT(mhdr, bytes);
510 return G.iface_exists;
513 #if ENABLE_FEATURE_PIDFILE
514 static NOINLINE pid_t read_pid(const char *filename)
519 len = open_read_close(filename, buf, 127);
522 /* returns ULONG_MAX on error => -1 */
523 return bb_strtoul(buf, NULL, 10);
529 int ifplugd_main(int argc, char **argv) MAIN_EXTERNALLY_VISIBLE;
530 int ifplugd_main(int argc UNUSED_PARAM, char **argv)
534 const char *iface_status_str;
535 struct pollfd netlink_pollfd[1];
537 const char *api_mode_found;
538 #if ENABLE_FEATURE_PIDFILE
540 pid_t pid_from_pidfile;
545 opt_complementary = "t+:u+:d+";
546 opts = getopt32(argv, OPTION_STR,
547 &G.iface, &G.script_name, &G.poll_time, &G.delay_up,
548 &G.delay_down, &G.api_mode, &G.extra_arg);
551 applet_name = xasprintf("ifplugd(%s)", G.iface);
553 #if ENABLE_FEATURE_PIDFILE
554 pidfile_name = xasprintf(CONFIG_PID_FILE_PATH "/ifplugd.%s.pid", G.iface);
555 pid_from_pidfile = read_pid(pidfile_name);
557 if (opts & FLAG_KILL) {
558 if (pid_from_pidfile > 0)
559 kill(pid_from_pidfile, SIGQUIT);
563 if (pid_from_pidfile > 0 && kill(pid_from_pidfile, 0) == 0)
564 bb_error_msg_and_die("daemon already running");
567 api_mode_found = strchr(api_modes, G.api_mode[0]);
569 bb_error_msg_and_die("unknown API mode '%s'", G.api_mode);
570 G.api_method_num = api_mode_found - api_modes;
572 if (!(opts & FLAG_NO_DAEMON))
573 bb_daemonize_or_rexec(DAEMON_CHDIR_ROOT, argv);
575 xmove_fd(xsocket(AF_INET, SOCK_DGRAM, 0), ioctl_fd);
576 if (opts & FLAG_MONITOR) {
577 struct sockaddr_nl addr;
578 int fd = xsocket(PF_NETLINK, SOCK_DGRAM, NETLINK_ROUTE);
580 memset(&addr, 0, sizeof(addr));
581 addr.nl_family = AF_NETLINK;
582 addr.nl_groups = RTMGRP_LINK;
583 addr.nl_pid = getpid();
585 xbind(fd, (struct sockaddr*)&addr, sizeof(addr));
586 xmove_fd(fd, netlink_fd);
589 write_pidfile(pidfile_name);
591 /* this can't be moved before socket creation */
592 if (!(opts & FLAG_NO_SYSLOG)) {
593 openlog(applet_name, 0, LOG_DAEMON);
594 logmode |= LOGMODE_SYSLOG;
601 | (1 << SIGHUP ) /* why we ignore it? */
602 /* | (1 << SIGCHLD) - run_script does not use it anymore */
605 bb_error_msg("started: %s", bb_banner);
607 if (opts & FLAG_MONITOR) {
608 struct ifreq ifrequest;
609 set_ifreq_to_ifname(&ifrequest);
610 G.iface_exists = (network_ioctl(SIOCGIFINDEX, &ifrequest, NULL) == 0);
614 maybe_up_new_iface();
616 iface_status = detect_link();
617 if (iface_status == IFSTATUS_ERR)
619 iface_status_str = strstatus(iface_status);
621 if (opts & FLAG_MONITOR) {
622 bb_error_msg("interface %s",
623 G.iface_exists ? "exists"
624 : "doesn't exist, waiting");
626 /* else we assume it always exists, but don't mislead user
627 * by potentially lying that it really exists */
629 if (G.iface_exists) {
630 bb_error_msg("link is %s", iface_status_str);
633 if ((!(opts & FLAG_NO_STARTUP)
634 && iface_status == IFSTATUS_UP
636 || (opts & FLAG_INITIAL_DOWN)
638 if (run_script(iface_status_str) != 0)
643 netlink_pollfd[0].fd = netlink_fd;
644 netlink_pollfd[0].events = POLLIN;
647 int iface_status_old;
648 int iface_exists_old;
650 switch (bb_got_signal) {
663 if (poll(netlink_pollfd,
664 (opts & FLAG_MONITOR) ? 1 : 0,
670 bb_perror_msg("poll");
674 iface_status_old = iface_status;
675 iface_exists_old = G.iface_exists;
677 if ((opts & FLAG_MONITOR)
678 && (netlink_pollfd[0].revents & POLLIN)
680 G.iface_exists = check_existence_through_netlink();
681 if (G.iface_exists < 0) /* error */
683 if (iface_exists_old != G.iface_exists) {
684 bb_error_msg("interface %sappeared",
685 G.iface_exists ? "" : "dis");
687 maybe_up_new_iface();
691 /* note: if !G.iface_exists, returns DOWN */
692 iface_status = detect_link();
693 if (iface_status == IFSTATUS_ERR) {
694 if (!(opts & FLAG_MONITOR))
696 iface_status = IFSTATUS_DOWN;
698 iface_status_str = strstatus(iface_status);
700 if (iface_status_old != iface_status) {
701 bb_error_msg("link is %s", iface_status_str);
704 /* link restored its old status before
705 * we run script. don't run the script: */
708 delay_time = monotonic_sec();
709 if (iface_status == IFSTATUS_UP)
710 delay_time += G.delay_up;
711 if (iface_status == IFSTATUS_DOWN)
712 delay_time += G.delay_down;
718 if (delay_time && (int)(monotonic_sec() - delay_time) >= 0) {
720 if (run_script(iface_status_str) != 0)
726 if (!(opts & FLAG_NO_SHUTDOWN)
727 && (iface_status == IFSTATUS_UP
728 || (iface_status == IFSTATUS_DOWN && delay_time)
731 setenv(IFPLUGD_ENV_PREVIOUS, strstatus(iface_status), 1);
732 setenv(IFPLUGD_ENV_CURRENT, strstatus(-1), 1);
733 run_script("down\0up"); /* reusing string */
737 remove_pidfile(pidfile_name);
738 bb_error_msg_and_die("exiting");