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)
135 struct mii_ioctl_data *mii = (void *)&ifreq.ifr_data;
137 set_ifreq_to_ifname(&ifreq);
139 if (network_ioctl(SIOCGMIIPHY, &ifreq, "SIOCGMIIPHY") < 0) {
145 if (network_ioctl(SIOCGMIIREG, &ifreq, "SIOCGMIIREG") < 0) {
149 return (mii->val_out & 0x0004) ? IFSTATUS_UP : IFSTATUS_DOWN;
152 static smallint detect_link_priv(void)
155 struct mii_ioctl_data *mii = (void *)&ifreq.ifr_data;
157 set_ifreq_to_ifname(&ifreq);
159 if (network_ioctl(SIOCDEVPRIVATE, &ifreq, "SIOCDEVPRIVATE") < 0) {
165 if (network_ioctl(SIOCDEVPRIVATE+1, &ifreq, "SIOCDEVPRIVATE+1") < 0) {
169 return (mii->val_out & 0x0004) ? IFSTATUS_UP : IFSTATUS_DOWN;
172 static smallint detect_link_ethtool(void)
175 struct ethtool_value edata;
177 set_ifreq_to_ifname(&ifreq);
179 edata.cmd = ETHTOOL_GLINK;
180 ifreq.ifr_data = (void*) &edata;
182 if (network_ioctl(SIOCETHTOOL, &ifreq, "ETHTOOL_GLINK") < 0) {
186 return edata.data ? IFSTATUS_UP : IFSTATUS_DOWN;
189 static smallint detect_link_iff(void)
193 set_ifreq_to_ifname(&ifreq);
195 if (network_ioctl(SIOCGIFFLAGS, &ifreq, "SIOCGIFFLAGS") < 0) {
199 /* If IFF_UP is not set (interface is down), IFF_RUNNING is never set
200 * regardless of link status. Simply continue to report last status -
201 * no point in reporting spurious link downs if interface is disabled
202 * by admin. When/if it will be brought up,
203 * we'll report real link status.
205 if (!(ifreq.ifr_flags & IFF_UP) && G.iface_last_status != IFSTATUS_ERR)
206 return G.iface_last_status;
208 return (ifreq.ifr_flags & IFF_RUNNING) ? IFSTATUS_UP : IFSTATUS_DOWN;
211 static smallint detect_link_wlan(void)
214 struct iwreq iwrequest;
215 uint8_t mac[ETH_ALEN];
217 memset(&iwrequest, 0, sizeof(iwrequest));
218 strncpy_IFNAMSIZ(iwrequest.ifr_ifrn.ifrn_name, G.iface);
220 if (network_ioctl(SIOCGIWAP, &iwrequest, "SIOCGIWAP") < 0) {
224 memcpy(mac, &iwrequest.u.ap_addr.sa_data, ETH_ALEN);
226 if (mac[0] == 0xFF || mac[0] == 0x44 || mac[0] == 0x00) {
227 for (i = 1; i < ETH_ALEN; ++i) {
228 if (mac[i] != mac[0])
231 return IFSTATUS_DOWN;
246 static const char api_modes[] ALIGN1 = "empwia";
248 static const struct {
250 smallint (*func)(void);
252 { "SIOCETHTOOL" , &detect_link_ethtool },
253 { "SIOCGMIIPHY" , &detect_link_mii },
254 { "SIOCDEVPRIVATE" , &detect_link_priv },
255 { "wireless extension", &detect_link_wlan },
256 { "IFF_RUNNING" , &detect_link_iff },
261 static const char *strstatus(int status)
263 if (status == IFSTATUS_ERR)
265 return "down\0up" + (status * 5);
268 static int run_script(const char *action)
270 char *env_PREVIOUS, *env_CURRENT;
274 bb_error_msg("executing '%s %s %s'", G.script_name, G.iface, action);
276 argv[0] = (char*) G.script_name;
277 argv[1] = (char*) G.iface;
278 argv[2] = (char*) action;
279 argv[3] = (char*) G.extra_arg;
282 env_PREVIOUS = xasprintf("%s=%s", IFPLUGD_ENV_PREVIOUS, strstatus(G.iface_prev_status));
283 putenv(env_PREVIOUS);
284 env_CURRENT = xasprintf("%s=%s", IFPLUGD_ENV_CURRENT, strstatus(G.iface_last_status));
287 /* r < 0 - can't exec, 0 <= r < 0x180 - exited, >=0x180 - killed by sig (r-0x180) */
288 r = spawn_and_wait(argv);
290 unsetenv(IFPLUGD_ENV_PREVIOUS);
291 unsetenv(IFPLUGD_ENV_CURRENT);
295 bb_error_msg("exit code: %d", r & 0xff);
296 return (option_mask32 & FLAG_IGNORE_RETVAL) ? 0 : r;
299 static void up_iface(void)
301 struct ifreq ifrequest;
306 set_ifreq_to_ifname(&ifrequest);
307 if (network_ioctl(SIOCGIFFLAGS, &ifrequest, "getting interface flags") < 0) {
312 if (!(ifrequest.ifr_flags & IFF_UP)) {
313 ifrequest.ifr_flags |= IFF_UP;
314 /* Let user know we mess up with interface */
315 bb_error_msg("upping interface");
316 if (network_ioctl(SIOCSIFFLAGS, &ifrequest, "setting interface flags") < 0)
320 #if 0 /* why do we mess with IP addr? It's not our business */
321 if (network_ioctl(SIOCGIFADDR, &ifrequest, "can't get interface address") < 0) {
322 } else if (ifrequest.ifr_addr.sa_family != AF_INET) {
323 bb_perror_msg("the interface is not IP-based");
325 ((struct sockaddr_in*)(&ifrequest.ifr_addr))->sin_addr.s_addr = INADDR_ANY;
326 network_ioctl(SIOCSIFADDR, &ifrequest, "can't set interface address");
328 network_ioctl(SIOCGIFFLAGS, &ifrequest, "can't get interface flags");
332 static void maybe_up_new_iface(void)
334 if (!(option_mask32 & FLAG_NO_AUTO))
338 struct ifreq ifrequest;
339 struct ethtool_drvinfo driver_info;
341 set_ifreq_to_ifname(&ifrequest);
342 driver_info.cmd = ETHTOOL_GDRVINFO;
343 ifrequest.ifr_data = &driver_info;
344 if (network_ioctl(SIOCETHTOOL, &ifrequest, NULL) == 0) {
345 char buf[sizeof("/xx:xx:xx:xx:xx:xx")];
349 set_ifreq_to_ifname(&ifrequest);
350 if (network_ioctl(SIOCGIFHWADDR, &ifrequest, NULL) == 0) {
351 sprintf(buf, "/%02X:%02X:%02X:%02X:%02X:%02X",
352 (uint8_t)(ifrequest.ifr_hwaddr.sa_data[0]),
353 (uint8_t)(ifrequest.ifr_hwaddr.sa_data[1]),
354 (uint8_t)(ifrequest.ifr_hwaddr.sa_data[2]),
355 (uint8_t)(ifrequest.ifr_hwaddr.sa_data[3]),
356 (uint8_t)(ifrequest.ifr_hwaddr.sa_data[4]),
357 (uint8_t)(ifrequest.ifr_hwaddr.sa_data[5]));
360 bb_error_msg("using interface %s%s with driver<%s> (version: %s)",
361 G.iface, buf, driver_info.driver, driver_info.version);
364 if (G.api_mode[0] == 'a')
365 G.api_method_num = API_AUTO;
368 static smallint detect_link(void)
373 return (option_mask32 & FLAG_MONITOR) ? IFSTATUS_DOWN : IFSTATUS_ERR;
375 /* Some drivers can't detect link status when the interface is down.
376 * I imagine detect_link_iff() is the most vulnerable.
377 * That's why -a "noauto" in an option, not a hardwired behavior.
379 if (!(option_mask32 & FLAG_NO_AUTO))
382 if (G.api_method_num == API_AUTO) {
386 sv_logmode = logmode;
387 for (i = 0; i < ARRAY_SIZE(method_table); i++) {
388 logmode = LOGMODE_NONE;
389 status = method_table[i].func();
390 logmode = sv_logmode;
391 if (status != IFSTATUS_ERR) {
392 G.api_method_num = i;
393 bb_error_msg("using %s detection mode", method_table[i].name);
398 status = method_table[G.api_method_num].func();
401 if (status == IFSTATUS_ERR) {
402 if (option_mask32 & FLAG_IGNORE_FAIL)
403 status = IFSTATUS_DOWN;
404 else if (option_mask32 & FLAG_IGNORE_FAIL_POSITIVE)
405 status = IFSTATUS_UP;
406 else if (G.api_mode[0] == 'a')
407 bb_error_msg("can't detect link status");
410 if (status != G.iface_last_status) {
411 G.iface_prev_status = G.iface_last_status;
412 G.iface_last_status = status;
418 static NOINLINE int check_existence_through_netlink(void)
423 iface_len = strlen(G.iface);
425 struct nlmsghdr *mhdr;
428 bytes = recv(netlink_fd, &replybuf, sizeof(replybuf), MSG_DONTWAIT);
431 return G.iface_exists;
435 bb_perror_msg("netlink: recv");
439 mhdr = (struct nlmsghdr*)replybuf;
441 if (!NLMSG_OK(mhdr, bytes)) {
442 bb_error_msg("netlink packet too small or truncated");
446 if (mhdr->nlmsg_type == RTM_NEWLINK || mhdr->nlmsg_type == RTM_DELLINK) {
450 if (mhdr->nlmsg_len < NLMSG_LENGTH(sizeof(struct ifinfomsg))) {
451 bb_error_msg("netlink packet too small or truncated");
455 attr = IFLA_RTA(NLMSG_DATA(mhdr));
456 attr_len = IFLA_PAYLOAD(mhdr);
458 while (RTA_OK(attr, attr_len)) {
459 if (attr->rta_type == IFLA_IFNAME) {
460 int len = RTA_PAYLOAD(attr);
464 && strncmp(G.iface, RTA_DATA(attr), len) == 0
466 G.iface_exists = (mhdr->nlmsg_type == RTM_NEWLINK);
469 attr = RTA_NEXT(attr, attr_len);
473 mhdr = NLMSG_NEXT(mhdr, bytes);
477 return G.iface_exists;
480 #if ENABLE_FEATURE_PIDFILE
481 static NOINLINE pid_t read_pid(const char *filename)
486 len = open_read_close(filename, buf, 127);
489 /* returns ULONG_MAX on error => -1 */
490 return bb_strtoul(buf, NULL, 10);
496 int ifplugd_main(int argc, char **argv) MAIN_EXTERNALLY_VISIBLE;
497 int ifplugd_main(int argc UNUSED_PARAM, char **argv)
501 const char *iface_status_str;
502 struct pollfd netlink_pollfd[1];
504 const char *api_mode_found;
505 #if ENABLE_FEATURE_PIDFILE
507 pid_t pid_from_pidfile;
512 opt_complementary = "t+:u+:d+";
513 opts = getopt32(argv, OPTION_STR,
514 &G.iface, &G.script_name, &G.poll_time, &G.delay_up,
515 &G.delay_down, &G.api_mode, &G.extra_arg);
518 applet_name = xasprintf("ifplugd(%s)", G.iface);
520 #if ENABLE_FEATURE_PIDFILE
521 pidfile_name = xasprintf(_PATH_VARRUN"ifplugd.%s.pid", G.iface);
522 pid_from_pidfile = read_pid(pidfile_name);
524 if (opts & FLAG_KILL) {
525 if (pid_from_pidfile > 0)
526 kill(pid_from_pidfile, SIGQUIT);
530 if (pid_from_pidfile > 0 && kill(pid_from_pidfile, 0) == 0)
531 bb_error_msg_and_die("daemon already running");
534 api_mode_found = strchr(api_modes, G.api_mode[0]);
536 bb_error_msg_and_die("unknown API mode '%s'", G.api_mode);
537 G.api_method_num = api_mode_found - api_modes;
539 if (!(opts & FLAG_NO_DAEMON))
540 bb_daemonize_or_rexec(DAEMON_CHDIR_ROOT, argv);
542 xmove_fd(xsocket(AF_INET, SOCK_DGRAM, 0), ioctl_fd);
543 if (opts & FLAG_MONITOR) {
544 struct sockaddr_nl addr;
545 int fd = xsocket(PF_NETLINK, SOCK_DGRAM, NETLINK_ROUTE);
547 memset(&addr, 0, sizeof(addr));
548 addr.nl_family = AF_NETLINK;
549 addr.nl_groups = RTMGRP_LINK;
550 addr.nl_pid = getpid();
552 xbind(fd, (struct sockaddr*)&addr, sizeof(addr));
553 xmove_fd(fd, netlink_fd);
556 write_pidfile(pidfile_name);
558 /* this can't be moved before socket creation */
559 if (!(opts & FLAG_NO_SYSLOG)) {
560 openlog(applet_name, 0, LOG_DAEMON);
561 logmode |= LOGMODE_SYSLOG;
568 | (1 << SIGHUP ) /* why we ignore it? */
569 /* | (1 << SIGCHLD) - run_script does not use it anymore */
572 bb_error_msg("started: %s", bb_banner);
574 if (opts & FLAG_MONITOR) {
575 struct ifreq ifrequest;
576 set_ifreq_to_ifname(&ifrequest);
577 G.iface_exists = (network_ioctl(SIOCGIFINDEX, &ifrequest, NULL) == 0);
581 maybe_up_new_iface();
583 iface_status = detect_link();
584 if (iface_status == IFSTATUS_ERR)
586 iface_status_str = strstatus(iface_status);
588 if (opts & FLAG_MONITOR) {
589 bb_error_msg("interface %s",
590 G.iface_exists ? "exists"
591 : "doesn't exist, waiting");
593 /* else we assume it always exists, but don't mislead user
594 * by potentially lying that it really exists */
596 if (G.iface_exists) {
597 bb_error_msg("link is %s", iface_status_str);
600 if ((!(opts & FLAG_NO_STARTUP)
601 && iface_status == IFSTATUS_UP
603 || (opts & FLAG_INITIAL_DOWN)
605 if (run_script(iface_status_str) != 0)
610 netlink_pollfd[0].fd = netlink_fd;
611 netlink_pollfd[0].events = POLLIN;
614 int iface_status_old;
615 int iface_exists_old;
617 switch (bb_got_signal) {
630 if (poll(netlink_pollfd,
631 (opts & FLAG_MONITOR) ? 1 : 0,
637 bb_perror_msg("poll");
641 iface_status_old = iface_status;
642 iface_exists_old = G.iface_exists;
644 if ((opts & FLAG_MONITOR)
645 && (netlink_pollfd[0].revents & POLLIN)
647 G.iface_exists = check_existence_through_netlink();
648 if (G.iface_exists < 0) /* error */
650 if (iface_exists_old != G.iface_exists) {
651 bb_error_msg("interface %sappeared",
652 G.iface_exists ? "" : "dis");
654 maybe_up_new_iface();
658 /* note: if !G.iface_exists, returns DOWN */
659 iface_status = detect_link();
660 if (iface_status == IFSTATUS_ERR) {
661 if (!(opts & FLAG_MONITOR))
663 iface_status = IFSTATUS_DOWN;
665 iface_status_str = strstatus(iface_status);
667 if (iface_status_old != iface_status) {
668 bb_error_msg("link is %s", iface_status_str);
671 /* link restored its old status before
672 * we run script. don't run the script: */
675 delay_time = monotonic_sec();
676 if (iface_status == IFSTATUS_UP)
677 delay_time += G.delay_up;
678 if (iface_status == IFSTATUS_DOWN)
679 delay_time += G.delay_down;
685 if (delay_time && (int)(monotonic_sec() - delay_time) >= 0) {
687 if (run_script(iface_status_str) != 0)
693 if (!(opts & FLAG_NO_SHUTDOWN)
694 && (iface_status == IFSTATUS_UP
695 || (iface_status == IFSTATUS_DOWN && delay_time)
698 setenv(IFPLUGD_ENV_PREVIOUS, strstatus(iface_status), 1);
699 setenv(IFPLUGD_ENV_CURRENT, strstatus(-1), 1);
700 run_script("down\0up"); /* reusing string */
704 remove_pidfile(pidfile_name);
705 bb_error_msg_and_die("exiting");