026ff1cc805b82d2bc142fdb3779bb2102eb9574
[oweals/busybox.git] / networking / ifplugd.c
1 /* vi: set sw=4 ts=4: */
2 /*
3  * ifplugd for busybox, based on ifplugd 0.28 (written by Lennart Poettering).
4  *
5  * Copyright (C) 2009 Maksym Kryzhanovskyy <xmaks@email.cz>
6  *
7  * Licensed under GPLv2 or later, see file LICENSE in this source tree.
8  */
9 //config:config IFPLUGD
10 //config:       bool "ifplugd (10 kb)"
11 //config:       default y
12 //config:       select PLATFORM_LINUX
13 //config:       help
14 //config:       Network interface plug detection daemon.
15
16 //applet:IF_IFPLUGD(APPLET(ifplugd, BB_DIR_USR_SBIN, BB_SUID_DROP))
17
18 //kbuild:lib-$(CONFIG_IFPLUGD) += ifplugd.o
19
20 //usage:#define ifplugd_trivial_usage
21 //usage:       "[OPTIONS]"
22 //usage:#define ifplugd_full_usage "\n\n"
23 //usage:       "Network interface plug detection daemon\n"
24 //usage:     "\n        -n              Don't daemonize"
25 //usage:     "\n        -s              Don't log to syslog"
26 //usage:     "\n        -i IFACE        Interface"
27 //usage:     "\n        -f/-F           Treat link detection error as link down/link up"
28 //usage:     "\n                        (otherwise exit on error)"
29 //usage:     "\n        -a              Don't up interface at each link probe"
30 //usage:     "\n        -M              Monitor creation/destruction of interface"
31 //usage:     "\n                        (otherwise it must exist)"
32 //usage:     "\n        -r PROG         Script to run"
33 //usage:     "\n        -x ARG          Extra argument for script"
34 //usage:     "\n        -I              Don't exit on nonzero exit code from script"
35 //usage:     "\n        -p              Don't run \"up\" script on startup"
36 //usage:     "\n        -q              Don't run \"down\" script on exit"
37 //usage:     "\n        -l              Always run script on startup"
38 //usage:     "\n        -t SECS         Poll time in seconds"
39 //usage:     "\n        -u SECS         Delay before running script after link up"
40 //usage:     "\n        -d SECS         Delay after link down"
41 //usage:     "\n        -m MODE         API mode (mii, priv, ethtool, wlan, iff, auto)"
42 //usage:     "\n        -k              Kill running daemon"
43
44 #include "libbb.h"
45
46 #include "fix_u32.h"
47 #include <linux/if.h>
48 #include <linux/mii.h>
49 #include <linux/ethtool.h>
50 #ifdef HAVE_NET_ETHERNET_H
51 /* musl breakage:
52  * In file included from /usr/include/net/ethernet.h:10,
53  *                  from networking/ifplugd.c:41:
54  * /usr/include/netinet/if_ether.h:96: error: redefinition of 'struct ethhdr'
55  *
56  * Build succeeds without it on musl. Commented it out.
57  * If on your system you need it, consider removing <linux/ethtool.h>
58  * and copy-pasting its definitions here (<linux/ethtool.h> is what pulls in
59  * conflicting definition of struct ethhdr on musl).
60  */
61 /* # include <net/ethernet.h> */
62 #endif
63 #include <linux/netlink.h>
64 #include <linux/rtnetlink.h>
65 #include <linux/sockios.h>
66 #include <syslog.h>
67
68 #define __user
69 #include <linux/wireless.h>
70
71 #ifndef ETH_ALEN
72 # define ETH_ALEN  6
73 #endif
74
75 /*
76 From initial port to busybox, removed most of the redundancy by
77 converting implementation of a polymorphic interface to the strict
78 functional style. The main role is run a script when link state
79 changed, other activities like audio signal or detailed reports
80 are on the script itself.
81
82 One questionable point of the design is netlink usage:
83
84 We have 1 second timeout by default to poll the link status,
85 it is short enough so that there are no real benefits in
86 using netlink to get "instantaneous" interface creation/deletion
87 notifications. We can check for interface existence by just
88 doing some fast ioctl using its name.
89
90 Netlink code then can be just dropped (1k or more?)
91 */
92
93
94 #define IFPLUGD_ENV_PREVIOUS "IFPLUGD_PREVIOUS"
95 #define IFPLUGD_ENV_CURRENT "IFPLUGD_CURRENT"
96
97 enum {
98         FLAG_NO_AUTO                    = 1 <<  0, // -a, Do not enable interface automatically
99         FLAG_NO_DAEMON                  = 1 <<  1, // -n, Do not daemonize
100         FLAG_NO_SYSLOG                  = 1 <<  2, // -s, Do not use syslog, use stderr instead
101         FLAG_IGNORE_FAIL                = 1 <<  3, // -f, Ignore detection failure, retry instead (failure is treated as DOWN)
102         FLAG_IGNORE_FAIL_POSITIVE       = 1 <<  4, // -F, Ignore detection failure, retry instead (failure is treated as UP)
103         FLAG_IFACE                      = 1 <<  5, // -i, Specify ethernet interface
104         FLAG_RUN                        = 1 <<  6, // -r, Specify program to execute
105         FLAG_IGNORE_RETVAL              = 1 <<  7, // -I, Don't exit on nonzero return value of program executed
106         FLAG_POLL_TIME                  = 1 <<  8, // -t, Specify poll time in seconds
107         FLAG_DELAY_UP                   = 1 <<  9, // -u, Specify delay for configuring interface
108         FLAG_DELAY_DOWN                 = 1 << 10, // -d, Specify delay for deconfiguring interface
109         FLAG_API_MODE                   = 1 << 11, // -m, Force API mode (mii, priv, ethtool, wlan, auto)
110         FLAG_NO_STARTUP                 = 1 << 12, // -p, Don't run script on daemon startup
111         FLAG_NO_SHUTDOWN                = 1 << 13, // -q, Don't run script on daemon quit
112         FLAG_INITIAL_DOWN               = 1 << 14, // -l, Run "down" script on startup if no cable is detected
113         FLAG_EXTRA_ARG                  = 1 << 15, // -x, Specify an extra argument for action script
114         FLAG_MONITOR                    = 1 << 16, // -M, Use interface monitoring
115 #if ENABLE_FEATURE_PIDFILE
116         FLAG_KILL                       = 1 << 17, // -k, Kill a running daemon
117 #endif
118 };
119 #if ENABLE_FEATURE_PIDFILE
120 # define OPTION_STR "+ansfFi:r:It:+u:+d:+m:pqlx:Mk"
121 #else
122 # define OPTION_STR "+ansfFi:r:It:+u:+d:+m:pqlx:M"
123 #endif
124
125 enum { // interface status
126         IFSTATUS_ERR = -1,
127         IFSTATUS_DOWN = 0,
128         IFSTATUS_UP = 1,
129 };
130
131 enum { // constant fds
132         ioctl_fd = 3,
133         netlink_fd = 4,
134 };
135
136 struct globals {
137         smallint iface_last_status;
138         smallint iface_prev_status;
139         smallint iface_exists;
140         smallint api_method_num;
141
142         /* Used in getopt32, must have sizeof == sizeof(int) */
143         unsigned poll_time;
144         unsigned delay_up;
145         unsigned delay_down;
146
147         const char *iface;
148         const char *api_mode;
149         const char *script_name;
150         const char *extra_arg;
151 };
152 #define G (*ptr_to_globals)
153 #define INIT_G() do { \
154         SET_PTR_TO_GLOBALS(xzalloc(sizeof(G))); \
155         G.iface_last_status = -1; \
156         G.iface_exists   = 1; \
157         G.poll_time      = 1; \
158         G.delay_down     = 5; \
159         G.iface          = "eth0"; \
160         G.api_mode       = "a"; \
161         G.script_name    = "/etc/ifplugd/ifplugd.action"; \
162 } while (0)
163
164
165 /* Utility routines */
166
167 static void set_ifreq_to_ifname(struct ifreq *ifreq)
168 {
169         memset(ifreq, 0, sizeof(struct ifreq));
170         strncpy_IFNAMSIZ(ifreq->ifr_name, G.iface);
171 }
172
173 static int network_ioctl(int request, void* data, const char *errmsg)
174 {
175         int r = ioctl(ioctl_fd, request, data);
176         if (r < 0 && errmsg)
177                 bb_perror_msg("%s failed", errmsg);
178         return r;
179 }
180
181 /* Link detection routines and table */
182
183 static smallint detect_link_mii(void)
184 {
185         /* char buffer instead of bona-fide struct avoids aliasing warning */
186         char buf[sizeof(struct ifreq)];
187         struct ifreq *const ifreq = (void *)buf;
188
189         struct mii_ioctl_data *mii = (void *)&ifreq->ifr_data;
190
191         set_ifreq_to_ifname(ifreq);
192
193         if (network_ioctl(SIOCGMIIPHY, ifreq, "SIOCGMIIPHY") < 0) {
194                 return IFSTATUS_ERR;
195         }
196
197         mii->reg_num = 1;
198
199         if (network_ioctl(SIOCGMIIREG, ifreq, "SIOCGMIIREG") < 0) {
200                 return IFSTATUS_ERR;
201         }
202
203         return (mii->val_out & 0x0004) ? IFSTATUS_UP : IFSTATUS_DOWN;
204 }
205
206 static smallint detect_link_priv(void)
207 {
208         /* char buffer instead of bona-fide struct avoids aliasing warning */
209         char buf[sizeof(struct ifreq)];
210         struct ifreq *const ifreq = (void *)buf;
211
212         struct mii_ioctl_data *mii = (void *)&ifreq->ifr_data;
213
214         set_ifreq_to_ifname(ifreq);
215
216         if (network_ioctl(SIOCDEVPRIVATE, ifreq, "SIOCDEVPRIVATE") < 0) {
217                 return IFSTATUS_ERR;
218         }
219
220         mii->reg_num = 1;
221
222         if (network_ioctl(SIOCDEVPRIVATE+1, ifreq, "SIOCDEVPRIVATE+1") < 0) {
223                 return IFSTATUS_ERR;
224         }
225
226         return (mii->val_out & 0x0004) ? IFSTATUS_UP : IFSTATUS_DOWN;
227 }
228
229 static smallint detect_link_ethtool(void)
230 {
231         struct ifreq ifreq;
232         struct ethtool_value edata;
233
234         set_ifreq_to_ifname(&ifreq);
235
236         edata.cmd = ETHTOOL_GLINK;
237         ifreq.ifr_data = (void*) &edata;
238
239         if (network_ioctl(SIOCETHTOOL, &ifreq, "ETHTOOL_GLINK") < 0) {
240                 return IFSTATUS_ERR;
241         }
242
243         return edata.data ? IFSTATUS_UP : IFSTATUS_DOWN;
244 }
245
246 static smallint detect_link_iff(void)
247 {
248         struct ifreq ifreq;
249
250         set_ifreq_to_ifname(&ifreq);
251
252         if (network_ioctl(SIOCGIFFLAGS, &ifreq, "SIOCGIFFLAGS") < 0) {
253                 return IFSTATUS_ERR;
254         }
255
256         /* If IFF_UP is not set (interface is down), IFF_RUNNING is never set
257          * regardless of link status. Simply continue to report last status -
258          * no point in reporting spurious link downs if interface is disabled
259          * by admin. When/if it will be brought up,
260          * we'll report real link status.
261          */
262         if (!(ifreq.ifr_flags & IFF_UP) && G.iface_last_status != IFSTATUS_ERR)
263                 return G.iface_last_status;
264
265         return (ifreq.ifr_flags & IFF_RUNNING) ? IFSTATUS_UP : IFSTATUS_DOWN;
266 }
267
268 static smallint detect_link_wlan(void)
269 {
270         int i;
271         struct iwreq iwrequest;
272         uint8_t mac[ETH_ALEN];
273
274         memset(&iwrequest, 0, sizeof(iwrequest));
275         strncpy_IFNAMSIZ(iwrequest.ifr_ifrn.ifrn_name, G.iface);
276
277         if (network_ioctl(SIOCGIWAP, &iwrequest, "SIOCGIWAP") < 0) {
278                 return IFSTATUS_ERR;
279         }
280
281         memcpy(mac, &iwrequest.u.ap_addr.sa_data, ETH_ALEN);
282
283         if (mac[0] == 0xFF || mac[0] == 0x44 || mac[0] == 0x00) {
284                 for (i = 1; i < ETH_ALEN; ++i) {
285                         if (mac[i] != mac[0])
286                                 return IFSTATUS_UP;
287                 }
288                 return IFSTATUS_DOWN;
289         }
290
291         return IFSTATUS_UP;
292 }
293
294 enum { // api mode
295         API_ETHTOOL, // 'e'
296         API_MII,     // 'm'
297         API_PRIVATE, // 'p'
298         API_WLAN,    // 'w'
299         API_IFF,     // 'i'
300         API_AUTO,    // 'a'
301 };
302
303 static const char api_modes[] ALIGN1 = "empwia";
304
305 static const struct {
306         const char *name;
307         smallint (*func)(void);
308 } method_table[] = {
309         { "SIOCETHTOOL"       , &detect_link_ethtool },
310         { "SIOCGMIIPHY"       , &detect_link_mii     },
311         { "SIOCDEVPRIVATE"    , &detect_link_priv    },
312         { "wireless extension", &detect_link_wlan    },
313         { "IFF_RUNNING"       , &detect_link_iff     },
314 };
315
316 static const char *strstatus(int status)
317 {
318         if (status == IFSTATUS_ERR)
319                 return "error";
320         return "down\0up" + (status * 5);
321 }
322
323 static int run_script(const char *action)
324 {
325         char *env_PREVIOUS, *env_CURRENT;
326         char *argv[5];
327         int r;
328
329         bb_error_msg("executing '%s %s %s'", G.script_name, G.iface, action);
330
331         argv[0] = (char*) G.script_name;
332         argv[1] = (char*) G.iface;
333         argv[2] = (char*) action;
334         argv[3] = (char*) G.extra_arg;
335         argv[4] = NULL;
336
337         env_PREVIOUS = xasprintf("%s=%s", IFPLUGD_ENV_PREVIOUS, strstatus(G.iface_prev_status));
338         putenv(env_PREVIOUS);
339         env_CURRENT = xasprintf("%s=%s", IFPLUGD_ENV_CURRENT, strstatus(G.iface_last_status));
340         putenv(env_CURRENT);
341
342         /* r < 0 - can't exec, 0 <= r < 0x180 - exited, >=0x180 - killed by sig (r-0x180) */
343         r = spawn_and_wait(argv);
344
345         bb_unsetenv_and_free(env_PREVIOUS);
346         bb_unsetenv_and_free(env_CURRENT);
347
348         bb_error_msg("exit code: %d", r & 0xff);
349         return (option_mask32 & FLAG_IGNORE_RETVAL) ? 0 : r;
350 }
351
352 static void up_iface(void)
353 {
354         struct ifreq ifrequest;
355
356         if (!G.iface_exists)
357                 return;
358
359         set_ifreq_to_ifname(&ifrequest);
360         if (network_ioctl(SIOCGIFFLAGS, &ifrequest, "getting interface flags") < 0) {
361                 G.iface_exists = 0;
362                 return;
363         }
364
365         if (!(ifrequest.ifr_flags & IFF_UP)) {
366                 ifrequest.ifr_flags |= IFF_UP;
367                 /* Let user know we mess up with interface */
368                 bb_error_msg("upping interface");
369                 if (network_ioctl(SIOCSIFFLAGS, &ifrequest, "setting interface flags") < 0) {
370                         if (errno != ENODEV && errno != EADDRNOTAVAIL)
371                                 xfunc_die();
372                         G.iface_exists = 0;
373                         return;
374                 }
375         }
376
377 #if 0 /* why do we mess with IP addr? It's not our business */
378         if (network_ioctl(SIOCGIFADDR, &ifrequest, "can't get interface address") < 0) {
379         } else if (ifrequest.ifr_addr.sa_family != AF_INET) {
380                 bb_perror_msg("the interface is not IP-based");
381         } else {
382                 ((struct sockaddr_in*)(&ifrequest.ifr_addr))->sin_addr.s_addr = INADDR_ANY;
383                 network_ioctl(SIOCSIFADDR, &ifrequest, "can't set interface address");
384         }
385         network_ioctl(SIOCGIFFLAGS, &ifrequest, "can't get interface flags");
386 #endif
387 }
388
389 static void maybe_up_new_iface(void)
390 {
391         if (!(option_mask32 & FLAG_NO_AUTO))
392                 up_iface();
393
394 #if 0 /* bloat */
395         struct ifreq ifrequest;
396         struct ethtool_drvinfo driver_info;
397
398         set_ifreq_to_ifname(&ifrequest);
399         driver_info.cmd = ETHTOOL_GDRVINFO;
400         ifrequest.ifr_data = &driver_info;
401         if (network_ioctl(SIOCETHTOOL, &ifrequest, NULL) == 0) {
402                 char buf[sizeof("/xx:xx:xx:xx:xx:xx")];
403
404                 /* Get MAC */
405                 buf[0] = '\0';
406                 set_ifreq_to_ifname(&ifrequest);
407                 if (network_ioctl(SIOCGIFHWADDR, &ifrequest, NULL) == 0) {
408                         sprintf(buf, "/%02X:%02X:%02X:%02X:%02X:%02X",
409                                 (uint8_t)(ifrequest.ifr_hwaddr.sa_data[0]),
410                                 (uint8_t)(ifrequest.ifr_hwaddr.sa_data[1]),
411                                 (uint8_t)(ifrequest.ifr_hwaddr.sa_data[2]),
412                                 (uint8_t)(ifrequest.ifr_hwaddr.sa_data[3]),
413                                 (uint8_t)(ifrequest.ifr_hwaddr.sa_data[4]),
414                                 (uint8_t)(ifrequest.ifr_hwaddr.sa_data[5]));
415                 }
416
417                 bb_error_msg("using interface %s%s with driver<%s> (version: %s)",
418                         G.iface, buf, driver_info.driver, driver_info.version);
419         }
420 #endif
421         if (G.api_mode[0] == 'a')
422                 G.api_method_num = API_AUTO;
423 }
424
425 static smallint detect_link(void)
426 {
427         smallint status;
428
429         if (!G.iface_exists)
430                 return (option_mask32 & FLAG_MONITOR) ? IFSTATUS_DOWN : IFSTATUS_ERR;
431
432         /* Some drivers can't detect link status when the interface is down.
433          * I imagine detect_link_iff() is the most vulnerable.
434          * That's why -a "noauto" in an option, not a hardwired behavior.
435          */
436         if (!(option_mask32 & FLAG_NO_AUTO))
437                 up_iface();
438
439         if (G.api_method_num == API_AUTO) {
440                 int i;
441                 smallint sv_logmode;
442
443                 sv_logmode = logmode;
444                 for (i = 0; i < ARRAY_SIZE(method_table); i++) {
445                         logmode = LOGMODE_NONE;
446                         status = method_table[i].func();
447                         logmode = sv_logmode;
448                         if (status != IFSTATUS_ERR) {
449                                 G.api_method_num = i;
450                                 bb_error_msg("using %s detection mode", method_table[i].name);
451                                 break;
452                         }
453                 }
454         } else {
455                 status = method_table[G.api_method_num].func();
456         }
457
458         if (status == IFSTATUS_ERR) {
459                 if (option_mask32 & FLAG_IGNORE_FAIL)
460                         status = IFSTATUS_DOWN;
461                 else if (option_mask32 & FLAG_IGNORE_FAIL_POSITIVE)
462                         status = IFSTATUS_UP;
463                 else if (G.api_mode[0] == 'a')
464                         bb_error_msg("can't detect link status");
465         }
466
467         if (status != G.iface_last_status) {
468                 G.iface_prev_status = G.iface_last_status;
469                 G.iface_last_status = status;
470         }
471
472         return status;
473 }
474
475 static NOINLINE int check_existence_through_netlink(void)
476 {
477         int iface_len;
478         /* Buffer was 1K, but on linux-3.9.9 it was reported to be too small.
479          * netlink.h: "limit to 8K to avoid MSG_TRUNC when PAGE_SIZE is very large".
480          * Note: on error returns (-1) we exit, no need to free replybuf.
481          */
482         enum { BUF_SIZE = 8 * 1024 };
483         char *replybuf = xmalloc(BUF_SIZE);
484
485         iface_len = strlen(G.iface);
486         while (1) {
487                 struct nlmsghdr *mhdr;
488                 ssize_t bytes;
489
490                 bytes = recv(netlink_fd, replybuf, BUF_SIZE, MSG_DONTWAIT);
491                 if (bytes < 0) {
492                         if (errno == EAGAIN)
493                                 goto ret;
494                         if (errno == EINTR)
495                                 continue;
496                         bb_perror_msg("netlink: recv");
497                         return -1;
498                 }
499
500                 mhdr = (struct nlmsghdr*)replybuf;
501                 while (bytes > 0) {
502                         if (!NLMSG_OK(mhdr, bytes)) {
503                                 bb_error_msg("netlink packet too small or truncated");
504                                 return -1;
505                         }
506
507                         if (mhdr->nlmsg_type == RTM_NEWLINK || mhdr->nlmsg_type == RTM_DELLINK) {
508                                 struct rtattr *attr;
509                                 int attr_len;
510
511                                 if (mhdr->nlmsg_len < NLMSG_LENGTH(sizeof(struct ifinfomsg))) {
512                                         bb_error_msg("netlink packet too small or truncated");
513                                         return -1;
514                                 }
515
516                                 attr = IFLA_RTA(NLMSG_DATA(mhdr));
517                                 attr_len = IFLA_PAYLOAD(mhdr);
518
519                                 while (RTA_OK(attr, attr_len)) {
520                                         if (attr->rta_type == IFLA_IFNAME) {
521                                                 int len = RTA_PAYLOAD(attr);
522                                                 if (len > IFNAMSIZ)
523                                                         len = IFNAMSIZ;
524                                                 if (iface_len <= len
525                                                  && strncmp(G.iface, RTA_DATA(attr), len) == 0
526                                                 ) {
527                                                         G.iface_exists = (mhdr->nlmsg_type == RTM_NEWLINK);
528                                                 }
529                                         }
530                                         attr = RTA_NEXT(attr, attr_len);
531                                 }
532                         }
533
534                         mhdr = NLMSG_NEXT(mhdr, bytes);
535                 }
536         }
537
538  ret:
539         free(replybuf);
540         return G.iface_exists;
541 }
542
543 #if ENABLE_FEATURE_PIDFILE
544 static NOINLINE pid_t read_pid(const char *filename)
545 {
546         int len;
547         char buf[128];
548
549         len = open_read_close(filename, buf, 127);
550         if (len > 0) {
551                 buf[len] = '\0';
552                 /* returns ULONG_MAX on error => -1 */
553                 return bb_strtoul(buf, NULL, 10);
554         }
555         return 0;
556 }
557 #endif
558
559 int ifplugd_main(int argc, char **argv) MAIN_EXTERNALLY_VISIBLE;
560 int ifplugd_main(int argc UNUSED_PARAM, char **argv)
561 {
562         int iface_status;
563         int delay_time;
564         const char *iface_status_str;
565         struct pollfd netlink_pollfd[1];
566         unsigned opts;
567         const char *api_mode_found;
568 #if ENABLE_FEATURE_PIDFILE
569         char *pidfile_name;
570         pid_t pid_from_pidfile;
571 #endif
572
573         INIT_G();
574
575         opts = getopt32(argv, OPTION_STR,
576                 &G.iface, &G.script_name, &G.poll_time, &G.delay_up,
577                 &G.delay_down, &G.api_mode, &G.extra_arg);
578         G.poll_time *= 1000;
579
580         applet_name = xasprintf("ifplugd(%s)", G.iface);
581
582 #if ENABLE_FEATURE_PIDFILE
583         pidfile_name = xasprintf(CONFIG_PID_FILE_PATH "/ifplugd.%s.pid", G.iface);
584         pid_from_pidfile = read_pid(pidfile_name);
585
586         if (opts & FLAG_KILL) {
587                 if (pid_from_pidfile > 0)
588                         /* Upstream tool use SIGINT for -k */
589                         kill(pid_from_pidfile, SIGINT);
590                 return EXIT_SUCCESS;
591         }
592
593         if (pid_from_pidfile > 0 && kill(pid_from_pidfile, 0) == 0)
594                 bb_error_msg_and_die("daemon already running");
595 #endif
596
597         api_mode_found = strchr(api_modes, G.api_mode[0]);
598         if (!api_mode_found)
599                 bb_error_msg_and_die("unknown API mode '%s'", G.api_mode);
600         G.api_method_num = api_mode_found - api_modes;
601
602         if (!(opts & FLAG_NO_DAEMON))
603                 bb_daemonize_or_rexec(DAEMON_CHDIR_ROOT, argv);
604
605         xmove_fd(xsocket(AF_INET, SOCK_DGRAM, 0), ioctl_fd);
606         if (opts & FLAG_MONITOR) {
607                 struct sockaddr_nl addr;
608                 int fd = xsocket(PF_NETLINK, SOCK_DGRAM, NETLINK_ROUTE);
609
610                 memset(&addr, 0, sizeof(addr));
611                 addr.nl_family = AF_NETLINK;
612                 addr.nl_groups = RTMGRP_LINK;
613                 addr.nl_pid = getpid();
614
615                 xbind(fd, (struct sockaddr*)&addr, sizeof(addr));
616                 xmove_fd(fd, netlink_fd);
617         }
618
619         write_pidfile(pidfile_name);
620
621         /* this can't be moved before socket creation */
622         if (!(opts & FLAG_NO_SYSLOG)) {
623                 openlog(applet_name, 0, LOG_DAEMON);
624                 logmode |= LOGMODE_SYSLOG;
625         }
626
627         bb_signals(0
628                 | (1 << SIGINT )
629                 | (1 << SIGTERM)
630                 | (1 << SIGQUIT)
631                 | (1 << SIGHUP ) /* why we ignore it? */
632                 /* | (1 << SIGCHLD) - run_script does not use it anymore */
633                 , record_signo);
634
635         bb_error_msg("started: %s", bb_banner);
636
637         if (opts & FLAG_MONITOR) {
638                 struct ifreq ifrequest;
639                 set_ifreq_to_ifname(&ifrequest);
640                 G.iface_exists = (network_ioctl(SIOCGIFINDEX, &ifrequest, NULL) == 0);
641         }
642
643         if (G.iface_exists)
644                 maybe_up_new_iface();
645
646         iface_status = detect_link();
647         if (iface_status == IFSTATUS_ERR)
648                 goto exiting;
649         iface_status_str = strstatus(iface_status);
650
651         if (opts & FLAG_MONITOR) {
652                 bb_error_msg("interface %s",
653                         G.iface_exists ? "exists"
654                         : "doesn't exist, waiting");
655         }
656         /* else we assume it always exists, but don't mislead user
657          * by potentially lying that it really exists */
658
659         if (G.iface_exists) {
660                 bb_error_msg("link is %s", iface_status_str);
661         }
662
663         if ((!(opts & FLAG_NO_STARTUP)
664              && iface_status == IFSTATUS_UP
665             )
666          || (opts & FLAG_INITIAL_DOWN)
667         ) {
668                 if (run_script(iface_status_str) != 0)
669                         goto exiting;
670         }
671
672         /* Main loop */
673         netlink_pollfd[0].fd = netlink_fd;
674         netlink_pollfd[0].events = POLLIN;
675         delay_time = 0;
676         while (1) {
677                 int iface_status_old;
678
679                 switch (bb_got_signal) {
680                 case SIGINT:
681                 case SIGTERM:
682                         bb_got_signal = 0;
683                         goto cleanup;
684                 case SIGQUIT:
685                         bb_got_signal = 0;
686                         goto exiting;
687                 default:
688                         bb_got_signal = 0;
689                 /* do not clear bb_got_signal if already 0, this can lose signals */
690                 case 0:
691                         break;
692                 }
693
694                 if (poll(netlink_pollfd,
695                                 (opts & FLAG_MONITOR) ? 1 : 0,
696                                 G.poll_time
697                         ) < 0
698                 ) {
699                         if (errno == EINTR)
700                                 continue;
701                         bb_perror_msg("poll");
702                         goto exiting;
703                 }
704
705                 if ((opts & FLAG_MONITOR)
706                  && (netlink_pollfd[0].revents & POLLIN)
707                 ) {
708                         int iface_exists_old;
709
710                         iface_exists_old = G.iface_exists;
711                         G.iface_exists = check_existence_through_netlink();
712                         if (G.iface_exists < 0) /* error */
713                                 goto exiting;
714                         if (iface_exists_old != G.iface_exists) {
715                                 bb_error_msg("interface %sappeared",
716                                                 G.iface_exists ? "" : "dis");
717                                 if (G.iface_exists)
718                                         maybe_up_new_iface();
719                         }
720                 }
721
722                 /* note: if !G.iface_exists, returns DOWN */
723                 iface_status_old = iface_status;
724                 iface_status = detect_link();
725                 if (iface_status == IFSTATUS_ERR) {
726                         if (!(opts & FLAG_MONITOR))
727                                 goto exiting;
728                         iface_status = IFSTATUS_DOWN;
729                 }
730                 iface_status_str = strstatus(iface_status);
731
732                 if (iface_status_old != iface_status) {
733                         bb_error_msg("link is %s", iface_status_str);
734
735                         if (delay_time) {
736                                 /* link restored its old status before
737                                  * we ran script. don't run the script: */
738                                 delay_time = 0;
739                         } else {
740                                 delay_time = monotonic_sec();
741                                 if (iface_status == IFSTATUS_UP)
742                                         delay_time += G.delay_up;
743                                 if (iface_status == IFSTATUS_DOWN)
744                                         delay_time += G.delay_down;
745 #if 0  /* if you are back in 1970... */
746                                 if (delay_time == 0) {
747                                         sleep(1);
748                                         delay_time = 1;
749                                 }
750 #endif
751                         }
752                 }
753
754                 if (delay_time && (int)(monotonic_sec() - delay_time) >= 0) {
755                         if (run_script(iface_status_str) != 0)
756                                 goto exiting;
757                         delay_time = 0;
758                 }
759         } /* while (1) */
760
761  cleanup:
762         if (!(opts & FLAG_NO_SHUTDOWN)
763          && (iface_status == IFSTATUS_UP
764              || (iface_status == IFSTATUS_DOWN && delay_time)
765             )
766         ) {
767                 setenv(IFPLUGD_ENV_PREVIOUS, strstatus(iface_status), 1);
768                 setenv(IFPLUGD_ENV_CURRENT, strstatus(-1), 1);
769                 run_script("down\0up"); /* reusing string */
770         }
771
772  exiting:
773         remove_pidfile(pidfile_name);
774         bb_error_msg_and_die("exiting");
775 }