Contents of /tags/mkinitrd-6_2_1/busybox/networking/ifplugd.c
Parent Directory | Revision Log
Revision 999 -
(show annotations)
(download)
Sun May 30 12:16:23 2010 UTC (14 years, 3 months ago) by niro
File MIME type: text/plain
File size: 19778 byte(s)
Sun May 30 12:16:23 2010 UTC (14 years, 3 months ago) by niro
File MIME type: text/plain
File size: 19778 byte(s)
tagged 'mkinitrd-6_2_1'
1 | /* vi: set sw=4 ts=4: */ |
2 | /* |
3 | * ifplugd for busybox |
4 | * |
5 | * Copyright (C) 2009 Maksym Kryzhanovskyy <xmaks@email.cz> |
6 | * |
7 | * Licensed under GPLv2 or later, see file LICENSE in this tarball for details. |
8 | */ |
9 | #include "libbb.h" |
10 | |
11 | #include "fix_u32.h" |
12 | #include <linux/if.h> |
13 | #include <linux/mii.h> |
14 | #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 | |
24 | /* |
25 | TODO: describe compat status here. |
26 | |
27 | One questionable point of the design is netlink usage: |
28 | |
29 | We have 1 second timeout by default to poll the link status, |
30 | it is short enough so that there are no real benefits in |
31 | using netlink to get "instantaneous" interface creation/deletion |
32 | notifications. We can check for interface existence by just |
33 | doing some fast ioctl using its name. |
34 | |
35 | Netlink code then can be just dropped (1k or more?) |
36 | */ |
37 | |
38 | |
39 | #define IFPLUGD_ENV_PREVIOUS "IFPLUGD_PREVIOUS" |
40 | #define IFPLUGD_ENV_CURRENT "IFPLUGD_CURRENT" |
41 | |
42 | enum { |
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 | |
70 | enum { // 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 | |
79 | enum { // interface status |
80 | IFSTATUS_ERR = -1, |
81 | IFSTATUS_DOWN = 0, |
82 | IFSTATUS_UP = 1, |
83 | }; |
84 | |
85 | enum { // constant fds |
86 | ioctl_fd = 3, |
87 | netlink_fd = 4, |
88 | }; |
89 | |
90 | struct 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 | |
120 | static int run_script(const char *action) |
121 | { |
122 | char *argv[5]; |
123 | int r; |
124 | |
125 | bb_error_msg("executing '%s %s %s'", G.script_name, G.iface, action); |
126 | |
127 | #if 1 |
128 | |
129 | 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; |
134 | |
135 | /* r < 0 - can't exec, 0 <= r < 1000 - exited, >1000 - killed by sig (r-1000) */ |
136 | r = wait4pid(spawn(argv)); |
137 | |
138 | bb_error_msg("exit code: %d", r); |
139 | 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 | |
213 | static int network_ioctl(int request, void* data, const char *errmsg) |
214 | { |
215 | int r = ioctl(ioctl_fd, request, data); |
216 | if (r < 0 && errmsg) |
217 | bb_perror_msg(errmsg); |
218 | return r; |
219 | } |
220 | |
221 | static void set_ifreq_to_ifname(struct ifreq *ifreq) |
222 | { |
223 | memset(ifreq, 0, sizeof(struct ifreq)); |
224 | strncpy_IFNAMSIZ(ifreq->ifr_name, G.iface); |
225 | } |
226 | |
227 | static const char *strstatus(int status) |
228 | { |
229 | if (status == IFSTATUS_ERR) |
230 | return "error"; |
231 | return "down\0up" + (status * 5); |
232 | } |
233 | |
234 | static void up_iface(void) |
235 | { |
236 | struct ifreq ifrequest; |
237 | |
238 | if (!G.iface_exists) |
239 | return; |
240 | |
241 | set_ifreq_to_ifname(&ifrequest); |
242 | if (network_ioctl(SIOCGIFFLAGS, &ifrequest, "can't get interface flags") < 0) { |
243 | 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"); |
251 | if (network_ioctl(SIOCSIFFLAGS, &ifrequest, "can't set interface flags") < 0) |
252 | xfunc_die(); |
253 | } |
254 | |
255 | #if 0 /* why do we mess with IP addr? It's not our business */ |
256 | if (network_ioctl(SIOCGIFADDR, &ifrequest, "can't get interface address") < 0) { |
257 | } else if (ifrequest.ifr_addr.sa_family != AF_INET) { |
258 | bb_perror_msg("the interface is not IP-based"); |
259 | } else { |
260 | ((struct sockaddr_in*)(&ifrequest.ifr_addr))->sin_addr.s_addr = INADDR_ANY; |
261 | network_ioctl(SIOCSIFADDR, &ifrequest, "can't set interface address"); |
262 | } |
263 | network_ioctl(SIOCGIFFLAGS, &ifrequest, "can't get interface flags"); |
264 | #endif |
265 | } |
266 | |
267 | static 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; |
279 | if (network_ioctl(SIOCETHTOOL, &ifrequest, NULL) == 0) { |
280 | char buf[sizeof("/xx:xx:xx:xx:xx:xx")]; |
281 | |
282 | /* Get MAC */ |
283 | buf[0] = '\0'; |
284 | set_ifreq_to_ifname(&ifrequest); |
285 | if (network_ioctl(SIOCGIFHWADDR, &ifrequest, NULL) == 0) { |
286 | 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 | |
295 | bb_error_msg("using interface %s%s with driver<%s> (version: %s)", |
296 | G.iface, buf, driver_info.driver, driver_info.version); |
297 | } |
298 | #endif |
299 | |
300 | G.cached_detect_link_func = NULL; |
301 | } |
302 | |
303 | static smallint detect_link_mii(void) |
304 | { |
305 | struct ifreq ifreq; |
306 | struct mii_ioctl_data *mii = (void *)&ifreq.ifr_data; |
307 | |
308 | set_ifreq_to_ifname(&ifreq); |
309 | |
310 | if (network_ioctl(SIOCGMIIPHY, &ifreq, "SIOCGMIIPHY failed") < 0) { |
311 | return IFSTATUS_ERR; |
312 | } |
313 | |
314 | mii->reg_num = 1; |
315 | |
316 | if (network_ioctl(SIOCGMIIREG, &ifreq, "SIOCGMIIREG failed") < 0) { |
317 | return IFSTATUS_ERR; |
318 | } |
319 | |
320 | return (mii->val_out & 0x0004) ? IFSTATUS_UP : IFSTATUS_DOWN; |
321 | } |
322 | |
323 | static smallint detect_link_priv(void) |
324 | { |
325 | struct ifreq ifreq; |
326 | struct mii_ioctl_data *mii = (void *)&ifreq.ifr_data; |
327 | |
328 | set_ifreq_to_ifname(&ifreq); |
329 | |
330 | if (network_ioctl(SIOCDEVPRIVATE, &ifreq, "SIOCDEVPRIVATE failed") < 0) { |
331 | return IFSTATUS_ERR; |
332 | } |
333 | |
334 | mii->reg_num = 1; |
335 | |
336 | if (network_ioctl(SIOCDEVPRIVATE+1, &ifreq, "SIOCDEVPRIVATE+1 failed") < 0) { |
337 | return IFSTATUS_ERR; |
338 | } |
339 | |
340 | return (mii->val_out & 0x0004) ? IFSTATUS_UP : IFSTATUS_DOWN; |
341 | } |
342 | |
343 | static 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; |
351 | ifreq.ifr_data = (void*) &edata; |
352 | |
353 | if (network_ioctl(SIOCETHTOOL, &ifreq, "ETHTOOL_GLINK failed") < 0) { |
354 | return IFSTATUS_ERR; |
355 | } |
356 | |
357 | return edata.data ? IFSTATUS_UP : IFSTATUS_DOWN; |
358 | } |
359 | |
360 | static smallint detect_link_iff(void) |
361 | { |
362 | struct ifreq ifreq; |
363 | |
364 | set_ifreq_to_ifname(&ifreq); |
365 | |
366 | if (network_ioctl(SIOCGIFFLAGS, &ifreq, "SIOCGIFFLAGS failed") < 0) { |
367 | return IFSTATUS_ERR; |
368 | } |
369 | |
370 | /* 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 | |
379 | return (ifreq.ifr_flags & IFF_RUNNING) ? IFSTATUS_UP : IFSTATUS_DOWN; |
380 | } |
381 | |
382 | static smallint detect_link_wlan(void) |
383 | { |
384 | struct iwreq iwrequest; |
385 | uint8_t mac[ETH_ALEN]; |
386 | |
387 | memset(&iwrequest, 0, sizeof(struct iwreq)); |
388 | strncpy_IFNAMSIZ(iwrequest.ifr_ifrn.ifrn_name, G.iface); |
389 | |
390 | if (network_ioctl(SIOCGIWAP, &iwrequest, "SIOCGIWAP failed") < 0) { |
391 | 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 | |
407 | static 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 | |
464 | static 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 | |
471 | /* 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 | */ |
475 | if (!(option_mask32 & FLAG_NO_AUTO)) |
476 | up_iface(); |
477 | |
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 | |
502 | static 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 | |
569 | static 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 | |
586 | #if ENABLE_FEATURE_PIDFILE |
587 | static 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 | } |
600 | #endif |
601 | |
602 | int ifplugd_main(int argc, char **argv) MAIN_EXTERNALLY_VISIBLE; |
603 | int 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); |
621 | G.poll_time *= 1000; |
622 | |
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); |
691 | G.iface_exists = (network_ioctl(SIOCGIFINDEX, &ifrequest, NULL) == 0); |
692 | } |
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, |
746 | G.poll_time |
747 | ) < 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 | } |