source: MondoRescue/branches/3.3/mindi-busybox/networking/zcip.c@ 3734

Last change on this file since 3734 was 3621, checked in by Bruno Cornec, 10 years ago

New 3?3 banch for incorporation of latest busybox 1.25. Changing minor version to handle potential incompatibilities.

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