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/ethtool.h>
13 #include <net/ethernet.h>
14 #include <linux/netlink.h>
15 #include <linux/rtnetlink.h>
16 #include <linux/sockios.h>
20 #include <linux/wireless.h>
23 TODO: describe compat status here.
25 One questionable point of the design is netlink usage:
27 We have 1 second timeout by default to poll the link status,
28 it is short enough so that there are no real benefits in
29 using netlink to get "instantaneous" interface creation/deletion
30 notifications. We can check for interface existence by just
31 doing some fast ioctl using its name.
33 Netlink code then can be just dropped (1k or more?)
37 #define IFPLUGD_ENV_PREVIOUS "IFPLUGD_PREVIOUS"
38 #define IFPLUGD_ENV_CURRENT "IFPLUGD_CURRENT"
41 FLAG_NO_AUTO = 1 << 0, // -a, Do not enable interface automatically
42 FLAG_NO_DAEMON = 1 << 1, // -n, Do not daemonize
43 FLAG_NO_SYSLOG = 1 << 2, // -s, Do not use syslog, use stderr instead
44 FLAG_IGNORE_FAIL = 1 << 3, // -f, Ignore detection failure, retry instead (failure is treated as DOWN)
45 FLAG_IGNORE_FAIL_POSITIVE = 1 << 4, // -F, Ignore detection failure, retry instead (failure is treated as UP)
46 FLAG_IFACE = 1 << 5, // -i, Specify ethernet interface
47 FLAG_RUN = 1 << 6, // -r, Specify program to execute
48 FLAG_IGNORE_RETVAL = 1 << 7, // -I, Don't exit on nonzero return value of program executed
49 FLAG_POLL_TIME = 1 << 8, // -t, Specify poll time in seconds
50 FLAG_DELAY_UP = 1 << 9, // -u, Specify delay for configuring interface
51 FLAG_DELAY_DOWN = 1 << 10, // -d, Specify delay for deconfiguring interface
52 FLAG_API_MODE = 1 << 11, // -m, Force API mode (mii, priv, ethtool, wlan, auto)
53 FLAG_NO_STARTUP = 1 << 12, // -p, Don't run script on daemon startup
54 FLAG_NO_SHUTDOWN = 1 << 13, // -q, Don't run script on daemon quit
55 FLAG_INITIAL_DOWN = 1 << 14, // -l, Run "down" script on startup if no cable is detected
56 FLAG_EXTRA_ARG = 1 << 15, // -x, Specify an extra argument for action script
57 FLAG_MONITOR = 1 << 16, // -M, Use interface monitoring
58 #if ENABLE_FEATURE_PIDFILE
59 FLAG_KILL = 1 << 17, // -k, Kill a running daemon
62 #if ENABLE_FEATURE_PIDFILE
63 # define OPTION_STR "+ansfFi:r:It:u:d:m:pqlx:Mk"
65 # define OPTION_STR "+ansfFi:r:It:u:d:m:pqlx:M"
77 enum { // interface status
83 enum { // constant fds
89 smallint iface_last_status;
90 smallint iface_exists;
92 /* Used in getopt32, must have sizeof == sizeof(int) */
99 const char *script_name;
100 const char *extra_arg;
102 smallint (*detect_link_func)(void);
103 smallint (*cached_detect_link_func)(void);
105 #define G (*ptr_to_globals)
106 #define INIT_G() do { \
107 SET_PTR_TO_GLOBALS(xzalloc(sizeof(G))); \
108 G.iface_last_status = -1; \
109 G.iface_exists = 1; \
114 G.script_name = "/etc/ifplugd/ifplugd.action"; \
118 static int run_script(const char *action)
123 bb_error_msg("executing '%s %s %s'", G.script_name, G.iface, action);
128 bb_perror_msg("fork");
134 execlp(G.script_name, G.script_name, G.iface, action, G.extra_arg, NULL);
135 bb_perror_msg_and_die("can't execute '%s'", G.script_name);
142 bb_error_msg("exit code: %u", r);
143 return (option_mask32 & FLAG_IGNORE_RETVAL) ? 0 : r;
147 struct fd_pair pipe_pair;
151 xpiped_pair(pipe_pair);
155 bb_perror_msg("fork");
161 xmove_fd(pipe_pair.wr, 1);
163 if (pipe_pair.rd > 2)
166 // umask(0022); // Set up a sane umask
168 execlp(G.script_name, G.script_name, G.iface, action, G.extra_arg, NULL);
176 if (bb_got_signal && bb_got_signal != SIGCHLD) {
177 bb_error_msg("killing child");
183 r = read(pipe_pair.rd, &buf[i], 1);
185 if (buf[i] == '\n' || i == sizeof(buf)-2 || r != 1) {
186 if (r == 1 && buf[i] != '\n')
192 bb_error_msg("client: %s", buf);
207 if (!WIFEXITED(r) || WEXITSTATUS(r) != 0) {
208 bb_error_msg("program execution failed, return value is %i",
210 return option_mask32 & FLAG_IGNORE_RETVAL ? 0 : WEXITSTATUS(r);
212 bb_error_msg("program executed successfully");
217 static int network_ioctl(int request, void* data)
219 return ioctl(ioctl_fd, request, data);
222 static void set_ifreq_to_ifname(struct ifreq *ifreq)
224 memset(ifreq, 0, sizeof(struct ifreq));
225 strncpy(ifreq->ifr_name, G.iface, IFNAMSIZ);
228 static const char *strstatus(int status)
230 if (status == IFSTATUS_ERR)
232 return "down\0up" + (status * 5);
235 static void up_iface(void)
237 struct ifreq ifrequest;
242 set_ifreq_to_ifname(&ifrequest);
243 if (network_ioctl(SIOCGIFFLAGS, &ifrequest) < 0) {
244 bb_perror_msg("can't %cet interface flags", 'g');
249 if (!(ifrequest.ifr_flags & IFF_UP)) {
250 ifrequest.ifr_flags |= IFF_UP;
251 /* Let user know we mess up with interface */
252 bb_error_msg("upping interface");
253 if (network_ioctl(SIOCSIFFLAGS, &ifrequest) < 0)
254 bb_perror_msg_and_die("can't %cet interface flags", 's');
257 #if 0 /* why do we mess with IP addr? It's not our business */
258 if (network_ioctl(SIOCGIFADDR, &ifrequest) < 0) {
259 bb_error_msg("can't get interface address");
260 } else if (ifrequest.ifr_addr.sa_family != AF_INET) {
261 bb_perror_msg("The interface is not IP-based");
263 ((struct sockaddr_in*)(&ifrequest.ifr_addr))->sin_addr.s_addr = INADDR_ANY;
264 if (network_ioctl(SIOCSIFADDR, &ifrequest) < 0)
265 bb_perror_msg("can't set interface address");
267 if (network_ioctl(SIOCGIFFLAGS, &ifrequest) < 0) {
268 bb_perror_msg("can't get interface flags");
274 static void maybe_up_new_iface(void)
276 if (!(option_mask32 & FLAG_NO_AUTO))
280 struct ifreq ifrequest;
281 struct ethtool_drvinfo driver_info;
283 set_ifreq_to_ifname(&ifrequest);
284 driver_info.cmd = ETHTOOL_GDRVINFO;
285 ifrequest.ifr_data = &driver_info;
286 if (network_ioctl(SIOCETHTOOL, &ifrequest) == 0) {
287 char buf[sizeof("/xx:xx:xx:xx:xx:xx")];
291 set_ifreq_to_ifname(&ifrequest);
292 if (network_ioctl(SIOCGIFHWADDR, &ifrequest) == 0) {
293 sprintf(buf, "/%02X:%02X:%02X:%02X:%02X:%02X",
294 (uint8_t)(ifrequest.ifr_hwaddr.sa_data[0]),
295 (uint8_t)(ifrequest.ifr_hwaddr.sa_data[1]),
296 (uint8_t)(ifrequest.ifr_hwaddr.sa_data[2]),
297 (uint8_t)(ifrequest.ifr_hwaddr.sa_data[3]),
298 (uint8_t)(ifrequest.ifr_hwaddr.sa_data[4]),
299 (uint8_t)(ifrequest.ifr_hwaddr.sa_data[5]));
302 bb_error_msg("Using interface %s%s with driver<%s> (version: %s)",
303 G.iface, buf, driver_info.driver, driver_info.version);
307 G.cached_detect_link_func = NULL;
310 static smallint detect_link_mii(void)
314 set_ifreq_to_ifname(&ifreq);
316 if (network_ioctl(SIOCGMIIPHY, &ifreq) < 0) {
317 bb_perror_msg("SIOCGMIIPHY failed");
321 ((unsigned short*)&ifreq.ifr_data)[1] = 1;
323 if (network_ioctl(SIOCGMIIREG, &ifreq) < 0) {
324 bb_perror_msg("SIOCGMIIREG failed");
328 return (((unsigned short*)&ifreq.ifr_data)[3] & 0x0004) ?
329 IFSTATUS_UP : IFSTATUS_DOWN;
332 static smallint detect_link_priv(void)
336 set_ifreq_to_ifname(&ifreq);
338 if (network_ioctl(SIOCDEVPRIVATE, &ifreq) < 0) {
339 bb_perror_msg("SIOCDEVPRIVATE failed");
343 ((unsigned short*) &ifreq.ifr_data)[1] = 1;
345 if (network_ioctl(SIOCDEVPRIVATE+1, &ifreq) < 0) {
346 bb_perror_msg("SIOCDEVPRIVATE+1 failed");
350 return (((unsigned short*)&ifreq.ifr_data)[3] & 0x0004) ?
351 IFSTATUS_UP : IFSTATUS_DOWN;
354 static smallint detect_link_ethtool(void)
357 struct ethtool_value edata;
359 set_ifreq_to_ifname(&ifreq);
361 edata.cmd = ETHTOOL_GLINK;
362 ifreq.ifr_data = &edata;
364 if (network_ioctl(SIOCETHTOOL, &ifreq) < 0) {
365 bb_perror_msg("ETHTOOL_GLINK failed");
369 return edata.data ? IFSTATUS_UP : IFSTATUS_DOWN;
372 static smallint detect_link_iff(void)
376 set_ifreq_to_ifname(&ifreq);
378 if (network_ioctl(SIOCGIFFLAGS, &ifreq) < 0) {
379 bb_perror_msg("SIOCGIFFLAGS failed");
383 return (ifreq.ifr_flags & IFF_RUNNING) ? IFSTATUS_UP : IFSTATUS_DOWN;
386 static smallint detect_link_wlan(void)
388 struct iwreq iwrequest;
389 uint8_t mac[ETH_ALEN];
391 memset(&iwrequest, 0, sizeof(struct iwreq));
392 strncpy(iwrequest.ifr_ifrn.ifrn_name, G.iface, IFNAMSIZ);
394 if (network_ioctl(SIOCGIWAP, &iwrequest) < 0) {
395 bb_perror_msg("SIOCGIWAP failed");
399 memcpy(mac, &(iwrequest.u.ap_addr.sa_data), ETH_ALEN);
401 if (mac[0] == 0xFF || mac[0] == 0x44 || mac[0] == 0x00) {
402 for (int i = 1; i < ETH_ALEN; ++i) {
403 if (mac[i] != mac[0])
406 return IFSTATUS_DOWN;
412 static smallint detect_link_auto(void)
415 smallint iface_status;
418 if (G.cached_detect_link_func) {
419 iface_status = G.cached_detect_link_func();
420 if (iface_status != IFSTATUS_ERR)
424 sv_logmode = logmode;
425 logmode = LOGMODE_NONE;
427 iface_status = detect_link_ethtool();
428 if (iface_status != IFSTATUS_ERR) {
429 G.cached_detect_link_func = detect_link_ethtool;
430 method = "SIOCETHTOOL";
432 logmode = sv_logmode;
433 bb_error_msg("using %s detection mode", method);
437 iface_status = detect_link_mii();
438 if (iface_status != IFSTATUS_ERR) {
439 G.cached_detect_link_func = detect_link_mii;
440 method = "SIOCGMIIPHY";
444 iface_status = detect_link_priv();
445 if (iface_status != IFSTATUS_ERR) {
446 G.cached_detect_link_func = detect_link_priv;
447 method = "SIOCDEVPRIVATE";
451 iface_status = detect_link_wlan();
452 if (iface_status != IFSTATUS_ERR) {
453 G.cached_detect_link_func = detect_link_wlan;
454 method = "wireless extension";
458 iface_status = detect_link_iff();
459 if (iface_status != IFSTATUS_ERR) {
460 G.cached_detect_link_func = detect_link_iff;
461 method = "IFF_RUNNING";
465 logmode = sv_logmode;
466 return iface_status; /* IFSTATUS_ERR */
469 static smallint detect_link(void)
474 return (option_mask32 & FLAG_MONITOR) ? IFSTATUS_DOWN : IFSTATUS_ERR;
477 /* Why? This behavior makes it hard to temporary down the iface.
478 * It makes a bit more sense to do only in maybe_up_new_iface.
479 * OTOH, maybe detect_link_wlan needs this. Then it should be done
482 if (!(option_mask32 & FLAG_NO_AUTO))
486 status = G.detect_link_func();
487 if (status == IFSTATUS_ERR) {
488 if (option_mask32 & FLAG_IGNORE_FAIL)
489 status = IFSTATUS_DOWN;
490 if (option_mask32 & FLAG_IGNORE_FAIL_POSITIVE)
491 status = IFSTATUS_UP;
494 if (status == IFSTATUS_ERR
495 && G.detect_link_func == detect_link_auto
497 bb_error_msg("failed to detect link status");
500 if (status != G.iface_last_status) {
501 //TODO: is it safe to repeatedly do this?
502 setenv(IFPLUGD_ENV_PREVIOUS, strstatus(G.iface_last_status), 1);
503 setenv(IFPLUGD_ENV_CURRENT, strstatus(status), 1);
504 G.iface_last_status = status;
510 static NOINLINE int check_existence_through_netlink(void)
515 struct nlmsghdr *mhdr;
518 bytes = recv(netlink_fd, &replybuf, sizeof(replybuf), MSG_DONTWAIT);
521 return G.iface_exists;
525 bb_perror_msg("netlink: recv");
529 mhdr = (struct nlmsghdr*)replybuf;
531 if (!NLMSG_OK(mhdr, bytes)
532 || bytes < sizeof(struct nlmsghdr)
533 || bytes < mhdr->nlmsg_len
535 bb_error_msg("netlink packet too small or truncated");
539 if (mhdr->nlmsg_type == RTM_NEWLINK || mhdr->nlmsg_type == RTM_DELLINK) {
541 struct ifinfomsg *imsg;
544 imsg = NLMSG_DATA(mhdr);
546 if (mhdr->nlmsg_len < NLMSG_LENGTH(sizeof(struct ifinfomsg))) {
547 bb_error_msg("netlink packet too small or truncated");
551 attr = (struct rtattr*)((char*)imsg + NLMSG_ALIGN(sizeof(struct ifinfomsg)));
552 attr_len = NLMSG_PAYLOAD(mhdr, sizeof(struct ifinfomsg));
554 while (RTA_OK(attr, attr_len)) {
555 if (attr->rta_type == IFLA_IFNAME) {
556 char ifname[IFNAMSIZ + 1];
557 int len = RTA_PAYLOAD(attr);
561 memcpy(ifname, RTA_DATA(attr), len);
562 if (strcmp(G.iface, ifname) == 0) {
563 G.iface_exists = (mhdr->nlmsg_type == RTM_NEWLINK);
566 attr = RTA_NEXT(attr, attr_len);
570 mhdr = NLMSG_NEXT(mhdr, bytes);
574 return G.iface_exists;
577 static NOINLINE int netlink_open(void)
580 struct sockaddr_nl addr;
582 fd = xsocket(PF_NETLINK, SOCK_DGRAM, NETLINK_ROUTE);
584 memset(&addr, 0, sizeof(addr));
585 addr.nl_family = AF_NETLINK;
586 addr.nl_groups = RTMGRP_LINK;
587 addr.nl_pid = getpid();
589 xbind(fd, (struct sockaddr*)&addr, sizeof(addr));
594 static NOINLINE pid_t read_pid(const char *filename)
599 len = open_read_close(filename, buf, 127);
602 /* returns ULONG_MAX on error => -1 */
603 return bb_strtoul(buf, NULL, 10);
608 int ifplugd_main(int argc, char **argv) MAIN_EXTERNALLY_VISIBLE;
609 int ifplugd_main(int argc UNUSED_PARAM, char **argv)
613 const char *iface_status_str;
614 struct pollfd netlink_pollfd[1];
616 #if ENABLE_FEATURE_PIDFILE
618 pid_t pid_from_pidfile;
623 opt_complementary = "t+:u+:d+";
624 opts = getopt32(argv, OPTION_STR,
625 &G.iface, &G.script_name, &G.poll_time, &G.delay_up,
626 &G.delay_down, &G.api_mode, &G.extra_arg);
628 applet_name = xasprintf("ifplugd(%s)", G.iface);
630 #if ENABLE_FEATURE_PIDFILE
631 pidfile_name = xasprintf(_PATH_VARRUN"ifplugd.%s.pid", G.iface);
632 pid_from_pidfile = read_pid(pidfile_name);
634 if (opts & FLAG_KILL) {
635 if (pid_from_pidfile > 0)
636 kill(pid_from_pidfile, SIGQUIT);
640 if (pid_from_pidfile > 0 && kill(pid_from_pidfile, 0) == 0)
641 bb_error_msg_and_die("daemon already running");
644 switch (G.api_mode[0]) {
646 G.detect_link_func = detect_link_auto;
649 G.detect_link_func = detect_link_ethtool;
652 G.detect_link_func = detect_link_mii;
655 G.detect_link_func = detect_link_priv;
658 G.detect_link_func = detect_link_wlan;
661 G.detect_link_func = detect_link_iff;
664 bb_error_msg_and_die("unknown API mode '%s'", G.api_mode);
667 if (!(opts & FLAG_NO_DAEMON))
668 bb_daemonize_or_rexec(DAEMON_CHDIR_ROOT, argv);
670 xmove_fd(xsocket(AF_INET, SOCK_DGRAM, 0), ioctl_fd);
671 if (opts & FLAG_MONITOR) {
672 xmove_fd(netlink_open(), netlink_fd);
675 write_pidfile(pidfile_name);
677 /* this can't be moved before socket creation */
678 if (!(opts & FLAG_NO_SYSLOG)) {
679 openlog(applet_name, 0, LOG_DAEMON);
680 logmode |= LOGMODE_SYSLOG;
687 | (1 << SIGHUP ) /* why we ignore it? */
688 /* | (1 << SIGCHLD) - run_script does not use it anymore */
691 bb_error_msg("started: %s", bb_banner);
693 if (opts & FLAG_MONITOR) {
694 struct ifreq ifrequest;
695 set_ifreq_to_ifname(&ifrequest);
696 G.iface_exists = (network_ioctl(SIOCGIFINDEX, &ifrequest) == 0);
700 maybe_up_new_iface();
702 iface_status = detect_link();
703 if (iface_status == IFSTATUS_ERR)
705 iface_status_str = strstatus(iface_status);
707 if (opts & FLAG_MONITOR) {
708 bb_error_msg("interface %s",
709 G.iface_exists ? "exists"
710 : "doesn't exist, waiting");
712 /* else we assume it always exists, but don't mislead user
713 * by potentially lying that it really exists */
715 if (G.iface_exists) {
716 bb_error_msg("link is %s", iface_status_str);
719 if ((!(opts & FLAG_NO_STARTUP)
720 && iface_status == IFSTATUS_UP
722 || (opts & FLAG_INITIAL_DOWN)
724 if (run_script(iface_status_str) != 0)
729 netlink_pollfd[0].fd = netlink_fd;
730 netlink_pollfd[0].events = POLLIN;
733 int iface_status_old;
734 int iface_exists_old;
736 switch (bb_got_signal) {
749 if (poll(netlink_pollfd,
750 (opts & FLAG_MONITOR) ? 1 : 0,
756 bb_perror_msg("poll");
760 iface_status_old = iface_status;
761 iface_exists_old = G.iface_exists;
763 if ((opts & FLAG_MONITOR)
764 && (netlink_pollfd[0].revents & POLLIN)
766 G.iface_exists = check_existence_through_netlink();
767 if (G.iface_exists < 0) /* error */
769 if (iface_exists_old != G.iface_exists) {
770 bb_error_msg("interface %sappeared",
771 G.iface_exists ? "" : "dis");
773 maybe_up_new_iface();
777 /* note: if !G.iface_exists, returns DOWN */
778 iface_status = detect_link();
779 if (iface_status == IFSTATUS_ERR) {
780 if (!(opts & FLAG_MONITOR))
782 iface_status = IFSTATUS_DOWN;
784 iface_status_str = strstatus(iface_status);
786 if (iface_status_old != iface_status) {
787 bb_error_msg("link is %s", iface_status_str);
790 /* link restored its old status before
791 * we run script. don't run the script: */
794 delay_time = monotonic_sec();
795 if (iface_status == IFSTATUS_UP)
796 delay_time += G.delay_up;
797 if (iface_status == IFSTATUS_DOWN)
798 delay_time += G.delay_down;
804 if (delay_time && (int)(monotonic_sec() - delay_time) >= 0) {
806 if (run_script(iface_status_str) != 0)
812 if (!(opts & FLAG_NO_SHUTDOWN)
813 && (iface_status == IFSTATUS_UP
814 || (iface_status == IFSTATUS_DOWN && delay_time)
817 setenv(IFPLUGD_ENV_PREVIOUS, strstatus(iface_status), 1);
818 setenv(IFPLUGD_ENV_CURRENT, strstatus(-1), 1);
819 run_script("down\0up"); /* reusing string */
823 remove_pidfile(pidfile_name);
824 bb_error_msg_and_die("exiting");