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 < 1000 - exited, >1000 - killed by sig (r-1000) */
136 r = wait4pid(spawn(argv));
138 bb_error_msg("exit code: %d", r);
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)
215 return ioctl(ioctl_fd, request, data);
218 static void set_ifreq_to_ifname(struct ifreq *ifreq)
220 memset(ifreq, 0, sizeof(struct ifreq));
221 strncpy_IFNAMSIZ(ifreq->ifr_name, G.iface);
224 static const char *strstatus(int status)
226 if (status == IFSTATUS_ERR)
228 return "down\0up" + (status * 5);
231 static void up_iface(void)
233 struct ifreq ifrequest;
238 set_ifreq_to_ifname(&ifrequest);
239 if (network_ioctl(SIOCGIFFLAGS, &ifrequest) < 0) {
240 bb_perror_msg("can't %cet interface flags", 'g');
245 if (!(ifrequest.ifr_flags & IFF_UP)) {
246 ifrequest.ifr_flags |= IFF_UP;
247 /* Let user know we mess up with interface */
248 bb_error_msg("upping interface");
249 if (network_ioctl(SIOCSIFFLAGS, &ifrequest) < 0)
250 bb_perror_msg_and_die("can't %cet interface flags", 's');
253 #if 0 /* why do we mess with IP addr? It's not our business */
254 if (network_ioctl(SIOCGIFADDR, &ifrequest) < 0) {
255 bb_error_msg("can't get interface address");
256 } else if (ifrequest.ifr_addr.sa_family != AF_INET) {
257 bb_perror_msg("the interface is not IP-based");
259 ((struct sockaddr_in*)(&ifrequest.ifr_addr))->sin_addr.s_addr = INADDR_ANY;
260 if (network_ioctl(SIOCSIFADDR, &ifrequest) < 0)
261 bb_perror_msg("can't set interface address");
263 if (network_ioctl(SIOCGIFFLAGS, &ifrequest) < 0) {
264 bb_perror_msg("can't get interface flags");
270 static void maybe_up_new_iface(void)
272 if (!(option_mask32 & FLAG_NO_AUTO))
276 struct ifreq ifrequest;
277 struct ethtool_drvinfo driver_info;
279 set_ifreq_to_ifname(&ifrequest);
280 driver_info.cmd = ETHTOOL_GDRVINFO;
281 ifrequest.ifr_data = &driver_info;
282 if (network_ioctl(SIOCETHTOOL, &ifrequest) == 0) {
283 char buf[sizeof("/xx:xx:xx:xx:xx:xx")];
287 set_ifreq_to_ifname(&ifrequest);
288 if (network_ioctl(SIOCGIFHWADDR, &ifrequest) == 0) {
289 sprintf(buf, "/%02X:%02X:%02X:%02X:%02X:%02X",
290 (uint8_t)(ifrequest.ifr_hwaddr.sa_data[0]),
291 (uint8_t)(ifrequest.ifr_hwaddr.sa_data[1]),
292 (uint8_t)(ifrequest.ifr_hwaddr.sa_data[2]),
293 (uint8_t)(ifrequest.ifr_hwaddr.sa_data[3]),
294 (uint8_t)(ifrequest.ifr_hwaddr.sa_data[4]),
295 (uint8_t)(ifrequest.ifr_hwaddr.sa_data[5]));
298 bb_error_msg("using interface %s%s with driver<%s> (version: %s)",
299 G.iface, buf, driver_info.driver, driver_info.version);
303 G.cached_detect_link_func = NULL;
306 static smallint detect_link_mii(void)
309 struct mii_ioctl_data *mii = (void *)&ifreq.ifr_data;
311 set_ifreq_to_ifname(&ifreq);
313 if (network_ioctl(SIOCGMIIPHY, &ifreq) < 0) {
314 bb_perror_msg("SIOCGMIIPHY failed");
320 if (network_ioctl(SIOCGMIIREG, &ifreq) < 0) {
321 bb_perror_msg("SIOCGMIIREG failed");
325 return (mii->val_out & 0x0004) ? IFSTATUS_UP : IFSTATUS_DOWN;
328 static smallint detect_link_priv(void)
331 struct mii_ioctl_data *mii = (void *)&ifreq.ifr_data;
333 set_ifreq_to_ifname(&ifreq);
335 if (network_ioctl(SIOCDEVPRIVATE, &ifreq) < 0) {
336 bb_perror_msg("SIOCDEVPRIVATE failed");
342 if (network_ioctl(SIOCDEVPRIVATE+1, &ifreq) < 0) {
343 bb_perror_msg("SIOCDEVPRIVATE+1 failed");
347 return (mii->val_out & 0x0004) ? IFSTATUS_UP : IFSTATUS_DOWN;
350 static smallint detect_link_ethtool(void)
353 struct ethtool_value edata;
355 set_ifreq_to_ifname(&ifreq);
357 edata.cmd = ETHTOOL_GLINK;
358 ifreq.ifr_data = (void*) &edata;
360 if (network_ioctl(SIOCETHTOOL, &ifreq) < 0) {
361 bb_perror_msg("ETHTOOL_GLINK failed");
365 return edata.data ? IFSTATUS_UP : IFSTATUS_DOWN;
368 static smallint detect_link_iff(void)
372 set_ifreq_to_ifname(&ifreq);
374 if (network_ioctl(SIOCGIFFLAGS, &ifreq) < 0) {
375 bb_perror_msg("SIOCGIFFLAGS failed");
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) < 0) {
391 bb_perror_msg("SIOCGIWAP failed");
395 memcpy(mac, &(iwrequest.u.ap_addr.sa_data), ETH_ALEN);
397 if (mac[0] == 0xFF || mac[0] == 0x44 || mac[0] == 0x00) {
398 for (int i = 1; i < ETH_ALEN; ++i) {
399 if (mac[i] != mac[0])
402 return IFSTATUS_DOWN;
408 static smallint detect_link_auto(void)
411 smallint iface_status;
414 if (G.cached_detect_link_func) {
415 iface_status = G.cached_detect_link_func();
416 if (iface_status != IFSTATUS_ERR)
420 sv_logmode = logmode;
421 logmode = LOGMODE_NONE;
423 iface_status = detect_link_ethtool();
424 if (iface_status != IFSTATUS_ERR) {
425 G.cached_detect_link_func = detect_link_ethtool;
426 method = "SIOCETHTOOL";
428 logmode = sv_logmode;
429 bb_error_msg("using %s detection mode", method);
433 iface_status = detect_link_mii();
434 if (iface_status != IFSTATUS_ERR) {
435 G.cached_detect_link_func = detect_link_mii;
436 method = "SIOCGMIIPHY";
440 iface_status = detect_link_priv();
441 if (iface_status != IFSTATUS_ERR) {
442 G.cached_detect_link_func = detect_link_priv;
443 method = "SIOCDEVPRIVATE";
447 iface_status = detect_link_wlan();
448 if (iface_status != IFSTATUS_ERR) {
449 G.cached_detect_link_func = detect_link_wlan;
450 method = "wireless extension";
454 iface_status = detect_link_iff();
455 if (iface_status != IFSTATUS_ERR) {
456 G.cached_detect_link_func = detect_link_iff;
457 method = "IFF_RUNNING";
461 logmode = sv_logmode;
462 return iface_status; /* IFSTATUS_ERR */
465 static smallint detect_link(void)
470 return (option_mask32 & FLAG_MONITOR) ? IFSTATUS_DOWN : IFSTATUS_ERR;
473 /* Why? This behavior makes it hard to temporary down the iface.
474 * It makes a bit more sense to do only in maybe_up_new_iface.
475 * OTOH, maybe detect_link_wlan needs this. Then it should be done
478 if (!(option_mask32 & FLAG_NO_AUTO))
482 status = G.detect_link_func();
483 if (status == IFSTATUS_ERR) {
484 if (option_mask32 & FLAG_IGNORE_FAIL)
485 status = IFSTATUS_DOWN;
486 if (option_mask32 & FLAG_IGNORE_FAIL_POSITIVE)
487 status = IFSTATUS_UP;
490 if (status == IFSTATUS_ERR
491 && G.detect_link_func == detect_link_auto
493 bb_error_msg("failed to detect link status");
496 if (status != G.iface_last_status) {
497 //TODO: is it safe to repeatedly do this?
498 setenv(IFPLUGD_ENV_PREVIOUS, strstatus(G.iface_last_status), 1);
499 setenv(IFPLUGD_ENV_CURRENT, strstatus(status), 1);
500 G.iface_last_status = status;
506 static NOINLINE int check_existence_through_netlink(void)
511 struct nlmsghdr *mhdr;
514 bytes = recv(netlink_fd, &replybuf, sizeof(replybuf), MSG_DONTWAIT);
517 return G.iface_exists;
521 bb_perror_msg("netlink: recv");
525 mhdr = (struct nlmsghdr*)replybuf;
527 if (!NLMSG_OK(mhdr, bytes)
528 || bytes < sizeof(struct nlmsghdr)
529 || bytes < mhdr->nlmsg_len
531 bb_error_msg("netlink packet too small or truncated");
535 if (mhdr->nlmsg_type == RTM_NEWLINK || mhdr->nlmsg_type == RTM_DELLINK) {
537 struct ifinfomsg *imsg;
540 imsg = NLMSG_DATA(mhdr);
542 if (mhdr->nlmsg_len < NLMSG_LENGTH(sizeof(struct ifinfomsg))) {
543 bb_error_msg("netlink packet too small or truncated");
547 attr = (struct rtattr*)((char*)imsg + NLMSG_ALIGN(sizeof(struct ifinfomsg)));
548 attr_len = NLMSG_PAYLOAD(mhdr, sizeof(struct ifinfomsg));
550 while (RTA_OK(attr, attr_len)) {
551 if (attr->rta_type == IFLA_IFNAME) {
552 char ifname[IFNAMSIZ + 1];
553 int len = RTA_PAYLOAD(attr);
557 memcpy(ifname, RTA_DATA(attr), len);
558 if (strcmp(G.iface, ifname) == 0) {
559 G.iface_exists = (mhdr->nlmsg_type == RTM_NEWLINK);
562 attr = RTA_NEXT(attr, attr_len);
566 mhdr = NLMSG_NEXT(mhdr, bytes);
570 return G.iface_exists;
573 static NOINLINE int netlink_open(void)
576 struct sockaddr_nl addr;
578 fd = xsocket(PF_NETLINK, SOCK_DGRAM, NETLINK_ROUTE);
580 memset(&addr, 0, sizeof(addr));
581 addr.nl_family = AF_NETLINK;
582 addr.nl_groups = RTMGRP_LINK;
583 addr.nl_pid = getpid();
585 xbind(fd, (struct sockaddr*)&addr, sizeof(addr));
590 #if ENABLE_FEATURE_PIDFILE
591 static NOINLINE pid_t read_pid(const char *filename)
596 len = open_read_close(filename, buf, 127);
599 /* returns ULONG_MAX on error => -1 */
600 return bb_strtoul(buf, NULL, 10);
606 int ifplugd_main(int argc, char **argv) MAIN_EXTERNALLY_VISIBLE;
607 int ifplugd_main(int argc UNUSED_PARAM, char **argv)
611 const char *iface_status_str;
612 struct pollfd netlink_pollfd[1];
614 #if ENABLE_FEATURE_PIDFILE
616 pid_t pid_from_pidfile;
621 opt_complementary = "t+:u+:d+";
622 opts = getopt32(argv, OPTION_STR,
623 &G.iface, &G.script_name, &G.poll_time, &G.delay_up,
624 &G.delay_down, &G.api_mode, &G.extra_arg);
627 applet_name = xasprintf("ifplugd(%s)", G.iface);
629 #if ENABLE_FEATURE_PIDFILE
630 pidfile_name = xasprintf(_PATH_VARRUN"ifplugd.%s.pid", G.iface);
631 pid_from_pidfile = read_pid(pidfile_name);
633 if (opts & FLAG_KILL) {
634 if (pid_from_pidfile > 0)
635 kill(pid_from_pidfile, SIGQUIT);
639 if (pid_from_pidfile > 0 && kill(pid_from_pidfile, 0) == 0)
640 bb_error_msg_and_die("daemon already running");
643 switch (G.api_mode[0]) {
645 G.detect_link_func = detect_link_auto;
648 G.detect_link_func = detect_link_ethtool;
651 G.detect_link_func = detect_link_mii;
654 G.detect_link_func = detect_link_priv;
657 G.detect_link_func = detect_link_wlan;
660 G.detect_link_func = detect_link_iff;
663 bb_error_msg_and_die("unknown API mode '%s'", G.api_mode);
666 if (!(opts & FLAG_NO_DAEMON))
667 bb_daemonize_or_rexec(DAEMON_CHDIR_ROOT, argv);
669 xmove_fd(xsocket(AF_INET, SOCK_DGRAM, 0), ioctl_fd);
670 if (opts & FLAG_MONITOR) {
671 xmove_fd(netlink_open(), netlink_fd);
674 write_pidfile(pidfile_name);
676 /* this can't be moved before socket creation */
677 if (!(opts & FLAG_NO_SYSLOG)) {
678 openlog(applet_name, 0, LOG_DAEMON);
679 logmode |= LOGMODE_SYSLOG;
686 | (1 << SIGHUP ) /* why we ignore it? */
687 /* | (1 << SIGCHLD) - run_script does not use it anymore */
690 bb_error_msg("started: %s", bb_banner);
692 if (opts & FLAG_MONITOR) {
693 struct ifreq ifrequest;
694 set_ifreq_to_ifname(&ifrequest);
695 G.iface_exists = (network_ioctl(SIOCGIFINDEX, &ifrequest) == 0);
699 maybe_up_new_iface();
701 iface_status = detect_link();
702 if (iface_status == IFSTATUS_ERR)
704 iface_status_str = strstatus(iface_status);
706 if (opts & FLAG_MONITOR) {
707 bb_error_msg("interface %s",
708 G.iface_exists ? "exists"
709 : "doesn't exist, waiting");
711 /* else we assume it always exists, but don't mislead user
712 * by potentially lying that it really exists */
714 if (G.iface_exists) {
715 bb_error_msg("link is %s", iface_status_str);
718 if ((!(opts & FLAG_NO_STARTUP)
719 && iface_status == IFSTATUS_UP
721 || (opts & FLAG_INITIAL_DOWN)
723 if (run_script(iface_status_str) != 0)
728 netlink_pollfd[0].fd = netlink_fd;
729 netlink_pollfd[0].events = POLLIN;
732 int iface_status_old;
733 int iface_exists_old;
735 switch (bb_got_signal) {
748 if (poll(netlink_pollfd,
749 (opts & FLAG_MONITOR) ? 1 : 0,
755 bb_perror_msg("poll");
759 iface_status_old = iface_status;
760 iface_exists_old = G.iface_exists;
762 if ((opts & FLAG_MONITOR)
763 && (netlink_pollfd[0].revents & POLLIN)
765 G.iface_exists = check_existence_through_netlink();
766 if (G.iface_exists < 0) /* error */
768 if (iface_exists_old != G.iface_exists) {
769 bb_error_msg("interface %sappeared",
770 G.iface_exists ? "" : "dis");
772 maybe_up_new_iface();
776 /* note: if !G.iface_exists, returns DOWN */
777 iface_status = detect_link();
778 if (iface_status == IFSTATUS_ERR) {
779 if (!(opts & FLAG_MONITOR))
781 iface_status = IFSTATUS_DOWN;
783 iface_status_str = strstatus(iface_status);
785 if (iface_status_old != iface_status) {
786 bb_error_msg("link is %s", iface_status_str);
789 /* link restored its old status before
790 * we run script. don't run the script: */
793 delay_time = monotonic_sec();
794 if (iface_status == IFSTATUS_UP)
795 delay_time += G.delay_up;
796 if (iface_status == IFSTATUS_DOWN)
797 delay_time += G.delay_down;
803 if (delay_time && (int)(monotonic_sec() - delay_time) >= 0) {
805 if (run_script(iface_status_str) != 0)
811 if (!(opts & FLAG_NO_SHUTDOWN)
812 && (iface_status == IFSTATUS_UP
813 || (iface_status == IFSTATUS_DOWN && delay_time)
816 setenv(IFPLUGD_ENV_PREVIOUS, strstatus(iface_status), 1);
817 setenv(IFPLUGD_ENV_CURRENT, strstatus(-1), 1);
818 run_script("down\0up"); /* reusing string */
822 remove_pidfile(pidfile_name);
823 bb_error_msg_and_die("exiting");