blob: 62b1355246986532f2968d934fc54c2a2b5cc436 [file] [log] [blame]
Denis Vlasenko71c16572009-04-26 01:08:51 +00001/* vi: set sw=4 ts=4: */
2/*
3 * ifplugd for busybox
4 *
Denis Vlasenkof4e45632009-04-26 01:17:44 +00005 * Copyright (C) 2009 Maksym Kryzhanovskyy <xmaks@email.cz>
Denis Vlasenko71c16572009-04-26 01:08:51 +00006 *
7 * Licensed under GPLv2 or later, see file LICENSE in this tarball for details.
8 */
9#include "libbb.h"
10
Denys Vlasenko0568b6e2009-08-08 03:20:12 +020011#include "fix_u32.h"
Denis Vlasenko71c16572009-04-26 01:08:51 +000012#include <linux/if.h>
Denys Vlasenko9c35a1c2009-05-01 09:04:25 +020013#include <linux/mii.h>
Denis Vlasenko71c16572009-04-26 01:08:51 +000014#include <linux/ethtool.h>
15#include <net/ethernet.h>
16#include <linux/netlink.h>
17#include <linux/rtnetlink.h>
18#include <linux/sockios.h>
19#include <syslog.h>
20
21#define __user
22#include <linux/wireless.h>
23
Denis Vlasenkof4e45632009-04-26 01:17:44 +000024/*
25TODO: describe compat status here.
26
27One questionable point of the design is netlink usage:
28
29We have 1 second timeout by default to poll the link status,
30it is short enough so that there are no real benefits in
31using netlink to get "instantaneous" interface creation/deletion
32notifications. We can check for interface existence by just
33doing some fast ioctl using its name.
34
35Netlink code then can be just dropped (1k or more?)
36*/
37
38
Denis Vlasenko71c16572009-04-26 01:08:51 +000039#define IFPLUGD_ENV_PREVIOUS "IFPLUGD_PREVIOUS"
40#define IFPLUGD_ENV_CURRENT "IFPLUGD_CURRENT"
41
42enum {
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
62#endif
63};
64#if ENABLE_FEATURE_PIDFILE
65# define OPTION_STR "+ansfFi:r:It:u:d:m:pqlx:Mk"
66#else
67# define OPTION_STR "+ansfFi:r:It:u:d:m:pqlx:M"
68#endif
69
70enum { // api mode
71 API_AUTO = 'a',
72 API_ETHTOOL = 'e',
73 API_MII = 'm',
74 API_PRIVATE = 'p',
75 API_WLAN = 'w',
76 API_IFF = 'i',
77};
78
79enum { // interface status
80 IFSTATUS_ERR = -1,
81 IFSTATUS_DOWN = 0,
82 IFSTATUS_UP = 1,
83};
84
85enum { // constant fds
86 ioctl_fd = 3,
87 netlink_fd = 4,
88};
89
90struct globals {
91 smallint iface_last_status;
92 smallint iface_exists;
93
94 /* Used in getopt32, must have sizeof == sizeof(int) */
95 unsigned poll_time;
96 unsigned delay_up;
97 unsigned delay_down;
98
99 const char *iface;
100 const char *api_mode;
101 const char *script_name;
102 const char *extra_arg;
103
104 smallint (*detect_link_func)(void);
105 smallint (*cached_detect_link_func)(void);
106};
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; \
112 G.poll_time = 1; \
113 G.delay_down = 5; \
114 G.iface = "eth0"; \
115 G.api_mode = "a"; \
116 G.script_name = "/etc/ifplugd/ifplugd.action"; \
117} while (0)
118
119
120static int run_script(const char *action)
121{
Denys Vlasenko79ae5342010-01-06 12:27:18 +0100122 char *argv[5];
Denis Vlasenko71c16572009-04-26 01:08:51 +0000123 int r;
124
125 bb_error_msg("executing '%s %s %s'", G.script_name, G.iface, action);
126
127#if 1
Denis Vlasenko71c16572009-04-26 01:08:51 +0000128
Denys Vlasenko79ae5342010-01-06 12:27:18 +0100129 argv[0] = (char*) G.script_name;
130 argv[1] = (char*) G.iface;
131 argv[2] = (char*) action;
132 argv[3] = (char*) G.extra_arg;
133 argv[4] = NULL;
Denis Vlasenko71c16572009-04-26 01:08:51 +0000134
Denys Vlasenko8531d762010-03-18 22:44:00 +0100135 /* r < 0 - can't exec, 0 <= r < 0x180 - exited, >=0x180 - killed by sig (r-0x180) */
136 r = spawn_and_wait(argv);
Denis Vlasenko71c16572009-04-26 01:08:51 +0000137
Denys Vlasenko8531d762010-03-18 22:44:00 +0100138 bb_error_msg("exit code: %d", r & 0xff);
Denis Vlasenko71c16572009-04-26 01:08:51 +0000139 return (option_mask32 & FLAG_IGNORE_RETVAL) ? 0 : r;
140
141#else /* insanity */
142
143 struct fd_pair pipe_pair;
144 char buf[256];
145 int i = 0;
146
147 xpiped_pair(pipe_pair);
148
149 pid = vfork();
150 if (pid < 0) {
151 bb_perror_msg("fork");
152 return -1;
153 }
154
155 /* child */
156 if (pid == 0) {
157 xmove_fd(pipe_pair.wr, 1);
158 xdup2(1, 2);
159 if (pipe_pair.rd > 2)
160 close(pipe_pair.rd);
161
162 // umask(0022); // Set up a sane umask
163
164 execlp(G.script_name, G.script_name, G.iface, action, G.extra_arg, NULL);
165 _exit(EXIT_FAILURE);
166 }
167
168 /* parent */
169 close(pipe_pair.wr);
170
171 while (1) {
172 if (bb_got_signal && bb_got_signal != SIGCHLD) {
173 bb_error_msg("killing child");
174 kill(pid, SIGTERM);
175 bb_got_signal = 0;
176 break;
177 }
178
179 r = read(pipe_pair.rd, &buf[i], 1);
180
181 if (buf[i] == '\n' || i == sizeof(buf)-2 || r != 1) {
182 if (r == 1 && buf[i] != '\n')
183 i++;
184
185 buf[i] = '\0';
186
187 if (i > 0)
188 bb_error_msg("client: %s", buf);
189
190 i = 0;
191 } else {
192 i++;
193 }
194
195 if (r != 1)
196 break;
197 }
198
199 close(pipe_pair.rd);
200
201 wait(&r);
202
203 if (!WIFEXITED(r) || WEXITSTATUS(r) != 0) {
204 bb_error_msg("program execution failed, return value is %i",
205 WEXITSTATUS(r));
206 return option_mask32 & FLAG_IGNORE_RETVAL ? 0 : WEXITSTATUS(r);
207 }
208 bb_error_msg("program executed successfully");
209 return 0;
210#endif
211}
212
Denys Vlasenkof422a722010-01-08 12:27:57 +0100213static int network_ioctl(int request, void* data, const char *errmsg)
Denis Vlasenko71c16572009-04-26 01:08:51 +0000214{
Denys Vlasenkof422a722010-01-08 12:27:57 +0100215 int r = ioctl(ioctl_fd, request, data);
216 if (r < 0 && errmsg)
217 bb_perror_msg(errmsg);
218 return r;
Denis Vlasenko71c16572009-04-26 01:08:51 +0000219}
220
221static void set_ifreq_to_ifname(struct ifreq *ifreq)
222{
223 memset(ifreq, 0, sizeof(struct ifreq));
Denis Vlasenko80e57eb2009-04-26 01:43:36 +0000224 strncpy_IFNAMSIZ(ifreq->ifr_name, G.iface);
Denis Vlasenko71c16572009-04-26 01:08:51 +0000225}
226
227static const char *strstatus(int status)
228{
229 if (status == IFSTATUS_ERR)
230 return "error";
231 return "down\0up" + (status * 5);
232}
233
234static void up_iface(void)
235{
236 struct ifreq ifrequest;
237
238 if (!G.iface_exists)
239 return;
240
241 set_ifreq_to_ifname(&ifrequest);
Denys Vlasenkof422a722010-01-08 12:27:57 +0100242 if (network_ioctl(SIOCGIFFLAGS, &ifrequest, "can't get interface flags") < 0) {
Denis Vlasenko71c16572009-04-26 01:08:51 +0000243 G.iface_exists = 0;
244 return;
245 }
246
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");
Denys Vlasenkof422a722010-01-08 12:27:57 +0100251 if (network_ioctl(SIOCSIFFLAGS, &ifrequest, "can't set interface flags") < 0)
252 xfunc_die();
Denis Vlasenko71c16572009-04-26 01:08:51 +0000253 }
254
255#if 0 /* why do we mess with IP addr? It's not our business */
Denys Vlasenkof422a722010-01-08 12:27:57 +0100256 if (network_ioctl(SIOCGIFADDR, &ifrequest, "can't get interface address") < 0) {
Denis Vlasenko71c16572009-04-26 01:08:51 +0000257 } else if (ifrequest.ifr_addr.sa_family != AF_INET) {
Denis Vlasenko1fd3b382009-04-29 12:02:57 +0000258 bb_perror_msg("the interface is not IP-based");
Denis Vlasenko71c16572009-04-26 01:08:51 +0000259 } else {
260 ((struct sockaddr_in*)(&ifrequest.ifr_addr))->sin_addr.s_addr = INADDR_ANY;
Denys Vlasenkof422a722010-01-08 12:27:57 +0100261 network_ioctl(SIOCSIFADDR, &ifrequest, "can't set interface address");
Denis Vlasenko71c16572009-04-26 01:08:51 +0000262 }
Denys Vlasenkof422a722010-01-08 12:27:57 +0100263 network_ioctl(SIOCGIFFLAGS, &ifrequest, "can't get interface flags");
Denis Vlasenko71c16572009-04-26 01:08:51 +0000264#endif
265}
266
267static void maybe_up_new_iface(void)
268{
269 if (!(option_mask32 & FLAG_NO_AUTO))
270 up_iface();
271
272#if 0 /* bloat */
273 struct ifreq ifrequest;
274 struct ethtool_drvinfo driver_info;
275
276 set_ifreq_to_ifname(&ifrequest);
277 driver_info.cmd = ETHTOOL_GDRVINFO;
278 ifrequest.ifr_data = &driver_info;
Denys Vlasenkof422a722010-01-08 12:27:57 +0100279 if (network_ioctl(SIOCETHTOOL, &ifrequest, NULL) == 0) {
Denis Vlasenko71c16572009-04-26 01:08:51 +0000280 char buf[sizeof("/xx:xx:xx:xx:xx:xx")];
281
282 /* Get MAC */
283 buf[0] = '\0';
284 set_ifreq_to_ifname(&ifrequest);
Denys Vlasenkof422a722010-01-08 12:27:57 +0100285 if (network_ioctl(SIOCGIFHWADDR, &ifrequest, NULL) == 0) {
Denis Vlasenko71c16572009-04-26 01:08:51 +0000286 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]));
293 }
294
Denis Vlasenko1fd3b382009-04-29 12:02:57 +0000295 bb_error_msg("using interface %s%s with driver<%s> (version: %s)",
Denis Vlasenko71c16572009-04-26 01:08:51 +0000296 G.iface, buf, driver_info.driver, driver_info.version);
297 }
298#endif
299
300 G.cached_detect_link_func = NULL;
301}
302
303static smallint detect_link_mii(void)
304{
305 struct ifreq ifreq;
Denys Vlasenko9c35a1c2009-05-01 09:04:25 +0200306 struct mii_ioctl_data *mii = (void *)&ifreq.ifr_data;
Denis Vlasenko71c16572009-04-26 01:08:51 +0000307
308 set_ifreq_to_ifname(&ifreq);
309
Denys Vlasenkof422a722010-01-08 12:27:57 +0100310 if (network_ioctl(SIOCGMIIPHY, &ifreq, "SIOCGMIIPHY failed") < 0) {
Denis Vlasenko71c16572009-04-26 01:08:51 +0000311 return IFSTATUS_ERR;
312 }
313
Denys Vlasenko9c35a1c2009-05-01 09:04:25 +0200314 mii->reg_num = 1;
Denis Vlasenko71c16572009-04-26 01:08:51 +0000315
Denys Vlasenkof422a722010-01-08 12:27:57 +0100316 if (network_ioctl(SIOCGMIIREG, &ifreq, "SIOCGMIIREG failed") < 0) {
Denis Vlasenko71c16572009-04-26 01:08:51 +0000317 return IFSTATUS_ERR;
318 }
319
Denys Vlasenko9c35a1c2009-05-01 09:04:25 +0200320 return (mii->val_out & 0x0004) ? IFSTATUS_UP : IFSTATUS_DOWN;
Denis Vlasenko71c16572009-04-26 01:08:51 +0000321}
322
323static smallint detect_link_priv(void)
324{
325 struct ifreq ifreq;
Denys Vlasenko9c35a1c2009-05-01 09:04:25 +0200326 struct mii_ioctl_data *mii = (void *)&ifreq.ifr_data;
Denis Vlasenko71c16572009-04-26 01:08:51 +0000327
328 set_ifreq_to_ifname(&ifreq);
329
Denys Vlasenkof422a722010-01-08 12:27:57 +0100330 if (network_ioctl(SIOCDEVPRIVATE, &ifreq, "SIOCDEVPRIVATE failed") < 0) {
Denis Vlasenko71c16572009-04-26 01:08:51 +0000331 return IFSTATUS_ERR;
332 }
333
Denys Vlasenko9c35a1c2009-05-01 09:04:25 +0200334 mii->reg_num = 1;
Denis Vlasenko71c16572009-04-26 01:08:51 +0000335
Denys Vlasenkof422a722010-01-08 12:27:57 +0100336 if (network_ioctl(SIOCDEVPRIVATE+1, &ifreq, "SIOCDEVPRIVATE+1 failed") < 0) {
Denis Vlasenko71c16572009-04-26 01:08:51 +0000337 return IFSTATUS_ERR;
338 }
339
Denys Vlasenko9c35a1c2009-05-01 09:04:25 +0200340 return (mii->val_out & 0x0004) ? IFSTATUS_UP : IFSTATUS_DOWN;
Denis Vlasenko71c16572009-04-26 01:08:51 +0000341}
342
343static smallint detect_link_ethtool(void)
344{
345 struct ifreq ifreq;
346 struct ethtool_value edata;
347
348 set_ifreq_to_ifname(&ifreq);
349
350 edata.cmd = ETHTOOL_GLINK;
Denys Vlasenko337a31b2009-10-23 18:31:02 +0200351 ifreq.ifr_data = (void*) &edata;
Denis Vlasenko71c16572009-04-26 01:08:51 +0000352
Denys Vlasenkof422a722010-01-08 12:27:57 +0100353 if (network_ioctl(SIOCETHTOOL, &ifreq, "ETHTOOL_GLINK failed") < 0) {
Denis Vlasenko71c16572009-04-26 01:08:51 +0000354 return IFSTATUS_ERR;
355 }
356
357 return edata.data ? IFSTATUS_UP : IFSTATUS_DOWN;
358}
359
360static smallint detect_link_iff(void)
361{
362 struct ifreq ifreq;
363
364 set_ifreq_to_ifname(&ifreq);
365
Denys Vlasenkof422a722010-01-08 12:27:57 +0100366 if (network_ioctl(SIOCGIFFLAGS, &ifreq, "SIOCGIFFLAGS failed") < 0) {
Denis Vlasenko71c16572009-04-26 01:08:51 +0000367 return IFSTATUS_ERR;
368 }
369
Denys Vlasenkof422a722010-01-08 12:27:57 +0100370 /* 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.
375 */
376 if (!(ifreq.ifr_flags & IFF_UP) && G.iface_last_status != IFSTATUS_ERR)
377 return G.iface_last_status;
378
Denis Vlasenko71c16572009-04-26 01:08:51 +0000379 return (ifreq.ifr_flags & IFF_RUNNING) ? IFSTATUS_UP : IFSTATUS_DOWN;
380}
381
382static smallint detect_link_wlan(void)
383{
384 struct iwreq iwrequest;
385 uint8_t mac[ETH_ALEN];
386
387 memset(&iwrequest, 0, sizeof(struct iwreq));
Denis Vlasenko80e57eb2009-04-26 01:43:36 +0000388 strncpy_IFNAMSIZ(iwrequest.ifr_ifrn.ifrn_name, G.iface);
Denis Vlasenko71c16572009-04-26 01:08:51 +0000389
Denys Vlasenkof422a722010-01-08 12:27:57 +0100390 if (network_ioctl(SIOCGIWAP, &iwrequest, "SIOCGIWAP failed") < 0) {
Denis Vlasenko71c16572009-04-26 01:08:51 +0000391 return IFSTATUS_ERR;
392 }
393
394 memcpy(mac, &(iwrequest.u.ap_addr.sa_data), ETH_ALEN);
395
396 if (mac[0] == 0xFF || mac[0] == 0x44 || mac[0] == 0x00) {
397 for (int i = 1; i < ETH_ALEN; ++i) {
398 if (mac[i] != mac[0])
399 return IFSTATUS_UP;
400 }
401 return IFSTATUS_DOWN;
402 }
403
404 return IFSTATUS_UP;
405}
406
407static smallint detect_link_auto(void)
408{
409 const char *method;
410 smallint iface_status;
411 smallint sv_logmode;
412
413 if (G.cached_detect_link_func) {
414 iface_status = G.cached_detect_link_func();
415 if (iface_status != IFSTATUS_ERR)
416 return iface_status;
417 }
418
419 sv_logmode = logmode;
420 logmode = LOGMODE_NONE;
421
422 iface_status = detect_link_ethtool();
423 if (iface_status != IFSTATUS_ERR) {
424 G.cached_detect_link_func = detect_link_ethtool;
425 method = "SIOCETHTOOL";
426 found_method:
427 logmode = sv_logmode;
428 bb_error_msg("using %s detection mode", method);
429 return iface_status;
430 }
431
432 iface_status = detect_link_mii();
433 if (iface_status != IFSTATUS_ERR) {
434 G.cached_detect_link_func = detect_link_mii;
435 method = "SIOCGMIIPHY";
436 goto found_method;
437 }
438
439 iface_status = detect_link_priv();
440 if (iface_status != IFSTATUS_ERR) {
441 G.cached_detect_link_func = detect_link_priv;
442 method = "SIOCDEVPRIVATE";
443 goto found_method;
444 }
445
446 iface_status = detect_link_wlan();
447 if (iface_status != IFSTATUS_ERR) {
448 G.cached_detect_link_func = detect_link_wlan;
449 method = "wireless extension";
450 goto found_method;
451 }
452
453 iface_status = detect_link_iff();
454 if (iface_status != IFSTATUS_ERR) {
455 G.cached_detect_link_func = detect_link_iff;
456 method = "IFF_RUNNING";
457 goto found_method;
458 }
459
460 logmode = sv_logmode;
461 return iface_status; /* IFSTATUS_ERR */
462}
463
464static smallint detect_link(void)
465{
466 smallint status;
467
468 if (!G.iface_exists)
469 return (option_mask32 & FLAG_MONITOR) ? IFSTATUS_DOWN : IFSTATUS_ERR;
470
Denys Vlasenkof422a722010-01-08 12:27:57 +0100471 /* Some drivers can't detect link status when the interface is down.
472 * I imagine detect_link_iff() is the most vulnerable.
473 * That's why -a "noauto" in an option, not a hardwired behavior.
474 */
Denis Vlasenko71c16572009-04-26 01:08:51 +0000475 if (!(option_mask32 & FLAG_NO_AUTO))
476 up_iface();
Denis Vlasenko71c16572009-04-26 01:08:51 +0000477
478 status = G.detect_link_func();
479 if (status == IFSTATUS_ERR) {
480 if (option_mask32 & FLAG_IGNORE_FAIL)
481 status = IFSTATUS_DOWN;
482 if (option_mask32 & FLAG_IGNORE_FAIL_POSITIVE)
483 status = IFSTATUS_UP;
484 }
485
486 if (status == IFSTATUS_ERR
487 && G.detect_link_func == detect_link_auto
488 ) {
489 bb_error_msg("failed to detect link status");
490 }
491
492 if (status != G.iface_last_status) {
493//TODO: is it safe to repeatedly do this?
494 setenv(IFPLUGD_ENV_PREVIOUS, strstatus(G.iface_last_status), 1);
495 setenv(IFPLUGD_ENV_CURRENT, strstatus(status), 1);
496 G.iface_last_status = status;
497 }
498
499 return status;
500}
501
502static NOINLINE int check_existence_through_netlink(void)
503{
504 char replybuf[1024];
505
506 while (1) {
507 struct nlmsghdr *mhdr;
508 ssize_t bytes;
509
510 bytes = recv(netlink_fd, &replybuf, sizeof(replybuf), MSG_DONTWAIT);
511 if (bytes < 0) {
512 if (errno == EAGAIN)
513 return G.iface_exists;
514 if (errno == EINTR)
515 continue;
516
517 bb_perror_msg("netlink: recv");
518 return -1;
519 }
520
521 mhdr = (struct nlmsghdr*)replybuf;
522 while (bytes > 0) {
523 if (!NLMSG_OK(mhdr, bytes)
524 || bytes < sizeof(struct nlmsghdr)
525 || bytes < mhdr->nlmsg_len
526 ) {
527 bb_error_msg("netlink packet too small or truncated");
528 return -1;
529 }
530
531 if (mhdr->nlmsg_type == RTM_NEWLINK || mhdr->nlmsg_type == RTM_DELLINK) {
532 struct rtattr *attr;
533 struct ifinfomsg *imsg;
534 int attr_len;
535
536 imsg = NLMSG_DATA(mhdr);
537
538 if (mhdr->nlmsg_len < NLMSG_LENGTH(sizeof(struct ifinfomsg))) {
539 bb_error_msg("netlink packet too small or truncated");
540 return -1;
541 }
542
543 attr = (struct rtattr*)((char*)imsg + NLMSG_ALIGN(sizeof(struct ifinfomsg)));
544 attr_len = NLMSG_PAYLOAD(mhdr, sizeof(struct ifinfomsg));
545
546 while (RTA_OK(attr, attr_len)) {
547 if (attr->rta_type == IFLA_IFNAME) {
548 char ifname[IFNAMSIZ + 1];
549 int len = RTA_PAYLOAD(attr);
550
551 if (len > IFNAMSIZ)
552 len = IFNAMSIZ;
553 memcpy(ifname, RTA_DATA(attr), len);
554 if (strcmp(G.iface, ifname) == 0) {
555 G.iface_exists = (mhdr->nlmsg_type == RTM_NEWLINK);
556 }
557 }
558 attr = RTA_NEXT(attr, attr_len);
559 }
560 }
561
562 mhdr = NLMSG_NEXT(mhdr, bytes);
563 }
564 }
565
566 return G.iface_exists;
567}
568
569static NOINLINE int netlink_open(void)
570{
571 int fd;
572 struct sockaddr_nl addr;
573
574 fd = xsocket(PF_NETLINK, SOCK_DGRAM, NETLINK_ROUTE);
575
576 memset(&addr, 0, sizeof(addr));
577 addr.nl_family = AF_NETLINK;
578 addr.nl_groups = RTMGRP_LINK;
579 addr.nl_pid = getpid();
580
581 xbind(fd, (struct sockaddr*)&addr, sizeof(addr));
582
583 return fd;
584}
585
Denys Vlasenko9c35a1c2009-05-01 09:04:25 +0200586#if ENABLE_FEATURE_PIDFILE
Denis Vlasenko71c16572009-04-26 01:08:51 +0000587static NOINLINE pid_t read_pid(const char *filename)
588{
589 int len;
590 char buf[128];
591
592 len = open_read_close(filename, buf, 127);
593 if (len > 0) {
594 buf[len] = '\0';
595 /* returns ULONG_MAX on error => -1 */
596 return bb_strtoul(buf, NULL, 10);
597 }
598 return 0;
599}
Denys Vlasenko9c35a1c2009-05-01 09:04:25 +0200600#endif
Denis Vlasenko71c16572009-04-26 01:08:51 +0000601
602int ifplugd_main(int argc, char **argv) MAIN_EXTERNALLY_VISIBLE;
603int ifplugd_main(int argc UNUSED_PARAM, char **argv)
604{
605 int iface_status;
606 int delay_time;
607 const char *iface_status_str;
608 struct pollfd netlink_pollfd[1];
609 unsigned opts;
610#if ENABLE_FEATURE_PIDFILE
611 char *pidfile_name;
612 pid_t pid_from_pidfile;
613#endif
614
615 INIT_G();
616
617 opt_complementary = "t+:u+:d+";
618 opts = getopt32(argv, OPTION_STR,
619 &G.iface, &G.script_name, &G.poll_time, &G.delay_up,
620 &G.delay_down, &G.api_mode, &G.extra_arg);
Denys Vlasenko5a34d022009-11-07 17:30:14 +0100621 G.poll_time *= 1000;
Denis Vlasenko71c16572009-04-26 01:08:51 +0000622
623 applet_name = xasprintf("ifplugd(%s)", G.iface);
624
625#if ENABLE_FEATURE_PIDFILE
626 pidfile_name = xasprintf(_PATH_VARRUN"ifplugd.%s.pid", G.iface);
627 pid_from_pidfile = read_pid(pidfile_name);
628
629 if (opts & FLAG_KILL) {
630 if (pid_from_pidfile > 0)
631 kill(pid_from_pidfile, SIGQUIT);
632 return EXIT_SUCCESS;
633 }
634
635 if (pid_from_pidfile > 0 && kill(pid_from_pidfile, 0) == 0)
636 bb_error_msg_and_die("daemon already running");
637#endif
638
639 switch (G.api_mode[0]) {
640 case API_AUTO:
641 G.detect_link_func = detect_link_auto;
642 break;
643 case API_ETHTOOL:
644 G.detect_link_func = detect_link_ethtool;
645 break;
646 case API_MII:
647 G.detect_link_func = detect_link_mii;
648 break;
649 case API_PRIVATE:
650 G.detect_link_func = detect_link_priv;
651 break;
652 case API_WLAN:
653 G.detect_link_func = detect_link_wlan;
654 break;
655 case API_IFF:
656 G.detect_link_func = detect_link_iff;
657 break;
658 default:
659 bb_error_msg_and_die("unknown API mode '%s'", G.api_mode);
660 }
661
662 if (!(opts & FLAG_NO_DAEMON))
663 bb_daemonize_or_rexec(DAEMON_CHDIR_ROOT, argv);
664
665 xmove_fd(xsocket(AF_INET, SOCK_DGRAM, 0), ioctl_fd);
666 if (opts & FLAG_MONITOR) {
667 xmove_fd(netlink_open(), netlink_fd);
668 }
669
670 write_pidfile(pidfile_name);
671
672 /* this can't be moved before socket creation */
673 if (!(opts & FLAG_NO_SYSLOG)) {
674 openlog(applet_name, 0, LOG_DAEMON);
675 logmode |= LOGMODE_SYSLOG;
676 }
677
678 bb_signals(0
679 | (1 << SIGINT )
680 | (1 << SIGTERM)
681 | (1 << SIGQUIT)
682 | (1 << SIGHUP ) /* why we ignore it? */
683 /* | (1 << SIGCHLD) - run_script does not use it anymore */
684 , record_signo);
685
686 bb_error_msg("started: %s", bb_banner);
687
688 if (opts & FLAG_MONITOR) {
689 struct ifreq ifrequest;
690 set_ifreq_to_ifname(&ifrequest);
Denys Vlasenkof422a722010-01-08 12:27:57 +0100691 G.iface_exists = (network_ioctl(SIOCGIFINDEX, &ifrequest, NULL) == 0);
Denis Vlasenko71c16572009-04-26 01:08:51 +0000692 }
693
694 if (G.iface_exists)
695 maybe_up_new_iface();
696
697 iface_status = detect_link();
698 if (iface_status == IFSTATUS_ERR)
699 goto exiting;
700 iface_status_str = strstatus(iface_status);
701
702 if (opts & FLAG_MONITOR) {
703 bb_error_msg("interface %s",
704 G.iface_exists ? "exists"
705 : "doesn't exist, waiting");
706 }
707 /* else we assume it always exists, but don't mislead user
708 * by potentially lying that it really exists */
709
710 if (G.iface_exists) {
711 bb_error_msg("link is %s", iface_status_str);
712 }
713
714 if ((!(opts & FLAG_NO_STARTUP)
715 && iface_status == IFSTATUS_UP
716 )
717 || (opts & FLAG_INITIAL_DOWN)
718 ) {
719 if (run_script(iface_status_str) != 0)
720 goto exiting;
721 }
722
723 /* Main loop */
724 netlink_pollfd[0].fd = netlink_fd;
725 netlink_pollfd[0].events = POLLIN;
726 delay_time = 0;
727 while (1) {
728 int iface_status_old;
729 int iface_exists_old;
730
731 switch (bb_got_signal) {
732 case SIGINT:
733 case SIGTERM:
734 bb_got_signal = 0;
735 goto cleanup;
736 case SIGQUIT:
737 bb_got_signal = 0;
738 goto exiting;
739 default:
740 bb_got_signal = 0;
741 break;
742 }
743
744 if (poll(netlink_pollfd,
745 (opts & FLAG_MONITOR) ? 1 : 0,
Denys Vlasenko5a34d022009-11-07 17:30:14 +0100746 G.poll_time
Denis Vlasenko71c16572009-04-26 01:08:51 +0000747 ) < 0
748 ) {
749 if (errno == EINTR)
750 continue;
751 bb_perror_msg("poll");
752 goto exiting;
753 }
754
755 iface_status_old = iface_status;
756 iface_exists_old = G.iface_exists;
757
758 if ((opts & FLAG_MONITOR)
759 && (netlink_pollfd[0].revents & POLLIN)
760 ) {
761 G.iface_exists = check_existence_through_netlink();
762 if (G.iface_exists < 0) /* error */
763 goto exiting;
764 if (iface_exists_old != G.iface_exists) {
765 bb_error_msg("interface %sappeared",
766 G.iface_exists ? "" : "dis");
767 if (G.iface_exists)
768 maybe_up_new_iface();
769 }
770 }
771
772 /* note: if !G.iface_exists, returns DOWN */
773 iface_status = detect_link();
774 if (iface_status == IFSTATUS_ERR) {
775 if (!(opts & FLAG_MONITOR))
776 goto exiting;
777 iface_status = IFSTATUS_DOWN;
778 }
779 iface_status_str = strstatus(iface_status);
780
781 if (iface_status_old != iface_status) {
782 bb_error_msg("link is %s", iface_status_str);
783
784 if (delay_time) {
785 /* link restored its old status before
786 * we run script. don't run the script: */
787 delay_time = 0;
788 } else {
789 delay_time = monotonic_sec();
790 if (iface_status == IFSTATUS_UP)
791 delay_time += G.delay_up;
792 if (iface_status == IFSTATUS_DOWN)
793 delay_time += G.delay_down;
794 if (delay_time == 0)
795 delay_time++;
796 }
797 }
798
799 if (delay_time && (int)(monotonic_sec() - delay_time) >= 0) {
800 delay_time = 0;
801 if (run_script(iface_status_str) != 0)
802 goto exiting;
803 }
804 } /* while (1) */
805
806 cleanup:
807 if (!(opts & FLAG_NO_SHUTDOWN)
808 && (iface_status == IFSTATUS_UP
809 || (iface_status == IFSTATUS_DOWN && delay_time)
810 )
811 ) {
812 setenv(IFPLUGD_ENV_PREVIOUS, strstatus(iface_status), 1);
813 setenv(IFPLUGD_ENV_CURRENT, strstatus(-1), 1);
814 run_script("down\0up"); /* reusing string */
815 }
816
817 exiting:
818 remove_pidfile(pidfile_name);
819 bb_error_msg_and_die("exiting");
820}