blob: 41b04c4ed83cf290d0959d840e1eb59a587d5602 [file] [log] [blame]
Denis Vlasenko71c16572009-04-26 01:08:51 +00001/* vi: set sw=4 ts=4: */
2/*
Maxim Kryzhanovsky2004fa12010-03-30 15:49:57 +02003 * ifplugd for busybox, based on ifplugd 0.28 (written by Lennart Poettering).
Denis Vlasenko71c16572009-04-26 01:08:51 +00004 *
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/*
Maxim Kryzhanovsky2004fa12010-03-30 15:49:57 +020025From initial port to busybox, removed most of the redundancy by
26converting implementation of a polymorphic interface to the strict
27functional style. The main role is run a script when link state
28changed, other activities like audio signal or detailed reports
29are on the script itself.
Denis Vlasenkof4e45632009-04-26 01:17:44 +000030
31One questionable point of the design is netlink usage:
32
33We have 1 second timeout by default to poll the link status,
34it is short enough so that there are no real benefits in
35using netlink to get "instantaneous" interface creation/deletion
36notifications. We can check for interface existence by just
37doing some fast ioctl using its name.
38
39Netlink code then can be just dropped (1k or more?)
40*/
41
42
Denis Vlasenko71c16572009-04-26 01:08:51 +000043#define IFPLUGD_ENV_PREVIOUS "IFPLUGD_PREVIOUS"
44#define IFPLUGD_ENV_CURRENT "IFPLUGD_CURRENT"
45
46enum {
47 FLAG_NO_AUTO = 1 << 0, // -a, Do not enable interface automatically
48 FLAG_NO_DAEMON = 1 << 1, // -n, Do not daemonize
49 FLAG_NO_SYSLOG = 1 << 2, // -s, Do not use syslog, use stderr instead
50 FLAG_IGNORE_FAIL = 1 << 3, // -f, Ignore detection failure, retry instead (failure is treated as DOWN)
51 FLAG_IGNORE_FAIL_POSITIVE = 1 << 4, // -F, Ignore detection failure, retry instead (failure is treated as UP)
52 FLAG_IFACE = 1 << 5, // -i, Specify ethernet interface
53 FLAG_RUN = 1 << 6, // -r, Specify program to execute
54 FLAG_IGNORE_RETVAL = 1 << 7, // -I, Don't exit on nonzero return value of program executed
55 FLAG_POLL_TIME = 1 << 8, // -t, Specify poll time in seconds
56 FLAG_DELAY_UP = 1 << 9, // -u, Specify delay for configuring interface
57 FLAG_DELAY_DOWN = 1 << 10, // -d, Specify delay for deconfiguring interface
58 FLAG_API_MODE = 1 << 11, // -m, Force API mode (mii, priv, ethtool, wlan, auto)
59 FLAG_NO_STARTUP = 1 << 12, // -p, Don't run script on daemon startup
60 FLAG_NO_SHUTDOWN = 1 << 13, // -q, Don't run script on daemon quit
61 FLAG_INITIAL_DOWN = 1 << 14, // -l, Run "down" script on startup if no cable is detected
62 FLAG_EXTRA_ARG = 1 << 15, // -x, Specify an extra argument for action script
63 FLAG_MONITOR = 1 << 16, // -M, Use interface monitoring
64#if ENABLE_FEATURE_PIDFILE
65 FLAG_KILL = 1 << 17, // -k, Kill a running daemon
66#endif
67};
68#if ENABLE_FEATURE_PIDFILE
69# define OPTION_STR "+ansfFi:r:It:u:d:m:pqlx:Mk"
70#else
71# define OPTION_STR "+ansfFi:r:It:u:d:m:pqlx:M"
72#endif
73
74enum { // api mode
75 API_AUTO = 'a',
76 API_ETHTOOL = 'e',
77 API_MII = 'm',
78 API_PRIVATE = 'p',
79 API_WLAN = 'w',
80 API_IFF = 'i',
81};
82
83enum { // interface status
84 IFSTATUS_ERR = -1,
85 IFSTATUS_DOWN = 0,
86 IFSTATUS_UP = 1,
87};
88
89enum { // constant fds
90 ioctl_fd = 3,
91 netlink_fd = 4,
92};
93
94struct globals {
95 smallint iface_last_status;
96 smallint iface_exists;
97
98 /* Used in getopt32, must have sizeof == sizeof(int) */
99 unsigned poll_time;
100 unsigned delay_up;
101 unsigned delay_down;
102
103 const char *iface;
104 const char *api_mode;
105 const char *script_name;
106 const char *extra_arg;
107
108 smallint (*detect_link_func)(void);
109 smallint (*cached_detect_link_func)(void);
110};
111#define G (*ptr_to_globals)
112#define INIT_G() do { \
113 SET_PTR_TO_GLOBALS(xzalloc(sizeof(G))); \
114 G.iface_last_status = -1; \
115 G.iface_exists = 1; \
116 G.poll_time = 1; \
117 G.delay_down = 5; \
118 G.iface = "eth0"; \
119 G.api_mode = "a"; \
120 G.script_name = "/etc/ifplugd/ifplugd.action"; \
121} while (0)
122
123
124static int run_script(const char *action)
125{
Denys Vlasenko79ae5342010-01-06 12:27:18 +0100126 char *argv[5];
Denis Vlasenko71c16572009-04-26 01:08:51 +0000127 int r;
128
129 bb_error_msg("executing '%s %s %s'", G.script_name, G.iface, action);
130
131#if 1
Denis Vlasenko71c16572009-04-26 01:08:51 +0000132
Denys Vlasenko79ae5342010-01-06 12:27:18 +0100133 argv[0] = (char*) G.script_name;
134 argv[1] = (char*) G.iface;
135 argv[2] = (char*) action;
136 argv[3] = (char*) G.extra_arg;
137 argv[4] = NULL;
Denis Vlasenko71c16572009-04-26 01:08:51 +0000138
Denys Vlasenko8531d762010-03-18 22:44:00 +0100139 /* r < 0 - can't exec, 0 <= r < 0x180 - exited, >=0x180 - killed by sig (r-0x180) */
140 r = spawn_and_wait(argv);
Denis Vlasenko71c16572009-04-26 01:08:51 +0000141
Denys Vlasenko8531d762010-03-18 22:44:00 +0100142 bb_error_msg("exit code: %d", r & 0xff);
Denis Vlasenko71c16572009-04-26 01:08:51 +0000143 return (option_mask32 & FLAG_IGNORE_RETVAL) ? 0 : r;
144
145#else /* insanity */
146
147 struct fd_pair pipe_pair;
148 char buf[256];
149 int i = 0;
150
151 xpiped_pair(pipe_pair);
152
153 pid = vfork();
154 if (pid < 0) {
155 bb_perror_msg("fork");
156 return -1;
157 }
158
159 /* child */
160 if (pid == 0) {
161 xmove_fd(pipe_pair.wr, 1);
162 xdup2(1, 2);
163 if (pipe_pair.rd > 2)
164 close(pipe_pair.rd);
165
166 // umask(0022); // Set up a sane umask
167
168 execlp(G.script_name, G.script_name, G.iface, action, G.extra_arg, NULL);
169 _exit(EXIT_FAILURE);
170 }
171
172 /* parent */
173 close(pipe_pair.wr);
174
175 while (1) {
176 if (bb_got_signal && bb_got_signal != SIGCHLD) {
177 bb_error_msg("killing child");
178 kill(pid, SIGTERM);
179 bb_got_signal = 0;
180 break;
181 }
182
183 r = read(pipe_pair.rd, &buf[i], 1);
184
185 if (buf[i] == '\n' || i == sizeof(buf)-2 || r != 1) {
186 if (r == 1 && buf[i] != '\n')
187 i++;
188
189 buf[i] = '\0';
190
191 if (i > 0)
192 bb_error_msg("client: %s", buf);
193
194 i = 0;
195 } else {
196 i++;
197 }
198
199 if (r != 1)
200 break;
201 }
202
203 close(pipe_pair.rd);
204
205 wait(&r);
206
207 if (!WIFEXITED(r) || WEXITSTATUS(r) != 0) {
208 bb_error_msg("program execution failed, return value is %i",
209 WEXITSTATUS(r));
210 return option_mask32 & FLAG_IGNORE_RETVAL ? 0 : WEXITSTATUS(r);
211 }
212 bb_error_msg("program executed successfully");
213 return 0;
214#endif
215}
216
Denys Vlasenkof422a722010-01-08 12:27:57 +0100217static int network_ioctl(int request, void* data, const char *errmsg)
Denis Vlasenko71c16572009-04-26 01:08:51 +0000218{
Denys Vlasenkof422a722010-01-08 12:27:57 +0100219 int r = ioctl(ioctl_fd, request, data);
220 if (r < 0 && errmsg)
Maxim Kryzhanovskyfcb84c82010-03-29 09:09:05 +0200221 bb_perror_msg("%s failed", errmsg);
Denys Vlasenkof422a722010-01-08 12:27:57 +0100222 return r;
Denis Vlasenko71c16572009-04-26 01:08:51 +0000223}
224
225static void set_ifreq_to_ifname(struct ifreq *ifreq)
226{
227 memset(ifreq, 0, sizeof(struct ifreq));
Denis Vlasenko80e57eb2009-04-26 01:43:36 +0000228 strncpy_IFNAMSIZ(ifreq->ifr_name, G.iface);
Denis Vlasenko71c16572009-04-26 01:08:51 +0000229}
230
231static const char *strstatus(int status)
232{
233 if (status == IFSTATUS_ERR)
234 return "error";
235 return "down\0up" + (status * 5);
236}
237
238static void up_iface(void)
239{
240 struct ifreq ifrequest;
241
242 if (!G.iface_exists)
243 return;
244
245 set_ifreq_to_ifname(&ifrequest);
Maxim Kryzhanovskyfcb84c82010-03-29 09:09:05 +0200246 if (network_ioctl(SIOCGIFFLAGS, &ifrequest, "getting interface flags") < 0) {
Denis Vlasenko71c16572009-04-26 01:08:51 +0000247 G.iface_exists = 0;
248 return;
249 }
250
251 if (!(ifrequest.ifr_flags & IFF_UP)) {
252 ifrequest.ifr_flags |= IFF_UP;
253 /* Let user know we mess up with interface */
254 bb_error_msg("upping interface");
Maxim Kryzhanovskyfcb84c82010-03-29 09:09:05 +0200255 if (network_ioctl(SIOCSIFFLAGS, &ifrequest, "setting interface flags") < 0)
Denys Vlasenkof422a722010-01-08 12:27:57 +0100256 xfunc_die();
Denis Vlasenko71c16572009-04-26 01:08:51 +0000257 }
258
259#if 0 /* why do we mess with IP addr? It's not our business */
Denys Vlasenkof422a722010-01-08 12:27:57 +0100260 if (network_ioctl(SIOCGIFADDR, &ifrequest, "can't get interface address") < 0) {
Denis Vlasenko71c16572009-04-26 01:08:51 +0000261 } else if (ifrequest.ifr_addr.sa_family != AF_INET) {
Denis Vlasenko1fd3b382009-04-29 12:02:57 +0000262 bb_perror_msg("the interface is not IP-based");
Denis Vlasenko71c16572009-04-26 01:08:51 +0000263 } else {
264 ((struct sockaddr_in*)(&ifrequest.ifr_addr))->sin_addr.s_addr = INADDR_ANY;
Denys Vlasenkof422a722010-01-08 12:27:57 +0100265 network_ioctl(SIOCSIFADDR, &ifrequest, "can't set interface address");
Denis Vlasenko71c16572009-04-26 01:08:51 +0000266 }
Denys Vlasenkof422a722010-01-08 12:27:57 +0100267 network_ioctl(SIOCGIFFLAGS, &ifrequest, "can't get interface flags");
Denis Vlasenko71c16572009-04-26 01:08:51 +0000268#endif
269}
270
271static void maybe_up_new_iface(void)
272{
273 if (!(option_mask32 & FLAG_NO_AUTO))
274 up_iface();
275
276#if 0 /* bloat */
277 struct ifreq ifrequest;
278 struct ethtool_drvinfo driver_info;
279
280 set_ifreq_to_ifname(&ifrequest);
281 driver_info.cmd = ETHTOOL_GDRVINFO;
282 ifrequest.ifr_data = &driver_info;
Denys Vlasenkof422a722010-01-08 12:27:57 +0100283 if (network_ioctl(SIOCETHTOOL, &ifrequest, NULL) == 0) {
Denis Vlasenko71c16572009-04-26 01:08:51 +0000284 char buf[sizeof("/xx:xx:xx:xx:xx:xx")];
285
286 /* Get MAC */
287 buf[0] = '\0';
288 set_ifreq_to_ifname(&ifrequest);
Denys Vlasenkof422a722010-01-08 12:27:57 +0100289 if (network_ioctl(SIOCGIFHWADDR, &ifrequest, NULL) == 0) {
Denis Vlasenko71c16572009-04-26 01:08:51 +0000290 sprintf(buf, "/%02X:%02X:%02X:%02X:%02X:%02X",
291 (uint8_t)(ifrequest.ifr_hwaddr.sa_data[0]),
292 (uint8_t)(ifrequest.ifr_hwaddr.sa_data[1]),
293 (uint8_t)(ifrequest.ifr_hwaddr.sa_data[2]),
294 (uint8_t)(ifrequest.ifr_hwaddr.sa_data[3]),
295 (uint8_t)(ifrequest.ifr_hwaddr.sa_data[4]),
296 (uint8_t)(ifrequest.ifr_hwaddr.sa_data[5]));
297 }
298
Denis Vlasenko1fd3b382009-04-29 12:02:57 +0000299 bb_error_msg("using interface %s%s with driver<%s> (version: %s)",
Denis Vlasenko71c16572009-04-26 01:08:51 +0000300 G.iface, buf, driver_info.driver, driver_info.version);
301 }
302#endif
303
304 G.cached_detect_link_func = NULL;
305}
306
307static smallint detect_link_mii(void)
308{
309 struct ifreq ifreq;
Denys Vlasenko9c35a1c2009-05-01 09:04:25 +0200310 struct mii_ioctl_data *mii = (void *)&ifreq.ifr_data;
Denis Vlasenko71c16572009-04-26 01:08:51 +0000311
312 set_ifreq_to_ifname(&ifreq);
313
Maxim Kryzhanovskyfcb84c82010-03-29 09:09:05 +0200314 if (network_ioctl(SIOCGMIIPHY, &ifreq, "SIOCGMIIPHY") < 0) {
Denis Vlasenko71c16572009-04-26 01:08:51 +0000315 return IFSTATUS_ERR;
316 }
317
Denys Vlasenko9c35a1c2009-05-01 09:04:25 +0200318 mii->reg_num = 1;
Denis Vlasenko71c16572009-04-26 01:08:51 +0000319
Maxim Kryzhanovskyfcb84c82010-03-29 09:09:05 +0200320 if (network_ioctl(SIOCGMIIREG, &ifreq, "SIOCGMIIREG") < 0) {
Denis Vlasenko71c16572009-04-26 01:08:51 +0000321 return IFSTATUS_ERR;
322 }
323
Denys Vlasenko9c35a1c2009-05-01 09:04:25 +0200324 return (mii->val_out & 0x0004) ? IFSTATUS_UP : IFSTATUS_DOWN;
Denis Vlasenko71c16572009-04-26 01:08:51 +0000325}
326
327static smallint detect_link_priv(void)
328{
329 struct ifreq ifreq;
Denys Vlasenko9c35a1c2009-05-01 09:04:25 +0200330 struct mii_ioctl_data *mii = (void *)&ifreq.ifr_data;
Denis Vlasenko71c16572009-04-26 01:08:51 +0000331
332 set_ifreq_to_ifname(&ifreq);
333
Maxim Kryzhanovskyfcb84c82010-03-29 09:09:05 +0200334 if (network_ioctl(SIOCDEVPRIVATE, &ifreq, "SIOCDEVPRIVATE") < 0) {
Denis Vlasenko71c16572009-04-26 01:08:51 +0000335 return IFSTATUS_ERR;
336 }
337
Denys Vlasenko9c35a1c2009-05-01 09:04:25 +0200338 mii->reg_num = 1;
Denis Vlasenko71c16572009-04-26 01:08:51 +0000339
Maxim Kryzhanovskyfcb84c82010-03-29 09:09:05 +0200340 if (network_ioctl(SIOCDEVPRIVATE+1, &ifreq, "SIOCDEVPRIVATE+1") < 0) {
Denis Vlasenko71c16572009-04-26 01:08:51 +0000341 return IFSTATUS_ERR;
342 }
343
Denys Vlasenko9c35a1c2009-05-01 09:04:25 +0200344 return (mii->val_out & 0x0004) ? IFSTATUS_UP : IFSTATUS_DOWN;
Denis Vlasenko71c16572009-04-26 01:08:51 +0000345}
346
347static smallint detect_link_ethtool(void)
348{
349 struct ifreq ifreq;
350 struct ethtool_value edata;
351
352 set_ifreq_to_ifname(&ifreq);
353
354 edata.cmd = ETHTOOL_GLINK;
Denys Vlasenko337a31b2009-10-23 18:31:02 +0200355 ifreq.ifr_data = (void*) &edata;
Denis Vlasenko71c16572009-04-26 01:08:51 +0000356
Maxim Kryzhanovskyfcb84c82010-03-29 09:09:05 +0200357 if (network_ioctl(SIOCETHTOOL, &ifreq, "ETHTOOL_GLINK") < 0) {
Denis Vlasenko71c16572009-04-26 01:08:51 +0000358 return IFSTATUS_ERR;
359 }
360
361 return edata.data ? IFSTATUS_UP : IFSTATUS_DOWN;
362}
363
364static smallint detect_link_iff(void)
365{
366 struct ifreq ifreq;
367
368 set_ifreq_to_ifname(&ifreq);
369
Maxim Kryzhanovskyfcb84c82010-03-29 09:09:05 +0200370 if (network_ioctl(SIOCGIFFLAGS, &ifreq, "SIOCGIFFLAGS") < 0) {
Denis Vlasenko71c16572009-04-26 01:08:51 +0000371 return IFSTATUS_ERR;
372 }
373
Denys Vlasenkof422a722010-01-08 12:27:57 +0100374 /* If IFF_UP is not set (interface is down), IFF_RUNNING is never set
375 * regardless of link status. Simply continue to report last status -
376 * no point in reporting spurious link downs if interface is disabled
377 * by admin. When/if it will be brought up,
378 * we'll report real link status.
379 */
380 if (!(ifreq.ifr_flags & IFF_UP) && G.iface_last_status != IFSTATUS_ERR)
381 return G.iface_last_status;
382
Denis Vlasenko71c16572009-04-26 01:08:51 +0000383 return (ifreq.ifr_flags & IFF_RUNNING) ? IFSTATUS_UP : IFSTATUS_DOWN;
384}
385
386static smallint detect_link_wlan(void)
387{
Maxim Kryzhanovskyfcb84c82010-03-29 09:09:05 +0200388 int i;
Denis Vlasenko71c16572009-04-26 01:08:51 +0000389 struct iwreq iwrequest;
390 uint8_t mac[ETH_ALEN];
391
Maxim Kryzhanovskyfcb84c82010-03-29 09:09:05 +0200392 memset(&iwrequest, 0, sizeof(iwrequest));
Denis Vlasenko80e57eb2009-04-26 01:43:36 +0000393 strncpy_IFNAMSIZ(iwrequest.ifr_ifrn.ifrn_name, G.iface);
Denis Vlasenko71c16572009-04-26 01:08:51 +0000394
Maxim Kryzhanovskyfcb84c82010-03-29 09:09:05 +0200395 if (network_ioctl(SIOCGIWAP, &iwrequest, "SIOCGIWAP") < 0) {
Denis Vlasenko71c16572009-04-26 01:08:51 +0000396 return IFSTATUS_ERR;
397 }
398
Maxim Kryzhanovskyfcb84c82010-03-29 09:09:05 +0200399 memcpy(mac, &iwrequest.u.ap_addr.sa_data, ETH_ALEN);
Denis Vlasenko71c16572009-04-26 01:08:51 +0000400
401 if (mac[0] == 0xFF || mac[0] == 0x44 || mac[0] == 0x00) {
Maxim Kryzhanovskyfcb84c82010-03-29 09:09:05 +0200402 for (i = 1; i < ETH_ALEN; ++i) {
Denis Vlasenko71c16572009-04-26 01:08:51 +0000403 if (mac[i] != mac[0])
404 return IFSTATUS_UP;
405 }
406 return IFSTATUS_DOWN;
407 }
408
409 return IFSTATUS_UP;
410}
411
412static smallint detect_link_auto(void)
413{
Maxim Kryzhanovskyfcb84c82010-03-29 09:09:05 +0200414 static const struct {
415 const char *name;
416 smallint (*func)(void);
417 } method[] = {
418 { "SIOCETHTOOL" , &detect_link_ethtool },
419 { "SIOCGMIIPHY" , &detect_link_mii },
420 { "SIOCDEVPRIVATE" , &detect_link_priv },
421 { "wireless extension", &detect_link_wlan },
422 { "IFF_RUNNING" , &detect_link_iff },
423 };
424 int i;
Denis Vlasenko71c16572009-04-26 01:08:51 +0000425 smallint iface_status;
426 smallint sv_logmode;
427
428 if (G.cached_detect_link_func) {
429 iface_status = G.cached_detect_link_func();
430 if (iface_status != IFSTATUS_ERR)
431 return iface_status;
432 }
433
434 sv_logmode = logmode;
Maxim Kryzhanovskyfcb84c82010-03-29 09:09:05 +0200435 for (i = 0; i < ARRAY_SIZE(method); i++) {
436 logmode = LOGMODE_NONE;
437 iface_status = method[i].func();
Denis Vlasenko71c16572009-04-26 01:08:51 +0000438 logmode = sv_logmode;
Maxim Kryzhanovskyfcb84c82010-03-29 09:09:05 +0200439 if (iface_status != IFSTATUS_ERR) {
440 G.cached_detect_link_func = method[i].func;
441 bb_error_msg("using %s detection mode", method[i].name);
442 break;
443 }
Denis Vlasenko71c16572009-04-26 01:08:51 +0000444 }
Maxim Kryzhanovskyfcb84c82010-03-29 09:09:05 +0200445 return iface_status;
Denis Vlasenko71c16572009-04-26 01:08:51 +0000446}
447
448static smallint detect_link(void)
449{
450 smallint status;
451
452 if (!G.iface_exists)
453 return (option_mask32 & FLAG_MONITOR) ? IFSTATUS_DOWN : IFSTATUS_ERR;
454
Denys Vlasenkof422a722010-01-08 12:27:57 +0100455 /* Some drivers can't detect link status when the interface is down.
456 * I imagine detect_link_iff() is the most vulnerable.
457 * That's why -a "noauto" in an option, not a hardwired behavior.
458 */
Denis Vlasenko71c16572009-04-26 01:08:51 +0000459 if (!(option_mask32 & FLAG_NO_AUTO))
460 up_iface();
Denis Vlasenko71c16572009-04-26 01:08:51 +0000461
462 status = G.detect_link_func();
463 if (status == IFSTATUS_ERR) {
464 if (option_mask32 & FLAG_IGNORE_FAIL)
465 status = IFSTATUS_DOWN;
466 if (option_mask32 & FLAG_IGNORE_FAIL_POSITIVE)
467 status = IFSTATUS_UP;
468 }
469
470 if (status == IFSTATUS_ERR
471 && G.detect_link_func == detect_link_auto
472 ) {
Denys Vlasenko651a2692010-03-23 16:25:17 +0100473 bb_error_msg("can't detect link status");
Denis Vlasenko71c16572009-04-26 01:08:51 +0000474 }
475
476 if (status != G.iface_last_status) {
477//TODO: is it safe to repeatedly do this?
478 setenv(IFPLUGD_ENV_PREVIOUS, strstatus(G.iface_last_status), 1);
479 setenv(IFPLUGD_ENV_CURRENT, strstatus(status), 1);
480 G.iface_last_status = status;
481 }
482
483 return status;
484}
485
486static NOINLINE int check_existence_through_netlink(void)
487{
Denys Vlasenko37201212010-04-02 07:04:44 +0200488 int iface_len;
Denis Vlasenko71c16572009-04-26 01:08:51 +0000489 char replybuf[1024];
490
Denys Vlasenko37201212010-04-02 07:04:44 +0200491 iface_len = strlen(G.iface);
Denis Vlasenko71c16572009-04-26 01:08:51 +0000492 while (1) {
493 struct nlmsghdr *mhdr;
494 ssize_t bytes;
495
496 bytes = recv(netlink_fd, &replybuf, sizeof(replybuf), MSG_DONTWAIT);
497 if (bytes < 0) {
498 if (errno == EAGAIN)
499 return G.iface_exists;
500 if (errno == EINTR)
501 continue;
502
503 bb_perror_msg("netlink: recv");
504 return -1;
505 }
506
507 mhdr = (struct nlmsghdr*)replybuf;
508 while (bytes > 0) {
Maxim Kryzhanovsky2004fa12010-03-30 15:49:57 +0200509 if (!NLMSG_OK(mhdr, bytes)) {
Denis Vlasenko71c16572009-04-26 01:08:51 +0000510 bb_error_msg("netlink packet too small or truncated");
511 return -1;
512 }
513
514 if (mhdr->nlmsg_type == RTM_NEWLINK || mhdr->nlmsg_type == RTM_DELLINK) {
515 struct rtattr *attr;
Denis Vlasenko71c16572009-04-26 01:08:51 +0000516 int attr_len;
517
Denis Vlasenko71c16572009-04-26 01:08:51 +0000518 if (mhdr->nlmsg_len < NLMSG_LENGTH(sizeof(struct ifinfomsg))) {
519 bb_error_msg("netlink packet too small or truncated");
520 return -1;
521 }
522
Maxim Kryzhanovsky2004fa12010-03-30 15:49:57 +0200523 attr = IFLA_RTA(NLMSG_DATA(mhdr));
524 attr_len = IFLA_PAYLOAD(mhdr);
Denis Vlasenko71c16572009-04-26 01:08:51 +0000525
526 while (RTA_OK(attr, attr_len)) {
527 if (attr->rta_type == IFLA_IFNAME) {
Denis Vlasenko71c16572009-04-26 01:08:51 +0000528 int len = RTA_PAYLOAD(attr);
Denis Vlasenko71c16572009-04-26 01:08:51 +0000529 if (len > IFNAMSIZ)
530 len = IFNAMSIZ;
Denys Vlasenko37201212010-04-02 07:04:44 +0200531 if (iface_len <= len
532 && strncmp(G.iface, RTA_DATA(attr), len) == 0
533 ) {
Denis Vlasenko71c16572009-04-26 01:08:51 +0000534 G.iface_exists = (mhdr->nlmsg_type == RTM_NEWLINK);
535 }
536 }
537 attr = RTA_NEXT(attr, attr_len);
538 }
539 }
540
541 mhdr = NLMSG_NEXT(mhdr, bytes);
542 }
543 }
544
545 return G.iface_exists;
546}
547
548static NOINLINE int netlink_open(void)
549{
550 int fd;
551 struct sockaddr_nl addr;
552
553 fd = xsocket(PF_NETLINK, SOCK_DGRAM, NETLINK_ROUTE);
554
555 memset(&addr, 0, sizeof(addr));
556 addr.nl_family = AF_NETLINK;
557 addr.nl_groups = RTMGRP_LINK;
558 addr.nl_pid = getpid();
559
560 xbind(fd, (struct sockaddr*)&addr, sizeof(addr));
561
562 return fd;
563}
564
Denys Vlasenko9c35a1c2009-05-01 09:04:25 +0200565#if ENABLE_FEATURE_PIDFILE
Denis Vlasenko71c16572009-04-26 01:08:51 +0000566static NOINLINE pid_t read_pid(const char *filename)
567{
568 int len;
569 char buf[128];
570
571 len = open_read_close(filename, buf, 127);
572 if (len > 0) {
573 buf[len] = '\0';
574 /* returns ULONG_MAX on error => -1 */
575 return bb_strtoul(buf, NULL, 10);
576 }
577 return 0;
578}
Denys Vlasenko9c35a1c2009-05-01 09:04:25 +0200579#endif
Denis Vlasenko71c16572009-04-26 01:08:51 +0000580
581int ifplugd_main(int argc, char **argv) MAIN_EXTERNALLY_VISIBLE;
582int ifplugd_main(int argc UNUSED_PARAM, char **argv)
583{
584 int iface_status;
585 int delay_time;
586 const char *iface_status_str;
587 struct pollfd netlink_pollfd[1];
588 unsigned opts;
589#if ENABLE_FEATURE_PIDFILE
590 char *pidfile_name;
591 pid_t pid_from_pidfile;
592#endif
593
594 INIT_G();
595
596 opt_complementary = "t+:u+:d+";
597 opts = getopt32(argv, OPTION_STR,
598 &G.iface, &G.script_name, &G.poll_time, &G.delay_up,
599 &G.delay_down, &G.api_mode, &G.extra_arg);
Denys Vlasenko5a34d022009-11-07 17:30:14 +0100600 G.poll_time *= 1000;
Denis Vlasenko71c16572009-04-26 01:08:51 +0000601
602 applet_name = xasprintf("ifplugd(%s)", G.iface);
603
604#if ENABLE_FEATURE_PIDFILE
605 pidfile_name = xasprintf(_PATH_VARRUN"ifplugd.%s.pid", G.iface);
606 pid_from_pidfile = read_pid(pidfile_name);
607
608 if (opts & FLAG_KILL) {
609 if (pid_from_pidfile > 0)
610 kill(pid_from_pidfile, SIGQUIT);
611 return EXIT_SUCCESS;
612 }
613
614 if (pid_from_pidfile > 0 && kill(pid_from_pidfile, 0) == 0)
615 bb_error_msg_and_die("daemon already running");
616#endif
617
618 switch (G.api_mode[0]) {
619 case API_AUTO:
620 G.detect_link_func = detect_link_auto;
621 break;
622 case API_ETHTOOL:
623 G.detect_link_func = detect_link_ethtool;
624 break;
625 case API_MII:
626 G.detect_link_func = detect_link_mii;
627 break;
628 case API_PRIVATE:
629 G.detect_link_func = detect_link_priv;
630 break;
631 case API_WLAN:
632 G.detect_link_func = detect_link_wlan;
633 break;
634 case API_IFF:
635 G.detect_link_func = detect_link_iff;
636 break;
637 default:
638 bb_error_msg_and_die("unknown API mode '%s'", G.api_mode);
639 }
640
641 if (!(opts & FLAG_NO_DAEMON))
642 bb_daemonize_or_rexec(DAEMON_CHDIR_ROOT, argv);
643
644 xmove_fd(xsocket(AF_INET, SOCK_DGRAM, 0), ioctl_fd);
645 if (opts & FLAG_MONITOR) {
646 xmove_fd(netlink_open(), netlink_fd);
647 }
648
649 write_pidfile(pidfile_name);
650
651 /* this can't be moved before socket creation */
652 if (!(opts & FLAG_NO_SYSLOG)) {
653 openlog(applet_name, 0, LOG_DAEMON);
654 logmode |= LOGMODE_SYSLOG;
655 }
656
657 bb_signals(0
658 | (1 << SIGINT )
659 | (1 << SIGTERM)
660 | (1 << SIGQUIT)
661 | (1 << SIGHUP ) /* why we ignore it? */
662 /* | (1 << SIGCHLD) - run_script does not use it anymore */
663 , record_signo);
664
665 bb_error_msg("started: %s", bb_banner);
666
667 if (opts & FLAG_MONITOR) {
668 struct ifreq ifrequest;
669 set_ifreq_to_ifname(&ifrequest);
Denys Vlasenkof422a722010-01-08 12:27:57 +0100670 G.iface_exists = (network_ioctl(SIOCGIFINDEX, &ifrequest, NULL) == 0);
Denis Vlasenko71c16572009-04-26 01:08:51 +0000671 }
672
673 if (G.iface_exists)
674 maybe_up_new_iface();
675
676 iface_status = detect_link();
677 if (iface_status == IFSTATUS_ERR)
678 goto exiting;
679 iface_status_str = strstatus(iface_status);
680
681 if (opts & FLAG_MONITOR) {
682 bb_error_msg("interface %s",
683 G.iface_exists ? "exists"
684 : "doesn't exist, waiting");
685 }
686 /* else we assume it always exists, but don't mislead user
687 * by potentially lying that it really exists */
688
689 if (G.iface_exists) {
690 bb_error_msg("link is %s", iface_status_str);
691 }
692
693 if ((!(opts & FLAG_NO_STARTUP)
694 && iface_status == IFSTATUS_UP
695 )
696 || (opts & FLAG_INITIAL_DOWN)
697 ) {
698 if (run_script(iface_status_str) != 0)
699 goto exiting;
700 }
701
702 /* Main loop */
703 netlink_pollfd[0].fd = netlink_fd;
704 netlink_pollfd[0].events = POLLIN;
705 delay_time = 0;
706 while (1) {
707 int iface_status_old;
708 int iface_exists_old;
709
710 switch (bb_got_signal) {
711 case SIGINT:
712 case SIGTERM:
713 bb_got_signal = 0;
714 goto cleanup;
715 case SIGQUIT:
716 bb_got_signal = 0;
717 goto exiting;
718 default:
719 bb_got_signal = 0;
720 break;
721 }
722
723 if (poll(netlink_pollfd,
724 (opts & FLAG_MONITOR) ? 1 : 0,
Denys Vlasenko5a34d022009-11-07 17:30:14 +0100725 G.poll_time
Denis Vlasenko71c16572009-04-26 01:08:51 +0000726 ) < 0
727 ) {
728 if (errno == EINTR)
729 continue;
730 bb_perror_msg("poll");
731 goto exiting;
732 }
733
734 iface_status_old = iface_status;
735 iface_exists_old = G.iface_exists;
736
737 if ((opts & FLAG_MONITOR)
738 && (netlink_pollfd[0].revents & POLLIN)
739 ) {
740 G.iface_exists = check_existence_through_netlink();
741 if (G.iface_exists < 0) /* error */
742 goto exiting;
743 if (iface_exists_old != G.iface_exists) {
744 bb_error_msg("interface %sappeared",
745 G.iface_exists ? "" : "dis");
746 if (G.iface_exists)
747 maybe_up_new_iface();
748 }
749 }
750
751 /* note: if !G.iface_exists, returns DOWN */
752 iface_status = detect_link();
753 if (iface_status == IFSTATUS_ERR) {
754 if (!(opts & FLAG_MONITOR))
755 goto exiting;
756 iface_status = IFSTATUS_DOWN;
757 }
758 iface_status_str = strstatus(iface_status);
759
760 if (iface_status_old != iface_status) {
761 bb_error_msg("link is %s", iface_status_str);
762
763 if (delay_time) {
764 /* link restored its old status before
765 * we run script. don't run the script: */
766 delay_time = 0;
767 } else {
768 delay_time = monotonic_sec();
769 if (iface_status == IFSTATUS_UP)
770 delay_time += G.delay_up;
771 if (iface_status == IFSTATUS_DOWN)
772 delay_time += G.delay_down;
773 if (delay_time == 0)
774 delay_time++;
775 }
776 }
777
778 if (delay_time && (int)(monotonic_sec() - delay_time) >= 0) {
779 delay_time = 0;
780 if (run_script(iface_status_str) != 0)
781 goto exiting;
782 }
783 } /* while (1) */
784
785 cleanup:
786 if (!(opts & FLAG_NO_SHUTDOWN)
787 && (iface_status == IFSTATUS_UP
788 || (iface_status == IFSTATUS_DOWN && delay_time)
789 )
790 ) {
791 setenv(IFPLUGD_ENV_PREVIOUS, strstatus(iface_status), 1);
792 setenv(IFPLUGD_ENV_CURRENT, strstatus(-1), 1);
793 run_script("down\0up"); /* reusing string */
794 }
795
796 exiting:
797 remove_pidfile(pidfile_name);
798 bb_error_msg_and_die("exiting");
799}