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.
12 #include <linux/mii.h>
13 #include <linux/ethtool.h>
14 #include <net/ethernet.h>
15 #include <linux/netlink.h>
16 #include <linux/rtnetlink.h>
17 #include <linux/sockios.h>
21 #include <linux/wireless.h>
24 TODO: describe compat status here.
26 One questionable point of the design is netlink usage:
28 We have 1 second timeout by default to poll the link status,
29 it is short enough so that there are no real benefits in
30 using netlink to get "instantaneous" interface creation/deletion
31 notifications. We can check for interface existence by just
32 doing some fast ioctl using its name.
34 Netlink code then can be just dropped (1k or more?)
38 #define IFPLUGD_ENV_PREVIOUS "IFPLUGD_PREVIOUS"
39 #define IFPLUGD_ENV_CURRENT "IFPLUGD_CURRENT"
42 FLAG_NO_AUTO = 1 << 0, // -a, Do not enable interface automatically
43 FLAG_NO_DAEMON = 1 << 1, // -n, Do not daemonize
44 FLAG_NO_SYSLOG = 1 << 2, // -s, Do not use syslog, use stderr instead
45 FLAG_IGNORE_FAIL = 1 << 3, // -f, Ignore detection failure, retry instead (failure is treated as DOWN)
46 FLAG_IGNORE_FAIL_POSITIVE = 1 << 4, // -F, Ignore detection failure, retry instead (failure is treated as UP)
47 FLAG_IFACE = 1 << 5, // -i, Specify ethernet interface
48 FLAG_RUN = 1 << 6, // -r, Specify program to execute
49 FLAG_IGNORE_RETVAL = 1 << 7, // -I, Don't exit on nonzero return value of program executed
50 FLAG_POLL_TIME = 1 << 8, // -t, Specify poll time in seconds
51 FLAG_DELAY_UP = 1 << 9, // -u, Specify delay for configuring interface
52 FLAG_DELAY_DOWN = 1 << 10, // -d, Specify delay for deconfiguring interface
53 FLAG_API_MODE = 1 << 11, // -m, Force API mode (mii, priv, ethtool, wlan, auto)
54 FLAG_NO_STARTUP = 1 << 12, // -p, Don't run script on daemon startup
55 FLAG_NO_SHUTDOWN = 1 << 13, // -q, Don't run script on daemon quit
56 FLAG_INITIAL_DOWN = 1 << 14, // -l, Run "down" script on startup if no cable is detected
57 FLAG_EXTRA_ARG = 1 << 15, // -x, Specify an extra argument for action script
58 FLAG_MONITOR = 1 << 16, // -M, Use interface monitoring
59 #if ENABLE_FEATURE_PIDFILE
60 FLAG_KILL = 1 << 17, // -k, Kill a running daemon
63 #if ENABLE_FEATURE_PIDFILE
64 # define OPTION_STR "+ansfFi:r:It:u:d:m:pqlx:Mk"
66 # define OPTION_STR "+ansfFi:r:It:u:d:m:pqlx:M"
78 enum { // interface status
84 enum { // constant fds
90 smallint iface_last_status;
91 smallint iface_exists;
93 /* Used in getopt32, must have sizeof == sizeof(int) */
100 const char *script_name;
101 const char *extra_arg;
103 smallint (*detect_link_func)(void);
104 smallint (*cached_detect_link_func)(void);
106 #define G (*ptr_to_globals)
107 #define INIT_G() do { \
108 SET_PTR_TO_GLOBALS(xzalloc(sizeof(G))); \
109 G.iface_last_status = -1; \
110 G.iface_exists = 1; \
115 G.script_name = "/etc/ifplugd/ifplugd.action"; \
119 static int run_script(const char *action)
124 bb_error_msg("executing '%s %s %s'", G.script_name, G.iface, action);
129 bb_perror_msg("fork");
135 execlp(G.script_name, G.script_name, G.iface, action, G.extra_arg, NULL);
136 bb_perror_msg_and_die("can't execute '%s'", G.script_name);
143 bb_error_msg("exit code: %u", r);
144 return (option_mask32 & FLAG_IGNORE_RETVAL) ? 0 : r;
148 struct fd_pair pipe_pair;
152 xpiped_pair(pipe_pair);
156 bb_perror_msg("fork");
162 xmove_fd(pipe_pair.wr, 1);
164 if (pipe_pair.rd > 2)
167 // umask(0022); // Set up a sane umask
169 execlp(G.script_name, G.script_name, G.iface, action, G.extra_arg, NULL);
177 if (bb_got_signal && bb_got_signal != SIGCHLD) {
178 bb_error_msg("killing child");
184 r = read(pipe_pair.rd, &buf[i], 1);
186 if (buf[i] == '\n' || i == sizeof(buf)-2 || r != 1) {
187 if (r == 1 && buf[i] != '\n')
193 bb_error_msg("client: %s", buf);
208 if (!WIFEXITED(r) || WEXITSTATUS(r) != 0) {
209 bb_error_msg("program execution failed, return value is %i",
211 return option_mask32 & FLAG_IGNORE_RETVAL ? 0 : WEXITSTATUS(r);
213 bb_error_msg("program executed successfully");
218 static int network_ioctl(int request, void* data)
220 return ioctl(ioctl_fd, request, data);
223 static void set_ifreq_to_ifname(struct ifreq *ifreq)
225 memset(ifreq, 0, sizeof(struct ifreq));
226 strncpy_IFNAMSIZ(ifreq->ifr_name, G.iface);
229 static const char *strstatus(int status)
231 if (status == IFSTATUS_ERR)
233 return "down\0up" + (status * 5);
236 static void up_iface(void)
238 struct ifreq ifrequest;
243 set_ifreq_to_ifname(&ifrequest);
244 if (network_ioctl(SIOCGIFFLAGS, &ifrequest) < 0) {
245 bb_perror_msg("can't %cet interface flags", 'g');
250 if (!(ifrequest.ifr_flags & IFF_UP)) {
251 ifrequest.ifr_flags |= IFF_UP;
252 /* Let user know we mess up with interface */
253 bb_error_msg("upping interface");
254 if (network_ioctl(SIOCSIFFLAGS, &ifrequest) < 0)
255 bb_perror_msg_and_die("can't %cet interface flags", 's');
258 #if 0 /* why do we mess with IP addr? It's not our business */
259 if (network_ioctl(SIOCGIFADDR, &ifrequest) < 0) {
260 bb_error_msg("can't get interface address");
261 } else if (ifrequest.ifr_addr.sa_family != AF_INET) {
262 bb_perror_msg("the interface is not IP-based");
264 ((struct sockaddr_in*)(&ifrequest.ifr_addr))->sin_addr.s_addr = INADDR_ANY;
265 if (network_ioctl(SIOCSIFADDR, &ifrequest) < 0)
266 bb_perror_msg("can't set interface address");
268 if (network_ioctl(SIOCGIFFLAGS, &ifrequest) < 0) {
269 bb_perror_msg("can't get interface flags");
275 static void maybe_up_new_iface(void)
277 if (!(option_mask32 & FLAG_NO_AUTO))
281 struct ifreq ifrequest;
282 struct ethtool_drvinfo driver_info;
284 set_ifreq_to_ifname(&ifrequest);
285 driver_info.cmd = ETHTOOL_GDRVINFO;
286 ifrequest.ifr_data = &driver_info;
287 if (network_ioctl(SIOCETHTOOL, &ifrequest) == 0) {
288 char buf[sizeof("/xx:xx:xx:xx:xx:xx")];
292 set_ifreq_to_ifname(&ifrequest);
293 if (network_ioctl(SIOCGIFHWADDR, &ifrequest) == 0) {
294 sprintf(buf, "/%02X:%02X:%02X:%02X:%02X:%02X",
295 (uint8_t)(ifrequest.ifr_hwaddr.sa_data[0]),
296 (uint8_t)(ifrequest.ifr_hwaddr.sa_data[1]),
297 (uint8_t)(ifrequest.ifr_hwaddr.sa_data[2]),
298 (uint8_t)(ifrequest.ifr_hwaddr.sa_data[3]),
299 (uint8_t)(ifrequest.ifr_hwaddr.sa_data[4]),
300 (uint8_t)(ifrequest.ifr_hwaddr.sa_data[5]));
303 bb_error_msg("using interface %s%s with driver<%s> (version: %s)",
304 G.iface, buf, driver_info.driver, driver_info.version);
308 G.cached_detect_link_func = NULL;
311 static smallint detect_link_mii(void)
314 struct mii_ioctl_data *mii = (void *)&ifreq.ifr_data;
316 set_ifreq_to_ifname(&ifreq);
318 if (network_ioctl(SIOCGMIIPHY, &ifreq) < 0) {
319 bb_perror_msg("SIOCGMIIPHY failed");
325 if (network_ioctl(SIOCGMIIREG, &ifreq) < 0) {
326 bb_perror_msg("SIOCGMIIREG failed");
330 return (mii->val_out & 0x0004) ? IFSTATUS_UP : IFSTATUS_DOWN;
333 static smallint detect_link_priv(void)
336 struct mii_ioctl_data *mii = (void *)&ifreq.ifr_data;
338 set_ifreq_to_ifname(&ifreq);
340 if (network_ioctl(SIOCDEVPRIVATE, &ifreq) < 0) {
341 bb_perror_msg("SIOCDEVPRIVATE failed");
347 if (network_ioctl(SIOCDEVPRIVATE+1, &ifreq) < 0) {
348 bb_perror_msg("SIOCDEVPRIVATE+1 failed");
352 return (mii->val_out & 0x0004) ? IFSTATUS_UP : IFSTATUS_DOWN;
355 static smallint detect_link_ethtool(void)
358 struct ethtool_value edata;
360 set_ifreq_to_ifname(&ifreq);
362 edata.cmd = ETHTOOL_GLINK;
363 ifreq.ifr_data = &edata;
365 if (network_ioctl(SIOCETHTOOL, &ifreq) < 0) {
366 bb_perror_msg("ETHTOOL_GLINK failed");
370 return edata.data ? IFSTATUS_UP : IFSTATUS_DOWN;
373 static smallint detect_link_iff(void)
377 set_ifreq_to_ifname(&ifreq);
379 if (network_ioctl(SIOCGIFFLAGS, &ifreq) < 0) {
380 bb_perror_msg("SIOCGIFFLAGS failed");
384 return (ifreq.ifr_flags & IFF_RUNNING) ? IFSTATUS_UP : IFSTATUS_DOWN;
387 static smallint detect_link_wlan(void)
389 struct iwreq iwrequest;
390 uint8_t mac[ETH_ALEN];
392 memset(&iwrequest, 0, sizeof(struct iwreq));
393 strncpy_IFNAMSIZ(iwrequest.ifr_ifrn.ifrn_name, G.iface);
395 if (network_ioctl(SIOCGIWAP, &iwrequest) < 0) {
396 bb_perror_msg("SIOCGIWAP failed");
400 memcpy(mac, &(iwrequest.u.ap_addr.sa_data), ETH_ALEN);
402 if (mac[0] == 0xFF || mac[0] == 0x44 || mac[0] == 0x00) {
403 for (int i = 1; i < ETH_ALEN; ++i) {
404 if (mac[i] != mac[0])
407 return IFSTATUS_DOWN;
413 static smallint detect_link_auto(void)
416 smallint iface_status;
419 if (G.cached_detect_link_func) {
420 iface_status = G.cached_detect_link_func();
421 if (iface_status != IFSTATUS_ERR)
425 sv_logmode = logmode;
426 logmode = LOGMODE_NONE;
428 iface_status = detect_link_ethtool();
429 if (iface_status != IFSTATUS_ERR) {
430 G.cached_detect_link_func = detect_link_ethtool;
431 method = "SIOCETHTOOL";
433 logmode = sv_logmode;
434 bb_error_msg("using %s detection mode", method);
438 iface_status = detect_link_mii();
439 if (iface_status != IFSTATUS_ERR) {
440 G.cached_detect_link_func = detect_link_mii;
441 method = "SIOCGMIIPHY";
445 iface_status = detect_link_priv();
446 if (iface_status != IFSTATUS_ERR) {
447 G.cached_detect_link_func = detect_link_priv;
448 method = "SIOCDEVPRIVATE";
452 iface_status = detect_link_wlan();
453 if (iface_status != IFSTATUS_ERR) {
454 G.cached_detect_link_func = detect_link_wlan;
455 method = "wireless extension";
459 iface_status = detect_link_iff();
460 if (iface_status != IFSTATUS_ERR) {
461 G.cached_detect_link_func = detect_link_iff;
462 method = "IFF_RUNNING";
466 logmode = sv_logmode;
467 return iface_status; /* IFSTATUS_ERR */
470 static smallint detect_link(void)
475 return (option_mask32 & FLAG_MONITOR) ? IFSTATUS_DOWN : IFSTATUS_ERR;
478 /* Why? This behavior makes it hard to temporary down the iface.
479 * It makes a bit more sense to do only in maybe_up_new_iface.
480 * OTOH, maybe detect_link_wlan needs this. Then it should be done
483 if (!(option_mask32 & FLAG_NO_AUTO))
487 status = G.detect_link_func();
488 if (status == IFSTATUS_ERR) {
489 if (option_mask32 & FLAG_IGNORE_FAIL)
490 status = IFSTATUS_DOWN;
491 if (option_mask32 & FLAG_IGNORE_FAIL_POSITIVE)
492 status = IFSTATUS_UP;
495 if (status == IFSTATUS_ERR
496 && G.detect_link_func == detect_link_auto
498 bb_error_msg("failed to detect link status");
501 if (status != G.iface_last_status) {
502 //TODO: is it safe to repeatedly do this?
503 setenv(IFPLUGD_ENV_PREVIOUS, strstatus(G.iface_last_status), 1);
504 setenv(IFPLUGD_ENV_CURRENT, strstatus(status), 1);
505 G.iface_last_status = status;
511 static NOINLINE int check_existence_through_netlink(void)
516 struct nlmsghdr *mhdr;
519 bytes = recv(netlink_fd, &replybuf, sizeof(replybuf), MSG_DONTWAIT);
522 return G.iface_exists;
526 bb_perror_msg("netlink: recv");
530 mhdr = (struct nlmsghdr*)replybuf;
532 if (!NLMSG_OK(mhdr, bytes)
533 || bytes < sizeof(struct nlmsghdr)
534 || bytes < mhdr->nlmsg_len
536 bb_error_msg("netlink packet too small or truncated");
540 if (mhdr->nlmsg_type == RTM_NEWLINK || mhdr->nlmsg_type == RTM_DELLINK) {
542 struct ifinfomsg *imsg;
545 imsg = NLMSG_DATA(mhdr);
547 if (mhdr->nlmsg_len < NLMSG_LENGTH(sizeof(struct ifinfomsg))) {
548 bb_error_msg("netlink packet too small or truncated");
552 attr = (struct rtattr*)((char*)imsg + NLMSG_ALIGN(sizeof(struct ifinfomsg)));
553 attr_len = NLMSG_PAYLOAD(mhdr, sizeof(struct ifinfomsg));
555 while (RTA_OK(attr, attr_len)) {
556 if (attr->rta_type == IFLA_IFNAME) {
557 char ifname[IFNAMSIZ + 1];
558 int len = RTA_PAYLOAD(attr);
562 memcpy(ifname, RTA_DATA(attr), len);
563 if (strcmp(G.iface, ifname) == 0) {
564 G.iface_exists = (mhdr->nlmsg_type == RTM_NEWLINK);
567 attr = RTA_NEXT(attr, attr_len);
571 mhdr = NLMSG_NEXT(mhdr, bytes);
575 return G.iface_exists;
578 static NOINLINE int netlink_open(void)
581 struct sockaddr_nl addr;
583 fd = xsocket(PF_NETLINK, SOCK_DGRAM, NETLINK_ROUTE);
585 memset(&addr, 0, sizeof(addr));
586 addr.nl_family = AF_NETLINK;
587 addr.nl_groups = RTMGRP_LINK;
588 addr.nl_pid = getpid();
590 xbind(fd, (struct sockaddr*)&addr, sizeof(addr));
595 #if ENABLE_FEATURE_PIDFILE
596 static NOINLINE pid_t read_pid(const char *filename)
601 len = open_read_close(filename, buf, 127);
604 /* returns ULONG_MAX on error => -1 */
605 return bb_strtoul(buf, NULL, 10);
611 int ifplugd_main(int argc, char **argv) MAIN_EXTERNALLY_VISIBLE;
612 int ifplugd_main(int argc UNUSED_PARAM, char **argv)
616 const char *iface_status_str;
617 struct pollfd netlink_pollfd[1];
619 #if ENABLE_FEATURE_PIDFILE
621 pid_t pid_from_pidfile;
626 opt_complementary = "t+:u+:d+";
627 opts = getopt32(argv, OPTION_STR,
628 &G.iface, &G.script_name, &G.poll_time, &G.delay_up,
629 &G.delay_down, &G.api_mode, &G.extra_arg);
631 applet_name = xasprintf("ifplugd(%s)", G.iface);
633 #if ENABLE_FEATURE_PIDFILE
634 pidfile_name = xasprintf(_PATH_VARRUN"ifplugd.%s.pid", G.iface);
635 pid_from_pidfile = read_pid(pidfile_name);
637 if (opts & FLAG_KILL) {
638 if (pid_from_pidfile > 0)
639 kill(pid_from_pidfile, SIGQUIT);
643 if (pid_from_pidfile > 0 && kill(pid_from_pidfile, 0) == 0)
644 bb_error_msg_and_die("daemon already running");
647 switch (G.api_mode[0]) {
649 G.detect_link_func = detect_link_auto;
652 G.detect_link_func = detect_link_ethtool;
655 G.detect_link_func = detect_link_mii;
658 G.detect_link_func = detect_link_priv;
661 G.detect_link_func = detect_link_wlan;
664 G.detect_link_func = detect_link_iff;
667 bb_error_msg_and_die("unknown API mode '%s'", G.api_mode);
670 if (!(opts & FLAG_NO_DAEMON))
671 bb_daemonize_or_rexec(DAEMON_CHDIR_ROOT, argv);
673 xmove_fd(xsocket(AF_INET, SOCK_DGRAM, 0), ioctl_fd);
674 if (opts & FLAG_MONITOR) {
675 xmove_fd(netlink_open(), netlink_fd);
678 write_pidfile(pidfile_name);
680 /* this can't be moved before socket creation */
681 if (!(opts & FLAG_NO_SYSLOG)) {
682 openlog(applet_name, 0, LOG_DAEMON);
683 logmode |= LOGMODE_SYSLOG;
690 | (1 << SIGHUP ) /* why we ignore it? */
691 /* | (1 << SIGCHLD) - run_script does not use it anymore */
694 bb_error_msg("started: %s", bb_banner);
696 if (opts & FLAG_MONITOR) {
697 struct ifreq ifrequest;
698 set_ifreq_to_ifname(&ifrequest);
699 G.iface_exists = (network_ioctl(SIOCGIFINDEX, &ifrequest) == 0);
703 maybe_up_new_iface();
705 iface_status = detect_link();
706 if (iface_status == IFSTATUS_ERR)
708 iface_status_str = strstatus(iface_status);
710 if (opts & FLAG_MONITOR) {
711 bb_error_msg("interface %s",
712 G.iface_exists ? "exists"
713 : "doesn't exist, waiting");
715 /* else we assume it always exists, but don't mislead user
716 * by potentially lying that it really exists */
718 if (G.iface_exists) {
719 bb_error_msg("link is %s", iface_status_str);
722 if ((!(opts & FLAG_NO_STARTUP)
723 && iface_status == IFSTATUS_UP
725 || (opts & FLAG_INITIAL_DOWN)
727 if (run_script(iface_status_str) != 0)
732 netlink_pollfd[0].fd = netlink_fd;
733 netlink_pollfd[0].events = POLLIN;
736 int iface_status_old;
737 int iface_exists_old;
739 switch (bb_got_signal) {
752 if (poll(netlink_pollfd,
753 (opts & FLAG_MONITOR) ? 1 : 0,
759 bb_perror_msg("poll");
763 iface_status_old = iface_status;
764 iface_exists_old = G.iface_exists;
766 if ((opts & FLAG_MONITOR)
767 && (netlink_pollfd[0].revents & POLLIN)
769 G.iface_exists = check_existence_through_netlink();
770 if (G.iface_exists < 0) /* error */
772 if (iface_exists_old != G.iface_exists) {
773 bb_error_msg("interface %sappeared",
774 G.iface_exists ? "" : "dis");
776 maybe_up_new_iface();
780 /* note: if !G.iface_exists, returns DOWN */
781 iface_status = detect_link();
782 if (iface_status == IFSTATUS_ERR) {
783 if (!(opts & FLAG_MONITOR))
785 iface_status = IFSTATUS_DOWN;
787 iface_status_str = strstatus(iface_status);
789 if (iface_status_old != iface_status) {
790 bb_error_msg("link is %s", iface_status_str);
793 /* link restored its old status before
794 * we run script. don't run the script: */
797 delay_time = monotonic_sec();
798 if (iface_status == IFSTATUS_UP)
799 delay_time += G.delay_up;
800 if (iface_status == IFSTATUS_DOWN)
801 delay_time += G.delay_down;
807 if (delay_time && (int)(monotonic_sec() - delay_time) >= 0) {
809 if (run_script(iface_status_str) != 0)
815 if (!(opts & FLAG_NO_SHUTDOWN)
816 && (iface_status == IFSTATUS_UP
817 || (iface_status == IFSTATUS_DOWN && delay_time)
820 setenv(IFPLUGD_ENV_PREVIOUS, strstatus(iface_status), 1);
821 setenv(IFPLUGD_ENV_CURRENT, strstatus(-1), 1);
822 run_script("down\0up"); /* reusing string */
826 remove_pidfile(pidfile_name);
827 bb_error_msg_and_die("exiting");