blob: 9122bd6819a50df72904db840c0ab4abbe0ee9d4
1 | /* vi: set sw=4 ts=4: */ |
2 | /* |
3 | * RFC3927 ZeroConf IPv4 Link-Local addressing |
4 | * (see <http://www.zeroconf.org/>) |
5 | * |
6 | * Copyright (C) 2003 by Arthur van Hoff (avh@strangeberry.com) |
7 | * Copyright (C) 2004 by David Brownell |
8 | * |
9 | * Licensed under GPLv2 or later, see file LICENSE in this source tree. |
10 | */ |
11 | |
12 | /* |
13 | * ZCIP just manages the 169.254.*.* addresses. That network is not |
14 | * routed at the IP level, though various proxies or bridges can |
15 | * certainly be used. Its naming is built over multicast DNS. |
16 | */ |
17 | //config:config ZCIP |
18 | //config: bool "zcip" |
19 | //config: default y |
20 | //config: select PLATFORM_LINUX |
21 | //config: select FEATURE_SYSLOG |
22 | //config: help |
23 | //config: ZCIP provides ZeroConf IPv4 address selection, according to RFC 3927. |
24 | //config: It's a daemon that allocates and defends a dynamically assigned |
25 | //config: address on the 169.254/16 network, requiring no system administrator. |
26 | //config: |
27 | //config: See http://www.zeroconf.org for further details, and "zcip.script" |
28 | //config: in the busybox examples. |
29 | |
30 | //applet:IF_ZCIP(APPLET(zcip, BB_DIR_SBIN, BB_SUID_DROP)) |
31 | |
32 | //kbuild:lib-$(CONFIG_ZCIP) += zcip.o |
33 | |
34 | //#define DEBUG |
35 | |
36 | // TODO: |
37 | // - more real-world usage/testing, especially daemon mode |
38 | // - kernel packet filters to reduce scheduling noise |
39 | // - avoid silent script failures, especially under load... |
40 | // - link status monitoring (restart on link-up; stop on link-down) |
41 | |
42 | //usage:#define zcip_trivial_usage |
43 | //usage: "[OPTIONS] IFACE SCRIPT" |
44 | //usage:#define zcip_full_usage "\n\n" |
45 | //usage: "Manage a ZeroConf IPv4 link-local address\n" |
46 | //usage: "\n -f Run in foreground" |
47 | //usage: "\n -q Quit after obtaining address" |
48 | //usage: "\n -r 169.254.x.x Request this address first" |
49 | //usage: "\n -l x.x.0.0 Use this range instead of 169.254" |
50 | //usage: "\n -v Verbose" |
51 | //usage: "\n" |
52 | //usage: "\n$LOGGING=none Suppress logging" |
53 | //usage: "\n$LOGGING=syslog Log to syslog" |
54 | //usage: "\n" |
55 | //usage: "\nWith no -q, runs continuously monitoring for ARP conflicts," |
56 | //usage: "\nexits only on I/O errors (link down etc)" |
57 | |
58 | #include "libbb.h" |
59 | #include "common_bufsiz.h" |
60 | #include <netinet/ether.h> |
61 | #include <net/if.h> |
62 | #include <net/if_arp.h> |
63 | #include <linux/sockios.h> |
64 | |
65 | #include <syslog.h> |
66 | |
67 | /* We don't need more than 32 bits of the counter */ |
68 | #define MONOTONIC_US() ((unsigned)monotonic_us()) |
69 | |
70 | struct arp_packet { |
71 | struct ether_header eth; |
72 | struct ether_arp arp; |
73 | } PACKED; |
74 | |
75 | enum { |
76 | /* 0-1 seconds before sending 1st probe */ |
77 | PROBE_WAIT = 1, |
78 | /* 1-2 seconds between probes */ |
79 | PROBE_MIN = 1, |
80 | PROBE_MAX = 2, |
81 | PROBE_NUM = 3, /* total probes to send */ |
82 | ANNOUNCE_INTERVAL = 2, /* 2 seconds between announces */ |
83 | ANNOUNCE_NUM = 3, /* announces to send */ |
84 | /* if probe/announce sees a conflict, multiply RANDOM(NUM_CONFLICT) by... */ |
85 | CONFLICT_MULTIPLIER = 2, |
86 | /* if we monitor and see a conflict, how long is defend state? */ |
87 | DEFEND_INTERVAL = 10, |
88 | }; |
89 | |
90 | /* States during the configuration process. */ |
91 | enum { |
92 | PROBE = 0, |
93 | ANNOUNCE, |
94 | MONITOR, |
95 | DEFEND |
96 | }; |
97 | |
98 | #define VDBG(...) do { } while (0) |
99 | |
100 | |
101 | enum { |
102 | sock_fd = 3 |
103 | }; |
104 | |
105 | struct globals { |
106 | struct sockaddr iface_sockaddr; |
107 | struct ether_addr our_ethaddr; |
108 | uint32_t localnet_ip; |
109 | } FIX_ALIASING; |
110 | #define G (*(struct globals*)bb_common_bufsiz1) |
111 | #define INIT_G() do { setup_common_bufsiz(); } while (0) |
112 | |
113 | |
114 | /** |
115 | * Pick a random link local IP address on 169.254/16, except that |
116 | * the first and last 256 addresses are reserved. |
117 | */ |
118 | static uint32_t pick_nip(void) |
119 | { |
120 | unsigned tmp; |
121 | |
122 | do { |
123 | tmp = rand() & IN_CLASSB_HOST; |
124 | } while (tmp > (IN_CLASSB_HOST - 0x0200)); |
125 | return htonl((G.localnet_ip + 0x0100) + tmp); |
126 | } |
127 | |
128 | static const char *nip_to_a(uint32_t nip) |
129 | { |
130 | struct in_addr in; |
131 | in.s_addr = nip; |
132 | return inet_ntoa(in); |
133 | } |
134 | |
135 | /** |
136 | * Broadcast an ARP packet. |
137 | */ |
138 | static void send_arp_request( |
139 | /* int op, - always ARPOP_REQUEST */ |
140 | /* const struct ether_addr *source_eth, - always &G.our_ethaddr */ |
141 | uint32_t source_nip, |
142 | const struct ether_addr *target_eth, uint32_t target_nip) |
143 | { |
144 | enum { op = ARPOP_REQUEST }; |
145 | #define source_eth (&G.our_ethaddr) |
146 | |
147 | struct arp_packet p; |
148 | memset(&p, 0, sizeof(p)); |
149 | |
150 | // ether header |
151 | p.eth.ether_type = htons(ETHERTYPE_ARP); |
152 | memcpy(p.eth.ether_shost, source_eth, ETH_ALEN); |
153 | memset(p.eth.ether_dhost, 0xff, ETH_ALEN); |
154 | |
155 | // arp request |
156 | p.arp.arp_hrd = htons(ARPHRD_ETHER); |
157 | p.arp.arp_pro = htons(ETHERTYPE_IP); |
158 | p.arp.arp_hln = ETH_ALEN; |
159 | p.arp.arp_pln = 4; |
160 | p.arp.arp_op = htons(op); |
161 | memcpy(&p.arp.arp_sha, source_eth, ETH_ALEN); |
162 | memcpy(&p.arp.arp_spa, &source_nip, 4); |
163 | memcpy(&p.arp.arp_tha, target_eth, ETH_ALEN); |
164 | memcpy(&p.arp.arp_tpa, &target_nip, 4); |
165 | |
166 | // send it |
167 | // Even though sock_fd is already bound to G.iface_sockaddr, just send() |
168 | // won't work, because "socket is not connected" |
169 | // (and connect() won't fix that, "operation not supported"). |
170 | // Thus we sendto() to G.iface_sockaddr. I wonder which sockaddr |
171 | // (from bind() or from sendto()?) kernel actually uses |
172 | // to determine iface to emit the packet from... |
173 | xsendto(sock_fd, &p, sizeof(p), &G.iface_sockaddr, sizeof(G.iface_sockaddr)); |
174 | #undef source_eth |
175 | } |
176 | |
177 | /** |
178 | * Run a script. |
179 | * argv[0]:intf argv[1]:script_name argv[2]:junk argv[3]:NULL |
180 | */ |
181 | static int run(char *argv[3], const char *param, uint32_t nip) |
182 | { |
183 | int status; |
184 | const char *addr = addr; /* for gcc */ |
185 | const char *fmt = "%s %s %s" + 3; |
186 | |
187 | argv[2] = (char*)param; |
188 | |
189 | VDBG("%s run %s %s\n", argv[0], argv[1], argv[2]); |
190 | |
191 | if (nip != 0) { |
192 | addr = nip_to_a(nip); |
193 | xsetenv("ip", addr); |
194 | fmt -= 3; |
195 | } |
196 | bb_error_msg(fmt, argv[2], argv[0], addr); |
197 | |
198 | status = spawn_and_wait(argv + 1); |
199 | if (status < 0) { |
200 | bb_perror_msg("%s %s %s" + 3, argv[2], argv[0]); |
201 | return -errno; |
202 | } |
203 | if (status != 0) |
204 | bb_error_msg("script %s %s failed, exitcode=%d", argv[1], argv[2], status & 0xff); |
205 | return status; |
206 | } |
207 | |
208 | /** |
209 | * Return milliseconds of random delay, up to "secs" seconds. |
210 | */ |
211 | static ALWAYS_INLINE unsigned random_delay_ms(unsigned secs) |
212 | { |
213 | return (unsigned)rand() % (secs * 1000); |
214 | } |
215 | |
216 | /** |
217 | * main program |
218 | */ |
219 | int zcip_main(int argc, char **argv) MAIN_EXTERNALLY_VISIBLE; |
220 | int zcip_main(int argc UNUSED_PARAM, char **argv) |
221 | { |
222 | char *r_opt; |
223 | const char *l_opt = "169.254.0.0"; |
224 | int state; |
225 | int nsent; |
226 | unsigned opts; |
227 | |
228 | // Ugly trick, but I want these zeroed in one go |
229 | struct { |
230 | const struct ether_addr null_ethaddr; |
231 | struct ifreq ifr; |
232 | uint32_t chosen_nip; |
233 | int conflicts; |
234 | int timeout_ms; // must be signed |
235 | int verbose; |
236 | } L; |
237 | #define null_ethaddr (L.null_ethaddr) |
238 | #define ifr (L.ifr ) |
239 | #define chosen_nip (L.chosen_nip ) |
240 | #define conflicts (L.conflicts ) |
241 | #define timeout_ms (L.timeout_ms ) |
242 | #define verbose (L.verbose ) |
243 | |
244 | memset(&L, 0, sizeof(L)); |
245 | INIT_G(); |
246 | |
247 | #define FOREGROUND (opts & 1) |
248 | #define QUIT (opts & 2) |
249 | // Parse commandline: prog [options] ifname script |
250 | // exactly 2 args; -v accumulates and implies -f |
251 | opt_complementary = "=2:vv:vf"; |
252 | opts = getopt32(argv, "fqr:l:v", &r_opt, &l_opt, &verbose); |
253 | #if !BB_MMU |
254 | // on NOMMU reexec early (or else we will rerun things twice) |
255 | if (!FOREGROUND) |
256 | bb_daemonize_or_rexec(0 /*was: DAEMON_CHDIR_ROOT*/, argv); |
257 | #endif |
258 | // Open an ARP socket |
259 | // (need to do it before openlog to prevent openlog from taking |
260 | // fd 3 (sock_fd==3)) |
261 | xmove_fd(xsocket(AF_PACKET, SOCK_PACKET, htons(ETH_P_ARP)), sock_fd); |
262 | if (!FOREGROUND) { |
263 | // do it before all bb_xx_msg calls |
264 | openlog(applet_name, 0, LOG_DAEMON); |
265 | logmode |= LOGMODE_SYSLOG; |
266 | } |
267 | bb_logenv_override(); |
268 | |
269 | { // -l n.n.n.n |
270 | struct in_addr net; |
271 | if (inet_aton(l_opt, &net) == 0 |
272 | || (net.s_addr & htonl(IN_CLASSB_NET)) != net.s_addr |
273 | ) { |
274 | bb_error_msg_and_die("invalid network address"); |
275 | } |
276 | G.localnet_ip = ntohl(net.s_addr); |
277 | } |
278 | if (opts & 4) { // -r n.n.n.n |
279 | struct in_addr ip; |
280 | if (inet_aton(r_opt, &ip) == 0 |
281 | || (ntohl(ip.s_addr) & IN_CLASSB_NET) != G.localnet_ip |
282 | ) { |
283 | bb_error_msg_and_die("invalid link address"); |
284 | } |
285 | chosen_nip = ip.s_addr; |
286 | } |
287 | argv += optind - 1; |
288 | |
289 | /* Now: argv[0]:junk argv[1]:intf argv[2]:script argv[3]:NULL */ |
290 | /* We need to make space for script argument: */ |
291 | argv[0] = argv[1]; |
292 | argv[1] = argv[2]; |
293 | /* Now: argv[0]:intf argv[1]:script argv[2]:junk argv[3]:NULL */ |
294 | #define argv_intf (argv[0]) |
295 | |
296 | xsetenv("interface", argv_intf); |
297 | |
298 | // Initialize the interface (modprobe, ifup, etc) |
299 | if (run(argv, "init", 0)) |
300 | return EXIT_FAILURE; |
301 | |
302 | // Initialize G.iface_sockaddr |
303 | // G.iface_sockaddr is: { u16 sa_family; u8 sa_data[14]; } |
304 | //memset(&G.iface_sockaddr, 0, sizeof(G.iface_sockaddr)); |
305 | //TODO: are we leaving sa_family == 0 (AF_UNSPEC)?! |
306 | safe_strncpy(G.iface_sockaddr.sa_data, argv_intf, sizeof(G.iface_sockaddr.sa_data)); |
307 | |
308 | // Bind to the interface's ARP socket |
309 | xbind(sock_fd, &G.iface_sockaddr, sizeof(G.iface_sockaddr)); |
310 | |
311 | // Get the interface's ethernet address |
312 | //memset(&ifr, 0, sizeof(ifr)); |
313 | strncpy_IFNAMSIZ(ifr.ifr_name, argv_intf); |
314 | xioctl(sock_fd, SIOCGIFHWADDR, &ifr); |
315 | memcpy(&G.our_ethaddr, &ifr.ifr_hwaddr.sa_data, ETH_ALEN); |
316 | |
317 | // Start with some stable ip address, either a function of |
318 | // the hardware address or else the last address we used. |
319 | // we are taking low-order four bytes, as top-order ones |
320 | // aren't random enough. |
321 | // NOTE: the sequence of addresses we try changes only |
322 | // depending on when we detect conflicts. |
323 | { |
324 | uint32_t t; |
325 | move_from_unaligned32(t, ((char *)&G.our_ethaddr + 2)); |
326 | srand(t); |
327 | } |
328 | // FIXME cases to handle: |
329 | // - zcip already running! |
330 | // - link already has local address... just defend/update |
331 | |
332 | // Daemonize now; don't delay system startup |
333 | if (!FOREGROUND) { |
334 | #if BB_MMU |
335 | bb_daemonize(0 /*was: DAEMON_CHDIR_ROOT*/); |
336 | #endif |
337 | bb_error_msg("start, interface %s", argv_intf); |
338 | } |
339 | |
340 | // Run the dynamic address negotiation protocol, |
341 | // restarting after address conflicts: |
342 | // - start with some address we want to try |
343 | // - short random delay |
344 | // - arp probes to see if another host uses it |
345 | // 00:04:e2:64:23:c2 > ff:ff:ff:ff:ff:ff arp who-has 169.254.194.171 tell 0.0.0.0 |
346 | // - arp announcements that we're claiming it |
347 | // 00:04:e2:64:23:c2 > ff:ff:ff:ff:ff:ff arp who-has 169.254.194.171 (00:04:e2:64:23:c2) tell 169.254.194.171 |
348 | // - use it |
349 | // - defend it, within limits |
350 | // exit if: |
351 | // - address is successfully obtained and -q was given: |
352 | // run "<script> config", then exit with exitcode 0 |
353 | // - poll error (when does this happen?) |
354 | // - read error (when does this happen?) |
355 | // - sendto error (in send_arp_request()) (when does this happen?) |
356 | // - revents & POLLERR (link down). run "<script> deconfig" first |
357 | if (chosen_nip == 0) { |
358 | new_nip_and_PROBE: |
359 | chosen_nip = pick_nip(); |
360 | } |
361 | nsent = 0; |
362 | state = PROBE; |
363 | while (1) { |
364 | struct pollfd fds[1]; |
365 | unsigned deadline_us = deadline_us; |
366 | struct arp_packet p; |
367 | int ip_conflict; |
368 | int n; |
369 | |
370 | fds[0].fd = sock_fd; |
371 | fds[0].events = POLLIN; |
372 | fds[0].revents = 0; |
373 | |
374 | // Poll, being ready to adjust current timeout |
375 | if (!timeout_ms) { |
376 | timeout_ms = random_delay_ms(PROBE_WAIT); |
377 | // FIXME setsockopt(sock_fd, SO_ATTACH_FILTER, ...) to |
378 | // make the kernel filter out all packets except |
379 | // ones we'd care about. |
380 | } |
381 | if (timeout_ms >= 0) { |
382 | // Set deadline_us to the point in time when we timeout |
383 | deadline_us = MONOTONIC_US() + timeout_ms * 1000; |
384 | } |
385 | |
386 | VDBG("...wait %d %s nsent=%u\n", |
387 | timeout_ms, argv_intf, nsent); |
388 | |
389 | n = safe_poll(fds, 1, timeout_ms); |
390 | if (n < 0) { |
391 | //bb_perror_msg("poll"); - done in safe_poll |
392 | return EXIT_FAILURE; |
393 | } |
394 | if (n == 0) { // timed out? |
395 | VDBG("state:%d\n", state); |
396 | switch (state) { |
397 | case PROBE: |
398 | // No conflicting ARP packets were seen: |
399 | // we can progress through the states |
400 | if (nsent < PROBE_NUM) { |
401 | nsent++; |
402 | VDBG("probe/%u %s@%s\n", |
403 | nsent, argv_intf, nip_to_a(chosen_nip)); |
404 | timeout_ms = PROBE_MIN * 1000; |
405 | timeout_ms += random_delay_ms(PROBE_MAX - PROBE_MIN); |
406 | send_arp_request(0, &null_ethaddr, chosen_nip); |
407 | continue; |
408 | } |
409 | // Switch to announce state |
410 | nsent = 0; |
411 | state = ANNOUNCE; |
412 | goto send_announce; |
413 | case ANNOUNCE: |
414 | // No conflicting ARP packets were seen: |
415 | // we can progress through the states |
416 | if (nsent < ANNOUNCE_NUM) { |
417 | send_announce: |
418 | nsent++; |
419 | VDBG("announce/%u %s@%s\n", |
420 | nsent, argv_intf, nip_to_a(chosen_nip)); |
421 | timeout_ms = ANNOUNCE_INTERVAL * 1000; |
422 | send_arp_request(chosen_nip, &G.our_ethaddr, chosen_nip); |
423 | continue; |
424 | } |
425 | // Switch to monitor state |
426 | // FIXME update filters |
427 | run(argv, "config", chosen_nip); |
428 | // NOTE: all other exit paths should deconfig... |
429 | if (QUIT) |
430 | return EXIT_SUCCESS; |
431 | // fall through: switch to MONITOR |
432 | default: |
433 | // case DEFEND: |
434 | // case MONITOR: (shouldn't happen, MONITOR timeout is infinite) |
435 | // Defend period ended with no ARP replies - we won |
436 | timeout_ms = -1; // never timeout in monitor state |
437 | state = MONITOR; |
438 | continue; |
439 | } |
440 | } |
441 | |
442 | // Packet arrived, or link went down. |
443 | // We need to adjust the timeout in case we didn't receive |
444 | // a conflicting packet. |
445 | if (timeout_ms > 0) { |
446 | unsigned diff = deadline_us - MONOTONIC_US(); |
447 | if ((int)(diff) < 0) { |
448 | // Current time is greater than the expected timeout time. |
449 | diff = 0; |
450 | } |
451 | VDBG("adjusting timeout\n"); |
452 | timeout_ms = (diff / 1000) | 1; // never 0 |
453 | } |
454 | |
455 | if ((fds[0].revents & POLLIN) == 0) { |
456 | if (fds[0].revents & POLLERR) { |
457 | // FIXME: links routinely go down; |
458 | // this shouldn't necessarily exit. |
459 | bb_error_msg("iface %s is down", argv_intf); |
460 | if (state >= MONITOR) { |
461 | // Only if we are in MONITOR or DEFEND |
462 | run(argv, "deconfig", chosen_nip); |
463 | } |
464 | return EXIT_FAILURE; |
465 | } |
466 | continue; |
467 | } |
468 | |
469 | // Read ARP packet |
470 | if (safe_read(sock_fd, &p, sizeof(p)) < 0) { |
471 | bb_perror_msg_and_die(bb_msg_read_error); |
472 | } |
473 | |
474 | if (p.eth.ether_type != htons(ETHERTYPE_ARP)) |
475 | continue; |
476 | if (p.arp.arp_op != htons(ARPOP_REQUEST) |
477 | && p.arp.arp_op != htons(ARPOP_REPLY) |
478 | ) { |
479 | continue; |
480 | } |
481 | #ifdef DEBUG |
482 | { |
483 | struct ether_addr *sha = (struct ether_addr *) p.arp.arp_sha; |
484 | struct ether_addr *tha = (struct ether_addr *) p.arp.arp_tha; |
485 | struct in_addr *spa = (struct in_addr *) p.arp.arp_spa; |
486 | struct in_addr *tpa = (struct in_addr *) p.arp.arp_tpa; |
487 | VDBG("source=%s %s\n", ether_ntoa(sha), inet_ntoa(*spa)); |
488 | VDBG("target=%s %s\n", ether_ntoa(tha), inet_ntoa(*tpa)); |
489 | } |
490 | #endif |
491 | ip_conflict = 0; |
492 | if (memcmp(&p.arp.arp_sha, &G.our_ethaddr, ETH_ALEN) != 0) { |
493 | if (memcmp(p.arp.arp_spa, &chosen_nip, 4) == 0) { |
494 | // A probe or reply with source_ip == chosen ip |
495 | ip_conflict = 1; |
496 | } |
497 | if (p.arp.arp_op == htons(ARPOP_REQUEST) |
498 | && memcmp(p.arp.arp_spa, &const_int_0, 4) == 0 |
499 | && memcmp(p.arp.arp_tpa, &chosen_nip, 4) == 0 |
500 | ) { |
501 | // A probe with source_ip == 0.0.0.0, target_ip == chosen ip: |
502 | // another host trying to claim this ip! |
503 | ip_conflict |= 2; |
504 | } |
505 | } |
506 | VDBG("state:%d ip_conflict:%d\n", state, ip_conflict); |
507 | if (!ip_conflict) |
508 | continue; |
509 | |
510 | // Either src or target IP conflict exists |
511 | if (state <= ANNOUNCE) { |
512 | // PROBE or ANNOUNCE |
513 | conflicts++; |
514 | timeout_ms = PROBE_MIN * 1000 |
515 | + CONFLICT_MULTIPLIER * random_delay_ms(conflicts); |
516 | goto new_nip_and_PROBE; |
517 | } |
518 | |
519 | // MONITOR or DEFEND: only src IP conflict is a problem |
520 | if (ip_conflict & 1) { |
521 | if (state == MONITOR) { |
522 | // Src IP conflict, defend with a single ARP probe |
523 | VDBG("monitor conflict - defending\n"); |
524 | timeout_ms = DEFEND_INTERVAL * 1000; |
525 | state = DEFEND; |
526 | send_arp_request(chosen_nip, &G.our_ethaddr, chosen_nip); |
527 | continue; |
528 | } |
529 | // state == DEFEND |
530 | // Another src IP conflict, start over |
531 | VDBG("defend conflict - starting over\n"); |
532 | run(argv, "deconfig", chosen_nip); |
533 | conflicts = 0; |
534 | timeout_ms = 0; |
535 | goto new_nip_and_PROBE; |
536 | } |
537 | // Note: if we only have a target IP conflict here (ip_conflict & 2), |
538 | // IOW: if we just saw this sort of ARP packet: |
539 | // aa:bb:cc:dd:ee:ff > xx:xx:xx:xx:xx:xx arp who-has <chosen_nip> tell 0.0.0.0 |
540 | // we expect _kernel_ to respond to that, because <chosen_nip> |
541 | // is (expected to be) configured on this iface. |
542 | } // while (1) |
543 | #undef argv_intf |
544 | } |
545 |