blob: 8cb07db5d88e72150e1245ea0aa7d8dbb6ab0bdf [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;
Denys Vlasenko19afe842010-05-08 23:26:16 +020096 smallint iface_prev_status;
Denis Vlasenko71c16572009-04-26 01:08:51 +000097 smallint iface_exists;
98
99 /* Used in getopt32, must have sizeof == sizeof(int) */
100 unsigned poll_time;
101 unsigned delay_up;
102 unsigned delay_down;
103
104 const char *iface;
105 const char *api_mode;
106 const char *script_name;
107 const char *extra_arg;
108
109 smallint (*detect_link_func)(void);
110 smallint (*cached_detect_link_func)(void);
111};
112#define G (*ptr_to_globals)
113#define INIT_G() do { \
114 SET_PTR_TO_GLOBALS(xzalloc(sizeof(G))); \
115 G.iface_last_status = -1; \
116 G.iface_exists = 1; \
117 G.poll_time = 1; \
118 G.delay_down = 5; \
119 G.iface = "eth0"; \
120 G.api_mode = "a"; \
121 G.script_name = "/etc/ifplugd/ifplugd.action"; \
122} while (0)
123
124
Denys Vlasenko19afe842010-05-08 23:26:16 +0200125static const char *strstatus(int status)
126{
127 if (status == IFSTATUS_ERR)
128 return "error";
129 return "down\0up" + (status * 5);
130}
131
Denis Vlasenko71c16572009-04-26 01:08:51 +0000132static int run_script(const char *action)
133{
Denys Vlasenko19afe842010-05-08 23:26:16 +0200134 char *env_PREVIOUS, *env_CURRENT;
Denys Vlasenko79ae5342010-01-06 12:27:18 +0100135 char *argv[5];
Denis Vlasenko71c16572009-04-26 01:08:51 +0000136 int r;
137
138 bb_error_msg("executing '%s %s %s'", G.script_name, G.iface, action);
139
Denys Vlasenko79ae5342010-01-06 12:27:18 +0100140 argv[0] = (char*) G.script_name;
141 argv[1] = (char*) G.iface;
142 argv[2] = (char*) action;
143 argv[3] = (char*) G.extra_arg;
144 argv[4] = NULL;
Denis Vlasenko71c16572009-04-26 01:08:51 +0000145
Denys Vlasenko19afe842010-05-08 23:26:16 +0200146 env_PREVIOUS = xasprintf("%s=%s", IFPLUGD_ENV_PREVIOUS, strstatus(G.iface_prev_status));
147 putenv(env_PREVIOUS);
Denys Vlasenko78d035f2010-05-11 04:37:13 +0200148 env_CURRENT = xasprintf("%s=%s", IFPLUGD_ENV_CURRENT, strstatus(G.iface_last_status));
Denys Vlasenko19afe842010-05-08 23:26:16 +0200149 putenv(env_CURRENT);
150
Denys Vlasenko8531d762010-03-18 22:44:00 +0100151 /* r < 0 - can't exec, 0 <= r < 0x180 - exited, >=0x180 - killed by sig (r-0x180) */
152 r = spawn_and_wait(argv);
Denis Vlasenko71c16572009-04-26 01:08:51 +0000153
Denys Vlasenko19afe842010-05-08 23:26:16 +0200154 unsetenv(IFPLUGD_ENV_PREVIOUS);
155 unsetenv(IFPLUGD_ENV_CURRENT);
156 free(env_PREVIOUS);
157 free(env_CURRENT);
158
Denys Vlasenko8531d762010-03-18 22:44:00 +0100159 bb_error_msg("exit code: %d", r & 0xff);
Denis Vlasenko71c16572009-04-26 01:08:51 +0000160 return (option_mask32 & FLAG_IGNORE_RETVAL) ? 0 : r;
Denis Vlasenko71c16572009-04-26 01:08:51 +0000161}
162
Denys Vlasenkof422a722010-01-08 12:27:57 +0100163static int network_ioctl(int request, void* data, const char *errmsg)
Denis Vlasenko71c16572009-04-26 01:08:51 +0000164{
Denys Vlasenkof422a722010-01-08 12:27:57 +0100165 int r = ioctl(ioctl_fd, request, data);
166 if (r < 0 && errmsg)
Maxim Kryzhanovskyfcb84c82010-03-29 09:09:05 +0200167 bb_perror_msg("%s failed", errmsg);
Denys Vlasenkof422a722010-01-08 12:27:57 +0100168 return r;
Denis Vlasenko71c16572009-04-26 01:08:51 +0000169}
170
171static void set_ifreq_to_ifname(struct ifreq *ifreq)
172{
173 memset(ifreq, 0, sizeof(struct ifreq));
Denis Vlasenko80e57eb2009-04-26 01:43:36 +0000174 strncpy_IFNAMSIZ(ifreq->ifr_name, G.iface);
Denis Vlasenko71c16572009-04-26 01:08:51 +0000175}
176
Denis Vlasenko71c16572009-04-26 01:08:51 +0000177static void up_iface(void)
178{
179 struct ifreq ifrequest;
180
181 if (!G.iface_exists)
182 return;
183
184 set_ifreq_to_ifname(&ifrequest);
Maxim Kryzhanovskyfcb84c82010-03-29 09:09:05 +0200185 if (network_ioctl(SIOCGIFFLAGS, &ifrequest, "getting interface flags") < 0) {
Denis Vlasenko71c16572009-04-26 01:08:51 +0000186 G.iface_exists = 0;
187 return;
188 }
189
190 if (!(ifrequest.ifr_flags & IFF_UP)) {
191 ifrequest.ifr_flags |= IFF_UP;
192 /* Let user know we mess up with interface */
193 bb_error_msg("upping interface");
Maxim Kryzhanovskyfcb84c82010-03-29 09:09:05 +0200194 if (network_ioctl(SIOCSIFFLAGS, &ifrequest, "setting interface flags") < 0)
Denys Vlasenkof422a722010-01-08 12:27:57 +0100195 xfunc_die();
Denis Vlasenko71c16572009-04-26 01:08:51 +0000196 }
197
198#if 0 /* why do we mess with IP addr? It's not our business */
Denys Vlasenkof422a722010-01-08 12:27:57 +0100199 if (network_ioctl(SIOCGIFADDR, &ifrequest, "can't get interface address") < 0) {
Denis Vlasenko71c16572009-04-26 01:08:51 +0000200 } else if (ifrequest.ifr_addr.sa_family != AF_INET) {
Denis Vlasenko1fd3b382009-04-29 12:02:57 +0000201 bb_perror_msg("the interface is not IP-based");
Denis Vlasenko71c16572009-04-26 01:08:51 +0000202 } else {
203 ((struct sockaddr_in*)(&ifrequest.ifr_addr))->sin_addr.s_addr = INADDR_ANY;
Denys Vlasenkof422a722010-01-08 12:27:57 +0100204 network_ioctl(SIOCSIFADDR, &ifrequest, "can't set interface address");
Denis Vlasenko71c16572009-04-26 01:08:51 +0000205 }
Denys Vlasenkof422a722010-01-08 12:27:57 +0100206 network_ioctl(SIOCGIFFLAGS, &ifrequest, "can't get interface flags");
Denis Vlasenko71c16572009-04-26 01:08:51 +0000207#endif
208}
209
210static void maybe_up_new_iface(void)
211{
212 if (!(option_mask32 & FLAG_NO_AUTO))
213 up_iface();
214
215#if 0 /* bloat */
216 struct ifreq ifrequest;
217 struct ethtool_drvinfo driver_info;
218
219 set_ifreq_to_ifname(&ifrequest);
220 driver_info.cmd = ETHTOOL_GDRVINFO;
221 ifrequest.ifr_data = &driver_info;
Denys Vlasenkof422a722010-01-08 12:27:57 +0100222 if (network_ioctl(SIOCETHTOOL, &ifrequest, NULL) == 0) {
Denis Vlasenko71c16572009-04-26 01:08:51 +0000223 char buf[sizeof("/xx:xx:xx:xx:xx:xx")];
224
225 /* Get MAC */
226 buf[0] = '\0';
227 set_ifreq_to_ifname(&ifrequest);
Denys Vlasenkof422a722010-01-08 12:27:57 +0100228 if (network_ioctl(SIOCGIFHWADDR, &ifrequest, NULL) == 0) {
Denis Vlasenko71c16572009-04-26 01:08:51 +0000229 sprintf(buf, "/%02X:%02X:%02X:%02X:%02X:%02X",
230 (uint8_t)(ifrequest.ifr_hwaddr.sa_data[0]),
231 (uint8_t)(ifrequest.ifr_hwaddr.sa_data[1]),
232 (uint8_t)(ifrequest.ifr_hwaddr.sa_data[2]),
233 (uint8_t)(ifrequest.ifr_hwaddr.sa_data[3]),
234 (uint8_t)(ifrequest.ifr_hwaddr.sa_data[4]),
235 (uint8_t)(ifrequest.ifr_hwaddr.sa_data[5]));
236 }
237
Denis Vlasenko1fd3b382009-04-29 12:02:57 +0000238 bb_error_msg("using interface %s%s with driver<%s> (version: %s)",
Denis Vlasenko71c16572009-04-26 01:08:51 +0000239 G.iface, buf, driver_info.driver, driver_info.version);
240 }
241#endif
242
243 G.cached_detect_link_func = NULL;
244}
245
246static smallint detect_link_mii(void)
247{
248 struct ifreq ifreq;
Denys Vlasenko9c35a1c2009-05-01 09:04:25 +0200249 struct mii_ioctl_data *mii = (void *)&ifreq.ifr_data;
Denis Vlasenko71c16572009-04-26 01:08:51 +0000250
251 set_ifreq_to_ifname(&ifreq);
252
Maxim Kryzhanovskyfcb84c82010-03-29 09:09:05 +0200253 if (network_ioctl(SIOCGMIIPHY, &ifreq, "SIOCGMIIPHY") < 0) {
Denis Vlasenko71c16572009-04-26 01:08:51 +0000254 return IFSTATUS_ERR;
255 }
256
Denys Vlasenko9c35a1c2009-05-01 09:04:25 +0200257 mii->reg_num = 1;
Denis Vlasenko71c16572009-04-26 01:08:51 +0000258
Maxim Kryzhanovskyfcb84c82010-03-29 09:09:05 +0200259 if (network_ioctl(SIOCGMIIREG, &ifreq, "SIOCGMIIREG") < 0) {
Denis Vlasenko71c16572009-04-26 01:08:51 +0000260 return IFSTATUS_ERR;
261 }
262
Denys Vlasenko9c35a1c2009-05-01 09:04:25 +0200263 return (mii->val_out & 0x0004) ? IFSTATUS_UP : IFSTATUS_DOWN;
Denis Vlasenko71c16572009-04-26 01:08:51 +0000264}
265
266static smallint detect_link_priv(void)
267{
268 struct ifreq ifreq;
Denys Vlasenko9c35a1c2009-05-01 09:04:25 +0200269 struct mii_ioctl_data *mii = (void *)&ifreq.ifr_data;
Denis Vlasenko71c16572009-04-26 01:08:51 +0000270
271 set_ifreq_to_ifname(&ifreq);
272
Maxim Kryzhanovskyfcb84c82010-03-29 09:09:05 +0200273 if (network_ioctl(SIOCDEVPRIVATE, &ifreq, "SIOCDEVPRIVATE") < 0) {
Denis Vlasenko71c16572009-04-26 01:08:51 +0000274 return IFSTATUS_ERR;
275 }
276
Denys Vlasenko9c35a1c2009-05-01 09:04:25 +0200277 mii->reg_num = 1;
Denis Vlasenko71c16572009-04-26 01:08:51 +0000278
Maxim Kryzhanovskyfcb84c82010-03-29 09:09:05 +0200279 if (network_ioctl(SIOCDEVPRIVATE+1, &ifreq, "SIOCDEVPRIVATE+1") < 0) {
Denis Vlasenko71c16572009-04-26 01:08:51 +0000280 return IFSTATUS_ERR;
281 }
282
Denys Vlasenko9c35a1c2009-05-01 09:04:25 +0200283 return (mii->val_out & 0x0004) ? IFSTATUS_UP : IFSTATUS_DOWN;
Denis Vlasenko71c16572009-04-26 01:08:51 +0000284}
285
286static smallint detect_link_ethtool(void)
287{
288 struct ifreq ifreq;
289 struct ethtool_value edata;
290
291 set_ifreq_to_ifname(&ifreq);
292
293 edata.cmd = ETHTOOL_GLINK;
Denys Vlasenko337a31b2009-10-23 18:31:02 +0200294 ifreq.ifr_data = (void*) &edata;
Denis Vlasenko71c16572009-04-26 01:08:51 +0000295
Maxim Kryzhanovskyfcb84c82010-03-29 09:09:05 +0200296 if (network_ioctl(SIOCETHTOOL, &ifreq, "ETHTOOL_GLINK") < 0) {
Denis Vlasenko71c16572009-04-26 01:08:51 +0000297 return IFSTATUS_ERR;
298 }
299
300 return edata.data ? IFSTATUS_UP : IFSTATUS_DOWN;
301}
302
303static smallint detect_link_iff(void)
304{
305 struct ifreq ifreq;
306
307 set_ifreq_to_ifname(&ifreq);
308
Maxim Kryzhanovskyfcb84c82010-03-29 09:09:05 +0200309 if (network_ioctl(SIOCGIFFLAGS, &ifreq, "SIOCGIFFLAGS") < 0) {
Denis Vlasenko71c16572009-04-26 01:08:51 +0000310 return IFSTATUS_ERR;
311 }
312
Denys Vlasenkof422a722010-01-08 12:27:57 +0100313 /* If IFF_UP is not set (interface is down), IFF_RUNNING is never set
314 * regardless of link status. Simply continue to report last status -
315 * no point in reporting spurious link downs if interface is disabled
316 * by admin. When/if it will be brought up,
317 * we'll report real link status.
318 */
319 if (!(ifreq.ifr_flags & IFF_UP) && G.iface_last_status != IFSTATUS_ERR)
320 return G.iface_last_status;
321
Denis Vlasenko71c16572009-04-26 01:08:51 +0000322 return (ifreq.ifr_flags & IFF_RUNNING) ? IFSTATUS_UP : IFSTATUS_DOWN;
323}
324
325static smallint detect_link_wlan(void)
326{
Maxim Kryzhanovskyfcb84c82010-03-29 09:09:05 +0200327 int i;
Denis Vlasenko71c16572009-04-26 01:08:51 +0000328 struct iwreq iwrequest;
329 uint8_t mac[ETH_ALEN];
330
Maxim Kryzhanovskyfcb84c82010-03-29 09:09:05 +0200331 memset(&iwrequest, 0, sizeof(iwrequest));
Denis Vlasenko80e57eb2009-04-26 01:43:36 +0000332 strncpy_IFNAMSIZ(iwrequest.ifr_ifrn.ifrn_name, G.iface);
Denis Vlasenko71c16572009-04-26 01:08:51 +0000333
Maxim Kryzhanovskyfcb84c82010-03-29 09:09:05 +0200334 if (network_ioctl(SIOCGIWAP, &iwrequest, "SIOCGIWAP") < 0) {
Denis Vlasenko71c16572009-04-26 01:08:51 +0000335 return IFSTATUS_ERR;
336 }
337
Maxim Kryzhanovskyfcb84c82010-03-29 09:09:05 +0200338 memcpy(mac, &iwrequest.u.ap_addr.sa_data, ETH_ALEN);
Denis Vlasenko71c16572009-04-26 01:08:51 +0000339
340 if (mac[0] == 0xFF || mac[0] == 0x44 || mac[0] == 0x00) {
Maxim Kryzhanovskyfcb84c82010-03-29 09:09:05 +0200341 for (i = 1; i < ETH_ALEN; ++i) {
Denis Vlasenko71c16572009-04-26 01:08:51 +0000342 if (mac[i] != mac[0])
343 return IFSTATUS_UP;
344 }
345 return IFSTATUS_DOWN;
346 }
347
348 return IFSTATUS_UP;
349}
350
351static smallint detect_link_auto(void)
352{
Maxim Kryzhanovskyfcb84c82010-03-29 09:09:05 +0200353 static const struct {
354 const char *name;
355 smallint (*func)(void);
356 } method[] = {
357 { "SIOCETHTOOL" , &detect_link_ethtool },
358 { "SIOCGMIIPHY" , &detect_link_mii },
359 { "SIOCDEVPRIVATE" , &detect_link_priv },
360 { "wireless extension", &detect_link_wlan },
361 { "IFF_RUNNING" , &detect_link_iff },
362 };
363 int i;
Denis Vlasenko71c16572009-04-26 01:08:51 +0000364 smallint iface_status;
365 smallint sv_logmode;
366
367 if (G.cached_detect_link_func) {
368 iface_status = G.cached_detect_link_func();
369 if (iface_status != IFSTATUS_ERR)
370 return iface_status;
371 }
372
373 sv_logmode = logmode;
Maxim Kryzhanovskyfcb84c82010-03-29 09:09:05 +0200374 for (i = 0; i < ARRAY_SIZE(method); i++) {
375 logmode = LOGMODE_NONE;
376 iface_status = method[i].func();
Denis Vlasenko71c16572009-04-26 01:08:51 +0000377 logmode = sv_logmode;
Maxim Kryzhanovskyfcb84c82010-03-29 09:09:05 +0200378 if (iface_status != IFSTATUS_ERR) {
379 G.cached_detect_link_func = method[i].func;
380 bb_error_msg("using %s detection mode", method[i].name);
381 break;
382 }
Denis Vlasenko71c16572009-04-26 01:08:51 +0000383 }
Maxim Kryzhanovskyfcb84c82010-03-29 09:09:05 +0200384 return iface_status;
Denis Vlasenko71c16572009-04-26 01:08:51 +0000385}
386
387static smallint detect_link(void)
388{
389 smallint status;
390
391 if (!G.iface_exists)
392 return (option_mask32 & FLAG_MONITOR) ? IFSTATUS_DOWN : IFSTATUS_ERR;
393
Denys Vlasenkof422a722010-01-08 12:27:57 +0100394 /* Some drivers can't detect link status when the interface is down.
395 * I imagine detect_link_iff() is the most vulnerable.
396 * That's why -a "noauto" in an option, not a hardwired behavior.
397 */
Denis Vlasenko71c16572009-04-26 01:08:51 +0000398 if (!(option_mask32 & FLAG_NO_AUTO))
399 up_iface();
Denis Vlasenko71c16572009-04-26 01:08:51 +0000400
401 status = G.detect_link_func();
402 if (status == IFSTATUS_ERR) {
403 if (option_mask32 & FLAG_IGNORE_FAIL)
404 status = IFSTATUS_DOWN;
405 if (option_mask32 & FLAG_IGNORE_FAIL_POSITIVE)
406 status = IFSTATUS_UP;
407 }
408
409 if (status == IFSTATUS_ERR
410 && G.detect_link_func == detect_link_auto
411 ) {
Denys Vlasenko651a2692010-03-23 16:25:17 +0100412 bb_error_msg("can't detect link status");
Denis Vlasenko71c16572009-04-26 01:08:51 +0000413 }
414
415 if (status != G.iface_last_status) {
Denys Vlasenko19afe842010-05-08 23:26:16 +0200416 G.iface_prev_status = G.iface_last_status;
Denis Vlasenko71c16572009-04-26 01:08:51 +0000417 G.iface_last_status = status;
418 }
419
420 return status;
421}
422
423static NOINLINE int check_existence_through_netlink(void)
424{
Denys Vlasenko37201212010-04-02 07:04:44 +0200425 int iface_len;
Denis Vlasenko71c16572009-04-26 01:08:51 +0000426 char replybuf[1024];
427
Denys Vlasenko37201212010-04-02 07:04:44 +0200428 iface_len = strlen(G.iface);
Denis Vlasenko71c16572009-04-26 01:08:51 +0000429 while (1) {
430 struct nlmsghdr *mhdr;
431 ssize_t bytes;
432
433 bytes = recv(netlink_fd, &replybuf, sizeof(replybuf), MSG_DONTWAIT);
434 if (bytes < 0) {
435 if (errno == EAGAIN)
436 return G.iface_exists;
437 if (errno == EINTR)
438 continue;
439
440 bb_perror_msg("netlink: recv");
441 return -1;
442 }
443
444 mhdr = (struct nlmsghdr*)replybuf;
445 while (bytes > 0) {
Maxim Kryzhanovsky2004fa12010-03-30 15:49:57 +0200446 if (!NLMSG_OK(mhdr, bytes)) {
Denis Vlasenko71c16572009-04-26 01:08:51 +0000447 bb_error_msg("netlink packet too small or truncated");
448 return -1;
449 }
450
451 if (mhdr->nlmsg_type == RTM_NEWLINK || mhdr->nlmsg_type == RTM_DELLINK) {
452 struct rtattr *attr;
Denis Vlasenko71c16572009-04-26 01:08:51 +0000453 int attr_len;
454
Denis Vlasenko71c16572009-04-26 01:08:51 +0000455 if (mhdr->nlmsg_len < NLMSG_LENGTH(sizeof(struct ifinfomsg))) {
456 bb_error_msg("netlink packet too small or truncated");
457 return -1;
458 }
459
Maxim Kryzhanovsky2004fa12010-03-30 15:49:57 +0200460 attr = IFLA_RTA(NLMSG_DATA(mhdr));
461 attr_len = IFLA_PAYLOAD(mhdr);
Denis Vlasenko71c16572009-04-26 01:08:51 +0000462
463 while (RTA_OK(attr, attr_len)) {
464 if (attr->rta_type == IFLA_IFNAME) {
Denis Vlasenko71c16572009-04-26 01:08:51 +0000465 int len = RTA_PAYLOAD(attr);
Denis Vlasenko71c16572009-04-26 01:08:51 +0000466 if (len > IFNAMSIZ)
467 len = IFNAMSIZ;
Denys Vlasenko37201212010-04-02 07:04:44 +0200468 if (iface_len <= len
469 && strncmp(G.iface, RTA_DATA(attr), len) == 0
470 ) {
Denis Vlasenko71c16572009-04-26 01:08:51 +0000471 G.iface_exists = (mhdr->nlmsg_type == RTM_NEWLINK);
472 }
473 }
474 attr = RTA_NEXT(attr, attr_len);
475 }
476 }
477
478 mhdr = NLMSG_NEXT(mhdr, bytes);
479 }
480 }
481
482 return G.iface_exists;
483}
484
485static NOINLINE int netlink_open(void)
486{
487 int fd;
488 struct sockaddr_nl addr;
489
490 fd = xsocket(PF_NETLINK, SOCK_DGRAM, NETLINK_ROUTE);
491
492 memset(&addr, 0, sizeof(addr));
493 addr.nl_family = AF_NETLINK;
494 addr.nl_groups = RTMGRP_LINK;
495 addr.nl_pid = getpid();
496
497 xbind(fd, (struct sockaddr*)&addr, sizeof(addr));
498
499 return fd;
500}
501
Denys Vlasenko9c35a1c2009-05-01 09:04:25 +0200502#if ENABLE_FEATURE_PIDFILE
Denis Vlasenko71c16572009-04-26 01:08:51 +0000503static NOINLINE pid_t read_pid(const char *filename)
504{
505 int len;
506 char buf[128];
507
508 len = open_read_close(filename, buf, 127);
509 if (len > 0) {
510 buf[len] = '\0';
511 /* returns ULONG_MAX on error => -1 */
512 return bb_strtoul(buf, NULL, 10);
513 }
514 return 0;
515}
Denys Vlasenko9c35a1c2009-05-01 09:04:25 +0200516#endif
Denis Vlasenko71c16572009-04-26 01:08:51 +0000517
518int ifplugd_main(int argc, char **argv) MAIN_EXTERNALLY_VISIBLE;
519int ifplugd_main(int argc UNUSED_PARAM, char **argv)
520{
521 int iface_status;
522 int delay_time;
523 const char *iface_status_str;
524 struct pollfd netlink_pollfd[1];
525 unsigned opts;
526#if ENABLE_FEATURE_PIDFILE
527 char *pidfile_name;
528 pid_t pid_from_pidfile;
529#endif
530
531 INIT_G();
532
533 opt_complementary = "t+:u+:d+";
534 opts = getopt32(argv, OPTION_STR,
535 &G.iface, &G.script_name, &G.poll_time, &G.delay_up,
536 &G.delay_down, &G.api_mode, &G.extra_arg);
Denys Vlasenko5a34d022009-11-07 17:30:14 +0100537 G.poll_time *= 1000;
Denis Vlasenko71c16572009-04-26 01:08:51 +0000538
539 applet_name = xasprintf("ifplugd(%s)", G.iface);
540
541#if ENABLE_FEATURE_PIDFILE
542 pidfile_name = xasprintf(_PATH_VARRUN"ifplugd.%s.pid", G.iface);
543 pid_from_pidfile = read_pid(pidfile_name);
544
545 if (opts & FLAG_KILL) {
546 if (pid_from_pidfile > 0)
547 kill(pid_from_pidfile, SIGQUIT);
548 return EXIT_SUCCESS;
549 }
550
551 if (pid_from_pidfile > 0 && kill(pid_from_pidfile, 0) == 0)
552 bb_error_msg_and_die("daemon already running");
553#endif
554
555 switch (G.api_mode[0]) {
556 case API_AUTO:
557 G.detect_link_func = detect_link_auto;
558 break;
559 case API_ETHTOOL:
560 G.detect_link_func = detect_link_ethtool;
561 break;
562 case API_MII:
563 G.detect_link_func = detect_link_mii;
564 break;
565 case API_PRIVATE:
566 G.detect_link_func = detect_link_priv;
567 break;
568 case API_WLAN:
569 G.detect_link_func = detect_link_wlan;
570 break;
571 case API_IFF:
572 G.detect_link_func = detect_link_iff;
573 break;
574 default:
575 bb_error_msg_and_die("unknown API mode '%s'", G.api_mode);
576 }
577
578 if (!(opts & FLAG_NO_DAEMON))
579 bb_daemonize_or_rexec(DAEMON_CHDIR_ROOT, argv);
580
581 xmove_fd(xsocket(AF_INET, SOCK_DGRAM, 0), ioctl_fd);
582 if (opts & FLAG_MONITOR) {
583 xmove_fd(netlink_open(), netlink_fd);
584 }
585
586 write_pidfile(pidfile_name);
587
588 /* this can't be moved before socket creation */
589 if (!(opts & FLAG_NO_SYSLOG)) {
590 openlog(applet_name, 0, LOG_DAEMON);
591 logmode |= LOGMODE_SYSLOG;
592 }
593
594 bb_signals(0
595 | (1 << SIGINT )
596 | (1 << SIGTERM)
597 | (1 << SIGQUIT)
598 | (1 << SIGHUP ) /* why we ignore it? */
599 /* | (1 << SIGCHLD) - run_script does not use it anymore */
600 , record_signo);
601
602 bb_error_msg("started: %s", bb_banner);
603
604 if (opts & FLAG_MONITOR) {
605 struct ifreq ifrequest;
606 set_ifreq_to_ifname(&ifrequest);
Denys Vlasenkof422a722010-01-08 12:27:57 +0100607 G.iface_exists = (network_ioctl(SIOCGIFINDEX, &ifrequest, NULL) == 0);
Denis Vlasenko71c16572009-04-26 01:08:51 +0000608 }
609
610 if (G.iface_exists)
611 maybe_up_new_iface();
612
613 iface_status = detect_link();
614 if (iface_status == IFSTATUS_ERR)
615 goto exiting;
616 iface_status_str = strstatus(iface_status);
617
618 if (opts & FLAG_MONITOR) {
619 bb_error_msg("interface %s",
620 G.iface_exists ? "exists"
621 : "doesn't exist, waiting");
622 }
623 /* else we assume it always exists, but don't mislead user
624 * by potentially lying that it really exists */
625
626 if (G.iface_exists) {
627 bb_error_msg("link is %s", iface_status_str);
628 }
629
630 if ((!(opts & FLAG_NO_STARTUP)
631 && iface_status == IFSTATUS_UP
632 )
633 || (opts & FLAG_INITIAL_DOWN)
634 ) {
635 if (run_script(iface_status_str) != 0)
636 goto exiting;
637 }
638
639 /* Main loop */
640 netlink_pollfd[0].fd = netlink_fd;
641 netlink_pollfd[0].events = POLLIN;
642 delay_time = 0;
643 while (1) {
644 int iface_status_old;
645 int iface_exists_old;
646
647 switch (bb_got_signal) {
648 case SIGINT:
649 case SIGTERM:
650 bb_got_signal = 0;
651 goto cleanup;
652 case SIGQUIT:
653 bb_got_signal = 0;
654 goto exiting;
655 default:
656 bb_got_signal = 0;
657 break;
658 }
659
660 if (poll(netlink_pollfd,
661 (opts & FLAG_MONITOR) ? 1 : 0,
Denys Vlasenko5a34d022009-11-07 17:30:14 +0100662 G.poll_time
Denis Vlasenko71c16572009-04-26 01:08:51 +0000663 ) < 0
664 ) {
665 if (errno == EINTR)
666 continue;
667 bb_perror_msg("poll");
668 goto exiting;
669 }
670
671 iface_status_old = iface_status;
672 iface_exists_old = G.iface_exists;
673
674 if ((opts & FLAG_MONITOR)
675 && (netlink_pollfd[0].revents & POLLIN)
676 ) {
677 G.iface_exists = check_existence_through_netlink();
678 if (G.iface_exists < 0) /* error */
679 goto exiting;
680 if (iface_exists_old != G.iface_exists) {
681 bb_error_msg("interface %sappeared",
682 G.iface_exists ? "" : "dis");
683 if (G.iface_exists)
684 maybe_up_new_iface();
685 }
686 }
687
688 /* note: if !G.iface_exists, returns DOWN */
689 iface_status = detect_link();
690 if (iface_status == IFSTATUS_ERR) {
691 if (!(opts & FLAG_MONITOR))
692 goto exiting;
693 iface_status = IFSTATUS_DOWN;
694 }
695 iface_status_str = strstatus(iface_status);
696
697 if (iface_status_old != iface_status) {
698 bb_error_msg("link is %s", iface_status_str);
699
700 if (delay_time) {
701 /* link restored its old status before
702 * we run script. don't run the script: */
703 delay_time = 0;
704 } else {
705 delay_time = monotonic_sec();
706 if (iface_status == IFSTATUS_UP)
707 delay_time += G.delay_up;
708 if (iface_status == IFSTATUS_DOWN)
709 delay_time += G.delay_down;
710 if (delay_time == 0)
711 delay_time++;
712 }
713 }
714
715 if (delay_time && (int)(monotonic_sec() - delay_time) >= 0) {
716 delay_time = 0;
717 if (run_script(iface_status_str) != 0)
718 goto exiting;
719 }
720 } /* while (1) */
721
722 cleanup:
723 if (!(opts & FLAG_NO_SHUTDOWN)
724 && (iface_status == IFSTATUS_UP
725 || (iface_status == IFSTATUS_DOWN && delay_time)
726 )
727 ) {
728 setenv(IFPLUGD_ENV_PREVIOUS, strstatus(iface_status), 1);
729 setenv(IFPLUGD_ENV_CURRENT, strstatus(-1), 1);
730 run_script("down\0up"); /* reusing string */
731 }
732
733 exiting:
734 remove_pidfile(pidfile_name);
735 bb_error_msg_and_die("exiting");
736}