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);
130 bb_perror_msg("fork");
136 execlp(G.script_name, G.script_name, G.iface, action, G.extra_arg, NULL);
137 bb_perror_msg_and_die("can't execute '%s'", G.script_name);
144 bb_error_msg("exit code: %u", r);
145 return (option_mask32 & FLAG_IGNORE_RETVAL) ? 0 : r;
149 struct fd_pair pipe_pair;
153 xpiped_pair(pipe_pair);
157 bb_perror_msg("fork");
163 xmove_fd(pipe_pair.wr, 1);
165 if (pipe_pair.rd > 2)
168 // umask(0022); // Set up a sane umask
170 execlp(G.script_name, G.script_name, G.iface, action, G.extra_arg, NULL);
178 if (bb_got_signal && bb_got_signal != SIGCHLD) {
179 bb_error_msg("killing child");
185 r = read(pipe_pair.rd, &buf[i], 1);
187 if (buf[i] == '\n' || i == sizeof(buf)-2 || r != 1) {
188 if (r == 1 && buf[i] != '\n')
194 bb_error_msg("client: %s", buf);
209 if (!WIFEXITED(r) || WEXITSTATUS(r) != 0) {
210 bb_error_msg("program execution failed, return value is %i",
212 return option_mask32 & FLAG_IGNORE_RETVAL ? 0 : WEXITSTATUS(r);
214 bb_error_msg("program executed successfully");
219 static int network_ioctl(int request, void* data)
221 return ioctl(ioctl_fd, request, data);
224 static void set_ifreq_to_ifname(struct ifreq *ifreq)
226 memset(ifreq, 0, sizeof(struct ifreq));
227 strncpy_IFNAMSIZ(ifreq->ifr_name, G.iface);
230 static const char *strstatus(int status)
232 if (status == IFSTATUS_ERR)
234 return "down\0up" + (status * 5);
237 static void up_iface(void)
239 struct ifreq ifrequest;
244 set_ifreq_to_ifname(&ifrequest);
245 if (network_ioctl(SIOCGIFFLAGS, &ifrequest) < 0) {
246 bb_perror_msg("can't %cet interface flags", 'g');
251 if (!(ifrequest.ifr_flags & IFF_UP)) {
252 ifrequest.ifr_flags |= IFF_UP;
253 /* Let user know we mess up with interface */
254 bb_error_msg("upping interface");
255 if (network_ioctl(SIOCSIFFLAGS, &ifrequest) < 0)
256 bb_perror_msg_and_die("can't %cet interface flags", 's');
259 #if 0 /* why do we mess with IP addr? It's not our business */
260 if (network_ioctl(SIOCGIFADDR, &ifrequest) < 0) {
261 bb_error_msg("can't get interface address");
262 } else if (ifrequest.ifr_addr.sa_family != AF_INET) {
263 bb_perror_msg("the interface is not IP-based");
265 ((struct sockaddr_in*)(&ifrequest.ifr_addr))->sin_addr.s_addr = INADDR_ANY;
266 if (network_ioctl(SIOCSIFADDR, &ifrequest) < 0)
267 bb_perror_msg("can't set interface address");
269 if (network_ioctl(SIOCGIFFLAGS, &ifrequest) < 0) {
270 bb_perror_msg("can't get interface flags");
276 static void maybe_up_new_iface(void)
278 if (!(option_mask32 & FLAG_NO_AUTO))
282 struct ifreq ifrequest;
283 struct ethtool_drvinfo driver_info;
285 set_ifreq_to_ifname(&ifrequest);
286 driver_info.cmd = ETHTOOL_GDRVINFO;
287 ifrequest.ifr_data = &driver_info;
288 if (network_ioctl(SIOCETHTOOL, &ifrequest) == 0) {
289 char buf[sizeof("/xx:xx:xx:xx:xx:xx")];
293 set_ifreq_to_ifname(&ifrequest);
294 if (network_ioctl(SIOCGIFHWADDR, &ifrequest) == 0) {
295 sprintf(buf, "/%02X:%02X:%02X:%02X:%02X:%02X",
296 (uint8_t)(ifrequest.ifr_hwaddr.sa_data[0]),
297 (uint8_t)(ifrequest.ifr_hwaddr.sa_data[1]),
298 (uint8_t)(ifrequest.ifr_hwaddr.sa_data[2]),
299 (uint8_t)(ifrequest.ifr_hwaddr.sa_data[3]),
300 (uint8_t)(ifrequest.ifr_hwaddr.sa_data[4]),
301 (uint8_t)(ifrequest.ifr_hwaddr.sa_data[5]));
304 bb_error_msg("using interface %s%s with driver<%s> (version: %s)",
305 G.iface, buf, driver_info.driver, driver_info.version);
309 G.cached_detect_link_func = NULL;
312 static smallint detect_link_mii(void)
315 struct mii_ioctl_data *mii = (void *)&ifreq.ifr_data;
317 set_ifreq_to_ifname(&ifreq);
319 if (network_ioctl(SIOCGMIIPHY, &ifreq) < 0) {
320 bb_perror_msg("SIOCGMIIPHY failed");
326 if (network_ioctl(SIOCGMIIREG, &ifreq) < 0) {
327 bb_perror_msg("SIOCGMIIREG failed");
331 return (mii->val_out & 0x0004) ? IFSTATUS_UP : IFSTATUS_DOWN;
334 static smallint detect_link_priv(void)
337 struct mii_ioctl_data *mii = (void *)&ifreq.ifr_data;
339 set_ifreq_to_ifname(&ifreq);
341 if (network_ioctl(SIOCDEVPRIVATE, &ifreq) < 0) {
342 bb_perror_msg("SIOCDEVPRIVATE failed");
348 if (network_ioctl(SIOCDEVPRIVATE+1, &ifreq) < 0) {
349 bb_perror_msg("SIOCDEVPRIVATE+1 failed");
353 return (mii->val_out & 0x0004) ? IFSTATUS_UP : IFSTATUS_DOWN;
356 static smallint detect_link_ethtool(void)
359 struct ethtool_value edata;
361 set_ifreq_to_ifname(&ifreq);
363 edata.cmd = ETHTOOL_GLINK;
364 ifreq.ifr_data = (void*) &edata;
366 if (network_ioctl(SIOCETHTOOL, &ifreq) < 0) {
367 bb_perror_msg("ETHTOOL_GLINK failed");
371 return edata.data ? IFSTATUS_UP : IFSTATUS_DOWN;
374 static smallint detect_link_iff(void)
378 set_ifreq_to_ifname(&ifreq);
380 if (network_ioctl(SIOCGIFFLAGS, &ifreq) < 0) {
381 bb_perror_msg("SIOCGIFFLAGS failed");
385 return (ifreq.ifr_flags & IFF_RUNNING) ? IFSTATUS_UP : IFSTATUS_DOWN;
388 static smallint detect_link_wlan(void)
390 struct iwreq iwrequest;
391 uint8_t mac[ETH_ALEN];
393 memset(&iwrequest, 0, sizeof(struct iwreq));
394 strncpy_IFNAMSIZ(iwrequest.ifr_ifrn.ifrn_name, G.iface);
396 if (network_ioctl(SIOCGIWAP, &iwrequest) < 0) {
397 bb_perror_msg("SIOCGIWAP failed");
401 memcpy(mac, &(iwrequest.u.ap_addr.sa_data), ETH_ALEN);
403 if (mac[0] == 0xFF || mac[0] == 0x44 || mac[0] == 0x00) {
404 for (int i = 1; i < ETH_ALEN; ++i) {
405 if (mac[i] != mac[0])
408 return IFSTATUS_DOWN;
414 static smallint detect_link_auto(void)
417 smallint iface_status;
420 if (G.cached_detect_link_func) {
421 iface_status = G.cached_detect_link_func();
422 if (iface_status != IFSTATUS_ERR)
426 sv_logmode = logmode;
427 logmode = LOGMODE_NONE;
429 iface_status = detect_link_ethtool();
430 if (iface_status != IFSTATUS_ERR) {
431 G.cached_detect_link_func = detect_link_ethtool;
432 method = "SIOCETHTOOL";
434 logmode = sv_logmode;
435 bb_error_msg("using %s detection mode", method);
439 iface_status = detect_link_mii();
440 if (iface_status != IFSTATUS_ERR) {
441 G.cached_detect_link_func = detect_link_mii;
442 method = "SIOCGMIIPHY";
446 iface_status = detect_link_priv();
447 if (iface_status != IFSTATUS_ERR) {
448 G.cached_detect_link_func = detect_link_priv;
449 method = "SIOCDEVPRIVATE";
453 iface_status = detect_link_wlan();
454 if (iface_status != IFSTATUS_ERR) {
455 G.cached_detect_link_func = detect_link_wlan;
456 method = "wireless extension";
460 iface_status = detect_link_iff();
461 if (iface_status != IFSTATUS_ERR) {
462 G.cached_detect_link_func = detect_link_iff;
463 method = "IFF_RUNNING";
467 logmode = sv_logmode;
468 return iface_status; /* IFSTATUS_ERR */
471 static smallint detect_link(void)
476 return (option_mask32 & FLAG_MONITOR) ? IFSTATUS_DOWN : IFSTATUS_ERR;
479 /* Why? This behavior makes it hard to temporary down the iface.
480 * It makes a bit more sense to do only in maybe_up_new_iface.
481 * OTOH, maybe detect_link_wlan needs this. Then it should be done
484 if (!(option_mask32 & FLAG_NO_AUTO))
488 status = G.detect_link_func();
489 if (status == IFSTATUS_ERR) {
490 if (option_mask32 & FLAG_IGNORE_FAIL)
491 status = IFSTATUS_DOWN;
492 if (option_mask32 & FLAG_IGNORE_FAIL_POSITIVE)
493 status = IFSTATUS_UP;
496 if (status == IFSTATUS_ERR
497 && G.detect_link_func == detect_link_auto
499 bb_error_msg("failed to detect link status");
502 if (status != G.iface_last_status) {
503 //TODO: is it safe to repeatedly do this?
504 setenv(IFPLUGD_ENV_PREVIOUS, strstatus(G.iface_last_status), 1);
505 setenv(IFPLUGD_ENV_CURRENT, strstatus(status), 1);
506 G.iface_last_status = status;
512 static NOINLINE int check_existence_through_netlink(void)
517 struct nlmsghdr *mhdr;
520 bytes = recv(netlink_fd, &replybuf, sizeof(replybuf), MSG_DONTWAIT);
523 return G.iface_exists;
527 bb_perror_msg("netlink: recv");
531 mhdr = (struct nlmsghdr*)replybuf;
533 if (!NLMSG_OK(mhdr, bytes)
534 || bytes < sizeof(struct nlmsghdr)
535 || bytes < mhdr->nlmsg_len
537 bb_error_msg("netlink packet too small or truncated");
541 if (mhdr->nlmsg_type == RTM_NEWLINK || mhdr->nlmsg_type == RTM_DELLINK) {
543 struct ifinfomsg *imsg;
546 imsg = NLMSG_DATA(mhdr);
548 if (mhdr->nlmsg_len < NLMSG_LENGTH(sizeof(struct ifinfomsg))) {
549 bb_error_msg("netlink packet too small or truncated");
553 attr = (struct rtattr*)((char*)imsg + NLMSG_ALIGN(sizeof(struct ifinfomsg)));
554 attr_len = NLMSG_PAYLOAD(mhdr, sizeof(struct ifinfomsg));
556 while (RTA_OK(attr, attr_len)) {
557 if (attr->rta_type == IFLA_IFNAME) {
558 char ifname[IFNAMSIZ + 1];
559 int len = RTA_PAYLOAD(attr);
563 memcpy(ifname, RTA_DATA(attr), len);
564 if (strcmp(G.iface, ifname) == 0) {
565 G.iface_exists = (mhdr->nlmsg_type == RTM_NEWLINK);
568 attr = RTA_NEXT(attr, attr_len);
572 mhdr = NLMSG_NEXT(mhdr, bytes);
576 return G.iface_exists;
579 static NOINLINE int netlink_open(void)
582 struct sockaddr_nl addr;
584 fd = xsocket(PF_NETLINK, SOCK_DGRAM, NETLINK_ROUTE);
586 memset(&addr, 0, sizeof(addr));
587 addr.nl_family = AF_NETLINK;
588 addr.nl_groups = RTMGRP_LINK;
589 addr.nl_pid = getpid();
591 xbind(fd, (struct sockaddr*)&addr, sizeof(addr));
596 #if ENABLE_FEATURE_PIDFILE
597 static NOINLINE pid_t read_pid(const char *filename)
602 len = open_read_close(filename, buf, 127);
605 /* returns ULONG_MAX on error => -1 */
606 return bb_strtoul(buf, NULL, 10);
612 int ifplugd_main(int argc, char **argv) MAIN_EXTERNALLY_VISIBLE;
613 int ifplugd_main(int argc UNUSED_PARAM, char **argv)
617 const char *iface_status_str;
618 struct pollfd netlink_pollfd[1];
620 #if ENABLE_FEATURE_PIDFILE
622 pid_t pid_from_pidfile;
627 opt_complementary = "t+:u+:d+";
628 opts = getopt32(argv, OPTION_STR,
629 &G.iface, &G.script_name, &G.poll_time, &G.delay_up,
630 &G.delay_down, &G.api_mode, &G.extra_arg);
633 applet_name = xasprintf("ifplugd(%s)", G.iface);
635 #if ENABLE_FEATURE_PIDFILE
636 pidfile_name = xasprintf(_PATH_VARRUN"ifplugd.%s.pid", G.iface);
637 pid_from_pidfile = read_pid(pidfile_name);
639 if (opts & FLAG_KILL) {
640 if (pid_from_pidfile > 0)
641 kill(pid_from_pidfile, SIGQUIT);
645 if (pid_from_pidfile > 0 && kill(pid_from_pidfile, 0) == 0)
646 bb_error_msg_and_die("daemon already running");
649 switch (G.api_mode[0]) {
651 G.detect_link_func = detect_link_auto;
654 G.detect_link_func = detect_link_ethtool;
657 G.detect_link_func = detect_link_mii;
660 G.detect_link_func = detect_link_priv;
663 G.detect_link_func = detect_link_wlan;
666 G.detect_link_func = detect_link_iff;
669 bb_error_msg_and_die("unknown API mode '%s'", G.api_mode);
672 if (!(opts & FLAG_NO_DAEMON))
673 bb_daemonize_or_rexec(DAEMON_CHDIR_ROOT, argv);
675 xmove_fd(xsocket(AF_INET, SOCK_DGRAM, 0), ioctl_fd);
676 if (opts & FLAG_MONITOR) {
677 xmove_fd(netlink_open(), netlink_fd);
680 write_pidfile(pidfile_name);
682 /* this can't be moved before socket creation */
683 if (!(opts & FLAG_NO_SYSLOG)) {
684 openlog(applet_name, 0, LOG_DAEMON);
685 logmode |= LOGMODE_SYSLOG;
692 | (1 << SIGHUP ) /* why we ignore it? */
693 /* | (1 << SIGCHLD) - run_script does not use it anymore */
696 bb_error_msg("started: %s", bb_banner);
698 if (opts & FLAG_MONITOR) {
699 struct ifreq ifrequest;
700 set_ifreq_to_ifname(&ifrequest);
701 G.iface_exists = (network_ioctl(SIOCGIFINDEX, &ifrequest) == 0);
705 maybe_up_new_iface();
707 iface_status = detect_link();
708 if (iface_status == IFSTATUS_ERR)
710 iface_status_str = strstatus(iface_status);
712 if (opts & FLAG_MONITOR) {
713 bb_error_msg("interface %s",
714 G.iface_exists ? "exists"
715 : "doesn't exist, waiting");
717 /* else we assume it always exists, but don't mislead user
718 * by potentially lying that it really exists */
720 if (G.iface_exists) {
721 bb_error_msg("link is %s", iface_status_str);
724 if ((!(opts & FLAG_NO_STARTUP)
725 && iface_status == IFSTATUS_UP
727 || (opts & FLAG_INITIAL_DOWN)
729 if (run_script(iface_status_str) != 0)
734 netlink_pollfd[0].fd = netlink_fd;
735 netlink_pollfd[0].events = POLLIN;
738 int iface_status_old;
739 int iface_exists_old;
741 switch (bb_got_signal) {
754 if (poll(netlink_pollfd,
755 (opts & FLAG_MONITOR) ? 1 : 0,
761 bb_perror_msg("poll");
765 iface_status_old = iface_status;
766 iface_exists_old = G.iface_exists;
768 if ((opts & FLAG_MONITOR)
769 && (netlink_pollfd[0].revents & POLLIN)
771 G.iface_exists = check_existence_through_netlink();
772 if (G.iface_exists < 0) /* error */
774 if (iface_exists_old != G.iface_exists) {
775 bb_error_msg("interface %sappeared",
776 G.iface_exists ? "" : "dis");
778 maybe_up_new_iface();
782 /* note: if !G.iface_exists, returns DOWN */
783 iface_status = detect_link();
784 if (iface_status == IFSTATUS_ERR) {
785 if (!(opts & FLAG_MONITOR))
787 iface_status = IFSTATUS_DOWN;
789 iface_status_str = strstatus(iface_status);
791 if (iface_status_old != iface_status) {
792 bb_error_msg("link is %s", iface_status_str);
795 /* link restored its old status before
796 * we run script. don't run the script: */
799 delay_time = monotonic_sec();
800 if (iface_status == IFSTATUS_UP)
801 delay_time += G.delay_up;
802 if (iface_status == IFSTATUS_DOWN)
803 delay_time += G.delay_down;
809 if (delay_time && (int)(monotonic_sec() - delay_time) >= 0) {
811 if (run_script(iface_status_str) != 0)
817 if (!(opts & FLAG_NO_SHUTDOWN)
818 && (iface_status == IFSTATUS_UP
819 || (iface_status == IFSTATUS_DOWN && delay_time)
822 setenv(IFPLUGD_ENV_PREVIOUS, strstatus(iface_status), 1);
823 setenv(IFPLUGD_ENV_CURRENT, strstatus(-1), 1);
824 run_script("down\0up"); /* reusing string */
828 remove_pidfile(pidfile_name);
829 bb_error_msg_and_die("exiting");