1 /* vi: set sw=4 ts=4: */
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 TODO: describe compat status here.
27 One questionable point of the design is netlink usage:
29 We have 1 second timeout by default to poll the link status,
30 it is short enough so that there are no real benefits in
31 using netlink to get "instantaneous" interface creation/deletion
32 notifications. We can check for interface existence by just
33 doing some fast ioctl using its name.
35 Netlink code then can be just dropped (1k or more?)
39 #define IFPLUGD_ENV_PREVIOUS "IFPLUGD_PREVIOUS"
40 #define IFPLUGD_ENV_CURRENT "IFPLUGD_CURRENT"
43 FLAG_NO_AUTO = 1 << 0, // -a, Do not enable interface automatically
44 FLAG_NO_DAEMON = 1 << 1, // -n, Do not daemonize
45 FLAG_NO_SYSLOG = 1 << 2, // -s, Do not use syslog, use stderr instead
46 FLAG_IGNORE_FAIL = 1 << 3, // -f, Ignore detection failure, retry instead (failure is treated as DOWN)
47 FLAG_IGNORE_FAIL_POSITIVE = 1 << 4, // -F, Ignore detection failure, retry instead (failure is treated as UP)
48 FLAG_IFACE = 1 << 5, // -i, Specify ethernet interface
49 FLAG_RUN = 1 << 6, // -r, Specify program to execute
50 FLAG_IGNORE_RETVAL = 1 << 7, // -I, Don't exit on nonzero return value of program executed
51 FLAG_POLL_TIME = 1 << 8, // -t, Specify poll time in seconds
52 FLAG_DELAY_UP = 1 << 9, // -u, Specify delay for configuring interface
53 FLAG_DELAY_DOWN = 1 << 10, // -d, Specify delay for deconfiguring interface
54 FLAG_API_MODE = 1 << 11, // -m, Force API mode (mii, priv, ethtool, wlan, auto)
55 FLAG_NO_STARTUP = 1 << 12, // -p, Don't run script on daemon startup
56 FLAG_NO_SHUTDOWN = 1 << 13, // -q, Don't run script on daemon quit
57 FLAG_INITIAL_DOWN = 1 << 14, // -l, Run "down" script on startup if no cable is detected
58 FLAG_EXTRA_ARG = 1 << 15, // -x, Specify an extra argument for action script
59 FLAG_MONITOR = 1 << 16, // -M, Use interface monitoring
60 #if ENABLE_FEATURE_PIDFILE
61 FLAG_KILL = 1 << 17, // -k, Kill a running daemon
64 #if ENABLE_FEATURE_PIDFILE
65 # define OPTION_STR "+ansfFi:r:It:u:d:m:pqlx:Mk"
67 # define OPTION_STR "+ansfFi:r:It:u:d:m:pqlx:M"
79 enum { // interface status
85 enum { // constant fds
91 smallint iface_last_status;
92 smallint iface_exists;
94 /* Used in getopt32, must have sizeof == sizeof(int) */
100 const char *api_mode;
101 const char *script_name;
102 const char *extra_arg;
104 smallint (*detect_link_func)(void);
105 smallint (*cached_detect_link_func)(void);
107 #define G (*ptr_to_globals)
108 #define INIT_G() do { \
109 SET_PTR_TO_GLOBALS(xzalloc(sizeof(G))); \
110 G.iface_last_status = -1; \
111 G.iface_exists = 1; \
116 G.script_name = "/etc/ifplugd/ifplugd.action"; \
120 static int run_script(const char *action)
125 bb_error_msg("executing '%s %s %s'", G.script_name, G.iface, action);
129 argv[0] = (char*) G.script_name;
130 argv[1] = (char*) G.iface;
131 argv[2] = (char*) action;
132 argv[3] = (char*) G.extra_arg;
135 /* r < 0 - can't exec, 0 <= r < 0x180 - exited, >=0x180 - killed by sig (r-0x180) */
136 r = spawn_and_wait(argv);
138 bb_error_msg("exit code: %d", r & 0xff);
139 return (option_mask32 & FLAG_IGNORE_RETVAL) ? 0 : r;
143 struct fd_pair pipe_pair;
147 xpiped_pair(pipe_pair);
151 bb_perror_msg("fork");
157 xmove_fd(pipe_pair.wr, 1);
159 if (pipe_pair.rd > 2)
162 // umask(0022); // Set up a sane umask
164 execlp(G.script_name, G.script_name, G.iface, action, G.extra_arg, NULL);
172 if (bb_got_signal && bb_got_signal != SIGCHLD) {
173 bb_error_msg("killing child");
179 r = read(pipe_pair.rd, &buf[i], 1);
181 if (buf[i] == '\n' || i == sizeof(buf)-2 || r != 1) {
182 if (r == 1 && buf[i] != '\n')
188 bb_error_msg("client: %s", buf);
203 if (!WIFEXITED(r) || WEXITSTATUS(r) != 0) {
204 bb_error_msg("program execution failed, return value is %i",
206 return option_mask32 & FLAG_IGNORE_RETVAL ? 0 : WEXITSTATUS(r);
208 bb_error_msg("program executed successfully");
213 static int network_ioctl(int request, void* data, const char *errmsg)
215 int r = ioctl(ioctl_fd, request, data);
217 bb_perror_msg(errmsg);
221 static void set_ifreq_to_ifname(struct ifreq *ifreq)
223 memset(ifreq, 0, sizeof(struct ifreq));
224 strncpy_IFNAMSIZ(ifreq->ifr_name, G.iface);
227 static const char *strstatus(int status)
229 if (status == IFSTATUS_ERR)
231 return "down\0up" + (status * 5);
234 static void up_iface(void)
236 struct ifreq ifrequest;
241 set_ifreq_to_ifname(&ifrequest);
242 if (network_ioctl(SIOCGIFFLAGS, &ifrequest, "can't get interface flags") < 0) {
247 if (!(ifrequest.ifr_flags & IFF_UP)) {
248 ifrequest.ifr_flags |= IFF_UP;
249 /* Let user know we mess up with interface */
250 bb_error_msg("upping interface");
251 if (network_ioctl(SIOCSIFFLAGS, &ifrequest, "can't set interface flags") < 0)
255 #if 0 /* why do we mess with IP addr? It's not our business */
256 if (network_ioctl(SIOCGIFADDR, &ifrequest, "can't get interface address") < 0) {
257 } else if (ifrequest.ifr_addr.sa_family != AF_INET) {
258 bb_perror_msg("the interface is not IP-based");
260 ((struct sockaddr_in*)(&ifrequest.ifr_addr))->sin_addr.s_addr = INADDR_ANY;
261 network_ioctl(SIOCSIFADDR, &ifrequest, "can't set interface address");
263 network_ioctl(SIOCGIFFLAGS, &ifrequest, "can't get interface flags");
267 static void maybe_up_new_iface(void)
269 if (!(option_mask32 & FLAG_NO_AUTO))
273 struct ifreq ifrequest;
274 struct ethtool_drvinfo driver_info;
276 set_ifreq_to_ifname(&ifrequest);
277 driver_info.cmd = ETHTOOL_GDRVINFO;
278 ifrequest.ifr_data = &driver_info;
279 if (network_ioctl(SIOCETHTOOL, &ifrequest, NULL) == 0) {
280 char buf[sizeof("/xx:xx:xx:xx:xx:xx")];
284 set_ifreq_to_ifname(&ifrequest);
285 if (network_ioctl(SIOCGIFHWADDR, &ifrequest, NULL) == 0) {
286 sprintf(buf, "/%02X:%02X:%02X:%02X:%02X:%02X",
287 (uint8_t)(ifrequest.ifr_hwaddr.sa_data[0]),
288 (uint8_t)(ifrequest.ifr_hwaddr.sa_data[1]),
289 (uint8_t)(ifrequest.ifr_hwaddr.sa_data[2]),
290 (uint8_t)(ifrequest.ifr_hwaddr.sa_data[3]),
291 (uint8_t)(ifrequest.ifr_hwaddr.sa_data[4]),
292 (uint8_t)(ifrequest.ifr_hwaddr.sa_data[5]));
295 bb_error_msg("using interface %s%s with driver<%s> (version: %s)",
296 G.iface, buf, driver_info.driver, driver_info.version);
300 G.cached_detect_link_func = NULL;
303 static smallint detect_link_mii(void)
306 struct mii_ioctl_data *mii = (void *)&ifreq.ifr_data;
308 set_ifreq_to_ifname(&ifreq);
310 if (network_ioctl(SIOCGMIIPHY, &ifreq, "SIOCGMIIPHY failed") < 0) {
316 if (network_ioctl(SIOCGMIIREG, &ifreq, "SIOCGMIIREG failed") < 0) {
320 return (mii->val_out & 0x0004) ? IFSTATUS_UP : IFSTATUS_DOWN;
323 static smallint detect_link_priv(void)
326 struct mii_ioctl_data *mii = (void *)&ifreq.ifr_data;
328 set_ifreq_to_ifname(&ifreq);
330 if (network_ioctl(SIOCDEVPRIVATE, &ifreq, "SIOCDEVPRIVATE failed") < 0) {
336 if (network_ioctl(SIOCDEVPRIVATE+1, &ifreq, "SIOCDEVPRIVATE+1 failed") < 0) {
340 return (mii->val_out & 0x0004) ? IFSTATUS_UP : IFSTATUS_DOWN;
343 static smallint detect_link_ethtool(void)
346 struct ethtool_value edata;
348 set_ifreq_to_ifname(&ifreq);
350 edata.cmd = ETHTOOL_GLINK;
351 ifreq.ifr_data = (void*) &edata;
353 if (network_ioctl(SIOCETHTOOL, &ifreq, "ETHTOOL_GLINK failed") < 0) {
357 return edata.data ? IFSTATUS_UP : IFSTATUS_DOWN;
360 static smallint detect_link_iff(void)
364 set_ifreq_to_ifname(&ifreq);
366 if (network_ioctl(SIOCGIFFLAGS, &ifreq, "SIOCGIFFLAGS failed") < 0) {
370 /* If IFF_UP is not set (interface is down), IFF_RUNNING is never set
371 * regardless of link status. Simply continue to report last status -
372 * no point in reporting spurious link downs if interface is disabled
373 * by admin. When/if it will be brought up,
374 * we'll report real link status.
376 if (!(ifreq.ifr_flags & IFF_UP) && G.iface_last_status != IFSTATUS_ERR)
377 return G.iface_last_status;
379 return (ifreq.ifr_flags & IFF_RUNNING) ? IFSTATUS_UP : IFSTATUS_DOWN;
382 static smallint detect_link_wlan(void)
384 struct iwreq iwrequest;
385 uint8_t mac[ETH_ALEN];
387 memset(&iwrequest, 0, sizeof(struct iwreq));
388 strncpy_IFNAMSIZ(iwrequest.ifr_ifrn.ifrn_name, G.iface);
390 if (network_ioctl(SIOCGIWAP, &iwrequest, "SIOCGIWAP failed") < 0) {
394 memcpy(mac, &(iwrequest.u.ap_addr.sa_data), ETH_ALEN);
396 if (mac[0] == 0xFF || mac[0] == 0x44 || mac[0] == 0x00) {
397 for (int i = 1; i < ETH_ALEN; ++i) {
398 if (mac[i] != mac[0])
401 return IFSTATUS_DOWN;
407 static smallint detect_link_auto(void)
410 smallint iface_status;
413 if (G.cached_detect_link_func) {
414 iface_status = G.cached_detect_link_func();
415 if (iface_status != IFSTATUS_ERR)
419 sv_logmode = logmode;
420 logmode = LOGMODE_NONE;
422 iface_status = detect_link_ethtool();
423 if (iface_status != IFSTATUS_ERR) {
424 G.cached_detect_link_func = detect_link_ethtool;
425 method = "SIOCETHTOOL";
427 logmode = sv_logmode;
428 bb_error_msg("using %s detection mode", method);
432 iface_status = detect_link_mii();
433 if (iface_status != IFSTATUS_ERR) {
434 G.cached_detect_link_func = detect_link_mii;
435 method = "SIOCGMIIPHY";
439 iface_status = detect_link_priv();
440 if (iface_status != IFSTATUS_ERR) {
441 G.cached_detect_link_func = detect_link_priv;
442 method = "SIOCDEVPRIVATE";
446 iface_status = detect_link_wlan();
447 if (iface_status != IFSTATUS_ERR) {
448 G.cached_detect_link_func = detect_link_wlan;
449 method = "wireless extension";
453 iface_status = detect_link_iff();
454 if (iface_status != IFSTATUS_ERR) {
455 G.cached_detect_link_func = detect_link_iff;
456 method = "IFF_RUNNING";
460 logmode = sv_logmode;
461 return iface_status; /* IFSTATUS_ERR */
464 static smallint detect_link(void)
469 return (option_mask32 & FLAG_MONITOR) ? IFSTATUS_DOWN : IFSTATUS_ERR;
471 /* Some drivers can't detect link status when the interface is down.
472 * I imagine detect_link_iff() is the most vulnerable.
473 * That's why -a "noauto" in an option, not a hardwired behavior.
475 if (!(option_mask32 & FLAG_NO_AUTO))
478 status = G.detect_link_func();
479 if (status == IFSTATUS_ERR) {
480 if (option_mask32 & FLAG_IGNORE_FAIL)
481 status = IFSTATUS_DOWN;
482 if (option_mask32 & FLAG_IGNORE_FAIL_POSITIVE)
483 status = IFSTATUS_UP;
486 if (status == IFSTATUS_ERR
487 && G.detect_link_func == detect_link_auto
489 bb_error_msg("failed to detect link status");
492 if (status != G.iface_last_status) {
493 //TODO: is it safe to repeatedly do this?
494 setenv(IFPLUGD_ENV_PREVIOUS, strstatus(G.iface_last_status), 1);
495 setenv(IFPLUGD_ENV_CURRENT, strstatus(status), 1);
496 G.iface_last_status = status;
502 static NOINLINE int check_existence_through_netlink(void)
507 struct nlmsghdr *mhdr;
510 bytes = recv(netlink_fd, &replybuf, sizeof(replybuf), MSG_DONTWAIT);
513 return G.iface_exists;
517 bb_perror_msg("netlink: recv");
521 mhdr = (struct nlmsghdr*)replybuf;
523 if (!NLMSG_OK(mhdr, bytes)
524 || bytes < sizeof(struct nlmsghdr)
525 || bytes < mhdr->nlmsg_len
527 bb_error_msg("netlink packet too small or truncated");
531 if (mhdr->nlmsg_type == RTM_NEWLINK || mhdr->nlmsg_type == RTM_DELLINK) {
533 struct ifinfomsg *imsg;
536 imsg = NLMSG_DATA(mhdr);
538 if (mhdr->nlmsg_len < NLMSG_LENGTH(sizeof(struct ifinfomsg))) {
539 bb_error_msg("netlink packet too small or truncated");
543 attr = (struct rtattr*)((char*)imsg + NLMSG_ALIGN(sizeof(struct ifinfomsg)));
544 attr_len = NLMSG_PAYLOAD(mhdr, sizeof(struct ifinfomsg));
546 while (RTA_OK(attr, attr_len)) {
547 if (attr->rta_type == IFLA_IFNAME) {
548 char ifname[IFNAMSIZ + 1];
549 int len = RTA_PAYLOAD(attr);
553 memcpy(ifname, RTA_DATA(attr), len);
554 if (strcmp(G.iface, ifname) == 0) {
555 G.iface_exists = (mhdr->nlmsg_type == RTM_NEWLINK);
558 attr = RTA_NEXT(attr, attr_len);
562 mhdr = NLMSG_NEXT(mhdr, bytes);
566 return G.iface_exists;
569 static NOINLINE int netlink_open(void)
572 struct sockaddr_nl addr;
574 fd = xsocket(PF_NETLINK, SOCK_DGRAM, NETLINK_ROUTE);
576 memset(&addr, 0, sizeof(addr));
577 addr.nl_family = AF_NETLINK;
578 addr.nl_groups = RTMGRP_LINK;
579 addr.nl_pid = getpid();
581 xbind(fd, (struct sockaddr*)&addr, sizeof(addr));
586 #if ENABLE_FEATURE_PIDFILE
587 static NOINLINE pid_t read_pid(const char *filename)
592 len = open_read_close(filename, buf, 127);
595 /* returns ULONG_MAX on error => -1 */
596 return bb_strtoul(buf, NULL, 10);
602 int ifplugd_main(int argc, char **argv) MAIN_EXTERNALLY_VISIBLE;
603 int ifplugd_main(int argc UNUSED_PARAM, char **argv)
607 const char *iface_status_str;
608 struct pollfd netlink_pollfd[1];
610 #if ENABLE_FEATURE_PIDFILE
612 pid_t pid_from_pidfile;
617 opt_complementary = "t+:u+:d+";
618 opts = getopt32(argv, OPTION_STR,
619 &G.iface, &G.script_name, &G.poll_time, &G.delay_up,
620 &G.delay_down, &G.api_mode, &G.extra_arg);
623 applet_name = xasprintf("ifplugd(%s)", G.iface);
625 #if ENABLE_FEATURE_PIDFILE
626 pidfile_name = xasprintf(_PATH_VARRUN"ifplugd.%s.pid", G.iface);
627 pid_from_pidfile = read_pid(pidfile_name);
629 if (opts & FLAG_KILL) {
630 if (pid_from_pidfile > 0)
631 kill(pid_from_pidfile, SIGQUIT);
635 if (pid_from_pidfile > 0 && kill(pid_from_pidfile, 0) == 0)
636 bb_error_msg_and_die("daemon already running");
639 switch (G.api_mode[0]) {
641 G.detect_link_func = detect_link_auto;
644 G.detect_link_func = detect_link_ethtool;
647 G.detect_link_func = detect_link_mii;
650 G.detect_link_func = detect_link_priv;
653 G.detect_link_func = detect_link_wlan;
656 G.detect_link_func = detect_link_iff;
659 bb_error_msg_and_die("unknown API mode '%s'", G.api_mode);
662 if (!(opts & FLAG_NO_DAEMON))
663 bb_daemonize_or_rexec(DAEMON_CHDIR_ROOT, argv);
665 xmove_fd(xsocket(AF_INET, SOCK_DGRAM, 0), ioctl_fd);
666 if (opts & FLAG_MONITOR) {
667 xmove_fd(netlink_open(), netlink_fd);
670 write_pidfile(pidfile_name);
672 /* this can't be moved before socket creation */
673 if (!(opts & FLAG_NO_SYSLOG)) {
674 openlog(applet_name, 0, LOG_DAEMON);
675 logmode |= LOGMODE_SYSLOG;
682 | (1 << SIGHUP ) /* why we ignore it? */
683 /* | (1 << SIGCHLD) - run_script does not use it anymore */
686 bb_error_msg("started: %s", bb_banner);
688 if (opts & FLAG_MONITOR) {
689 struct ifreq ifrequest;
690 set_ifreq_to_ifname(&ifrequest);
691 G.iface_exists = (network_ioctl(SIOCGIFINDEX, &ifrequest, NULL) == 0);
695 maybe_up_new_iface();
697 iface_status = detect_link();
698 if (iface_status == IFSTATUS_ERR)
700 iface_status_str = strstatus(iface_status);
702 if (opts & FLAG_MONITOR) {
703 bb_error_msg("interface %s",
704 G.iface_exists ? "exists"
705 : "doesn't exist, waiting");
707 /* else we assume it always exists, but don't mislead user
708 * by potentially lying that it really exists */
710 if (G.iface_exists) {
711 bb_error_msg("link is %s", iface_status_str);
714 if ((!(opts & FLAG_NO_STARTUP)
715 && iface_status == IFSTATUS_UP
717 || (opts & FLAG_INITIAL_DOWN)
719 if (run_script(iface_status_str) != 0)
724 netlink_pollfd[0].fd = netlink_fd;
725 netlink_pollfd[0].events = POLLIN;
728 int iface_status_old;
729 int iface_exists_old;
731 switch (bb_got_signal) {
744 if (poll(netlink_pollfd,
745 (opts & FLAG_MONITOR) ? 1 : 0,
751 bb_perror_msg("poll");
755 iface_status_old = iface_status;
756 iface_exists_old = G.iface_exists;
758 if ((opts & FLAG_MONITOR)
759 && (netlink_pollfd[0].revents & POLLIN)
761 G.iface_exists = check_existence_through_netlink();
762 if (G.iface_exists < 0) /* error */
764 if (iface_exists_old != G.iface_exists) {
765 bb_error_msg("interface %sappeared",
766 G.iface_exists ? "" : "dis");
768 maybe_up_new_iface();
772 /* note: if !G.iface_exists, returns DOWN */
773 iface_status = detect_link();
774 if (iface_status == IFSTATUS_ERR) {
775 if (!(opts & FLAG_MONITOR))
777 iface_status = IFSTATUS_DOWN;
779 iface_status_str = strstatus(iface_status);
781 if (iface_status_old != iface_status) {
782 bb_error_msg("link is %s", iface_status_str);
785 /* link restored its old status before
786 * we run script. don't run the script: */
789 delay_time = monotonic_sec();
790 if (iface_status == IFSTATUS_UP)
791 delay_time += G.delay_up;
792 if (iface_status == IFSTATUS_DOWN)
793 delay_time += G.delay_down;
799 if (delay_time && (int)(monotonic_sec() - delay_time) >= 0) {
801 if (run_script(iface_status_str) != 0)
807 if (!(opts & FLAG_NO_SHUTDOWN)
808 && (iface_status == IFSTATUS_UP
809 || (iface_status == IFSTATUS_DOWN && delay_time)
812 setenv(IFPLUGD_ENV_PREVIOUS, strstatus(iface_status), 1);
813 setenv(IFPLUGD_ENV_CURRENT, strstatus(-1), 1);
814 run_script("down\0up"); /* reusing string */
818 remove_pidfile(pidfile_name);
819 bb_error_msg_and_die("exiting");