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("%s failed", 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, "getting 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, "setting 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") < 0) {
316 if (network_ioctl(SIOCGMIIREG, &ifreq, "SIOCGMIIREG") < 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") < 0) {
336 if (network_ioctl(SIOCDEVPRIVATE+1, &ifreq, "SIOCDEVPRIVATE+1") < 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") < 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") < 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)
385 struct iwreq iwrequest;
386 uint8_t mac[ETH_ALEN];
388 memset(&iwrequest, 0, sizeof(iwrequest));
389 strncpy_IFNAMSIZ(iwrequest.ifr_ifrn.ifrn_name, G.iface);
391 if (network_ioctl(SIOCGIWAP, &iwrequest, "SIOCGIWAP") < 0) {
395 memcpy(mac, &iwrequest.u.ap_addr.sa_data, ETH_ALEN);
397 if (mac[0] == 0xFF || mac[0] == 0x44 || mac[0] == 0x00) {
398 for (i = 1; i < ETH_ALEN; ++i) {
399 if (mac[i] != mac[0])
402 return IFSTATUS_DOWN;
408 static smallint detect_link_auto(void)
410 static const struct {
412 smallint (*func)(void);
414 { "SIOCETHTOOL" , &detect_link_ethtool },
415 { "SIOCGMIIPHY" , &detect_link_mii },
416 { "SIOCDEVPRIVATE" , &detect_link_priv },
417 { "wireless extension", &detect_link_wlan },
418 { "IFF_RUNNING" , &detect_link_iff },
421 smallint iface_status;
424 if (G.cached_detect_link_func) {
425 iface_status = G.cached_detect_link_func();
426 if (iface_status != IFSTATUS_ERR)
430 sv_logmode = logmode;
431 for (i = 0; i < ARRAY_SIZE(method); i++) {
432 logmode = LOGMODE_NONE;
433 iface_status = method[i].func();
434 logmode = sv_logmode;
435 if (iface_status != IFSTATUS_ERR) {
436 G.cached_detect_link_func = method[i].func;
437 bb_error_msg("using %s detection mode", method[i].name);
444 static smallint detect_link(void)
449 return (option_mask32 & FLAG_MONITOR) ? IFSTATUS_DOWN : IFSTATUS_ERR;
451 /* Some drivers can't detect link status when the interface is down.
452 * I imagine detect_link_iff() is the most vulnerable.
453 * That's why -a "noauto" in an option, not a hardwired behavior.
455 if (!(option_mask32 & FLAG_NO_AUTO))
458 status = G.detect_link_func();
459 if (status == IFSTATUS_ERR) {
460 if (option_mask32 & FLAG_IGNORE_FAIL)
461 status = IFSTATUS_DOWN;
462 if (option_mask32 & FLAG_IGNORE_FAIL_POSITIVE)
463 status = IFSTATUS_UP;
466 if (status == IFSTATUS_ERR
467 && G.detect_link_func == detect_link_auto
469 bb_error_msg("can't detect link status");
472 if (status != G.iface_last_status) {
473 //TODO: is it safe to repeatedly do this?
474 setenv(IFPLUGD_ENV_PREVIOUS, strstatus(G.iface_last_status), 1);
475 setenv(IFPLUGD_ENV_CURRENT, strstatus(status), 1);
476 G.iface_last_status = status;
482 static NOINLINE int check_existence_through_netlink(void)
487 struct nlmsghdr *mhdr;
490 bytes = recv(netlink_fd, &replybuf, sizeof(replybuf), MSG_DONTWAIT);
493 return G.iface_exists;
497 bb_perror_msg("netlink: recv");
501 mhdr = (struct nlmsghdr*)replybuf;
503 if (!NLMSG_OK(mhdr, bytes)
504 || bytes < sizeof(struct nlmsghdr)
505 || bytes < mhdr->nlmsg_len
507 bb_error_msg("netlink packet too small or truncated");
511 if (mhdr->nlmsg_type == RTM_NEWLINK || mhdr->nlmsg_type == RTM_DELLINK) {
513 struct ifinfomsg *imsg;
516 imsg = NLMSG_DATA(mhdr);
518 if (mhdr->nlmsg_len < NLMSG_LENGTH(sizeof(struct ifinfomsg))) {
519 bb_error_msg("netlink packet too small or truncated");
523 attr = (struct rtattr*)((char*)imsg + NLMSG_ALIGN(sizeof(struct ifinfomsg)));
524 attr_len = NLMSG_PAYLOAD(mhdr, sizeof(struct ifinfomsg));
526 while (RTA_OK(attr, attr_len)) {
527 if (attr->rta_type == IFLA_IFNAME) {
528 char ifname[IFNAMSIZ + 1];
529 int len = RTA_PAYLOAD(attr);
533 memcpy(ifname, RTA_DATA(attr), len);
534 if (strcmp(G.iface, ifname) == 0) {
535 G.iface_exists = (mhdr->nlmsg_type == RTM_NEWLINK);
538 attr = RTA_NEXT(attr, attr_len);
542 mhdr = NLMSG_NEXT(mhdr, bytes);
546 return G.iface_exists;
549 static NOINLINE int netlink_open(void)
552 struct sockaddr_nl addr;
554 fd = xsocket(PF_NETLINK, SOCK_DGRAM, NETLINK_ROUTE);
556 memset(&addr, 0, sizeof(addr));
557 addr.nl_family = AF_NETLINK;
558 addr.nl_groups = RTMGRP_LINK;
559 addr.nl_pid = getpid();
561 xbind(fd, (struct sockaddr*)&addr, sizeof(addr));
566 #if ENABLE_FEATURE_PIDFILE
567 static NOINLINE pid_t read_pid(const char *filename)
572 len = open_read_close(filename, buf, 127);
575 /* returns ULONG_MAX on error => -1 */
576 return bb_strtoul(buf, NULL, 10);
582 int ifplugd_main(int argc, char **argv) MAIN_EXTERNALLY_VISIBLE;
583 int ifplugd_main(int argc UNUSED_PARAM, char **argv)
587 const char *iface_status_str;
588 struct pollfd netlink_pollfd[1];
590 #if ENABLE_FEATURE_PIDFILE
592 pid_t pid_from_pidfile;
597 opt_complementary = "t+:u+:d+";
598 opts = getopt32(argv, OPTION_STR,
599 &G.iface, &G.script_name, &G.poll_time, &G.delay_up,
600 &G.delay_down, &G.api_mode, &G.extra_arg);
603 applet_name = xasprintf("ifplugd(%s)", G.iface);
605 #if ENABLE_FEATURE_PIDFILE
606 pidfile_name = xasprintf(_PATH_VARRUN"ifplugd.%s.pid", G.iface);
607 pid_from_pidfile = read_pid(pidfile_name);
609 if (opts & FLAG_KILL) {
610 if (pid_from_pidfile > 0)
611 kill(pid_from_pidfile, SIGQUIT);
615 if (pid_from_pidfile > 0 && kill(pid_from_pidfile, 0) == 0)
616 bb_error_msg_and_die("daemon already running");
619 switch (G.api_mode[0]) {
621 G.detect_link_func = detect_link_auto;
624 G.detect_link_func = detect_link_ethtool;
627 G.detect_link_func = detect_link_mii;
630 G.detect_link_func = detect_link_priv;
633 G.detect_link_func = detect_link_wlan;
636 G.detect_link_func = detect_link_iff;
639 bb_error_msg_and_die("unknown API mode '%s'", G.api_mode);
642 if (!(opts & FLAG_NO_DAEMON))
643 bb_daemonize_or_rexec(DAEMON_CHDIR_ROOT, argv);
645 xmove_fd(xsocket(AF_INET, SOCK_DGRAM, 0), ioctl_fd);
646 if (opts & FLAG_MONITOR) {
647 xmove_fd(netlink_open(), netlink_fd);
650 write_pidfile(pidfile_name);
652 /* this can't be moved before socket creation */
653 if (!(opts & FLAG_NO_SYSLOG)) {
654 openlog(applet_name, 0, LOG_DAEMON);
655 logmode |= LOGMODE_SYSLOG;
662 | (1 << SIGHUP ) /* why we ignore it? */
663 /* | (1 << SIGCHLD) - run_script does not use it anymore */
666 bb_error_msg("started: %s", bb_banner);
668 if (opts & FLAG_MONITOR) {
669 struct ifreq ifrequest;
670 set_ifreq_to_ifname(&ifrequest);
671 G.iface_exists = (network_ioctl(SIOCGIFINDEX, &ifrequest, NULL) == 0);
675 maybe_up_new_iface();
677 iface_status = detect_link();
678 if (iface_status == IFSTATUS_ERR)
680 iface_status_str = strstatus(iface_status);
682 if (opts & FLAG_MONITOR) {
683 bb_error_msg("interface %s",
684 G.iface_exists ? "exists"
685 : "doesn't exist, waiting");
687 /* else we assume it always exists, but don't mislead user
688 * by potentially lying that it really exists */
690 if (G.iface_exists) {
691 bb_error_msg("link is %s", iface_status_str);
694 if ((!(opts & FLAG_NO_STARTUP)
695 && iface_status == IFSTATUS_UP
697 || (opts & FLAG_INITIAL_DOWN)
699 if (run_script(iface_status_str) != 0)
704 netlink_pollfd[0].fd = netlink_fd;
705 netlink_pollfd[0].events = POLLIN;
708 int iface_status_old;
709 int iface_exists_old;
711 switch (bb_got_signal) {
724 if (poll(netlink_pollfd,
725 (opts & FLAG_MONITOR) ? 1 : 0,
731 bb_perror_msg("poll");
735 iface_status_old = iface_status;
736 iface_exists_old = G.iface_exists;
738 if ((opts & FLAG_MONITOR)
739 && (netlink_pollfd[0].revents & POLLIN)
741 G.iface_exists = check_existence_through_netlink();
742 if (G.iface_exists < 0) /* error */
744 if (iface_exists_old != G.iface_exists) {
745 bb_error_msg("interface %sappeared",
746 G.iface_exists ? "" : "dis");
748 maybe_up_new_iface();
752 /* note: if !G.iface_exists, returns DOWN */
753 iface_status = detect_link();
754 if (iface_status == IFSTATUS_ERR) {
755 if (!(opts & FLAG_MONITOR))
757 iface_status = IFSTATUS_DOWN;
759 iface_status_str = strstatus(iface_status);
761 if (iface_status_old != iface_status) {
762 bb_error_msg("link is %s", iface_status_str);
765 /* link restored its old status before
766 * we run script. don't run the script: */
769 delay_time = monotonic_sec();
770 if (iface_status == IFSTATUS_UP)
771 delay_time += G.delay_up;
772 if (iface_status == IFSTATUS_DOWN)
773 delay_time += G.delay_down;
779 if (delay_time && (int)(monotonic_sec() - delay_time) >= 0) {
781 if (run_script(iface_status_str) != 0)
787 if (!(opts & FLAG_NO_SHUTDOWN)
788 && (iface_status == IFSTATUS_UP
789 || (iface_status == IFSTATUS_DOWN && delay_time)
792 setenv(IFPLUGD_ENV_PREVIOUS, strstatus(iface_status), 1);
793 setenv(IFPLUGD_ENV_CURRENT, strstatus(-1), 1);
794 run_script("down\0up"); /* reusing string */
798 remove_pidfile(pidfile_name);
799 bb_error_msg_and_die("exiting");