/src/u-boot/net/link_local.c
Line | Count | Source |
1 | | /* |
2 | | * RFC3927 ZeroConf IPv4 Link-Local addressing |
3 | | * (see <http://www.zeroconf.org/>) |
4 | | * |
5 | | * Copied from BusyBox - networking/zcip.c |
6 | | * |
7 | | * Copyright (C) 2003 by Arthur van Hoff (avh@strangeberry.com) |
8 | | * Copyright (C) 2004 by David Brownell |
9 | | * Copyright (C) 2010 by Joe Hershberger |
10 | | * |
11 | | * Licensed under the GPL v2 or later |
12 | | */ |
13 | | |
14 | | #include <env.h> |
15 | | #include <log.h> |
16 | | #include <net.h> |
17 | | #include <rand.h> |
18 | | #include "arp.h" |
19 | | #include "net_rand.h" |
20 | | |
21 | | /* We don't need more than 32 bits of the counter */ |
22 | 0 | #define MONOTONIC_MS() ((unsigned)get_timer(0) * (1000 / CONFIG_SYS_HZ)) |
23 | | |
24 | | enum { |
25 | | /* 169.254.0.0 */ |
26 | | LINKLOCAL_ADDR = 0xa9fe0000, |
27 | | |
28 | | IN_CLASSB_NET = 0xffff0000, |
29 | | IN_CLASSB_HOST = 0x0000ffff, |
30 | | |
31 | | /* protocol timeout parameters, specified in seconds */ |
32 | | PROBE_WAIT = 1, |
33 | | PROBE_MIN = 1, |
34 | | PROBE_MAX = 2, |
35 | | PROBE_NUM = 3, |
36 | | MAX_CONFLICTS = 10, |
37 | | RATE_LIMIT_INTERVAL = 60, |
38 | | ANNOUNCE_WAIT = 2, |
39 | | ANNOUNCE_NUM = 2, |
40 | | ANNOUNCE_INTERVAL = 2, |
41 | | DEFEND_INTERVAL = 10 |
42 | | }; |
43 | | |
44 | | /* States during the configuration process. */ |
45 | | static enum ll_state_t { |
46 | | PROBE = 0, |
47 | | RATE_LIMIT_PROBE, |
48 | | ANNOUNCE, |
49 | | MONITOR, |
50 | | DEFEND, |
51 | | DISABLED |
52 | | } state = DISABLED; |
53 | | |
54 | | static struct in_addr ip; |
55 | | static int timeout_ms = -1; |
56 | | static unsigned deadline_ms; |
57 | | static unsigned conflicts; |
58 | | static unsigned nprobes; |
59 | | static unsigned nclaims; |
60 | | static int ready; |
61 | | static unsigned int seed; |
62 | | |
63 | | static void link_local_timeout(void); |
64 | | |
65 | | /** |
66 | | * Pick a random link local IP address on 169.254/16, except that |
67 | | * the first and last 256 addresses are reserved. |
68 | | */ |
69 | | static struct in_addr pick(void) |
70 | 0 | { |
71 | 0 | unsigned tmp; |
72 | 0 | struct in_addr ip; |
73 | |
|
74 | 0 | do { |
75 | 0 | tmp = rand_r(&seed) & IN_CLASSB_HOST; |
76 | 0 | } while (tmp > (IN_CLASSB_HOST - 0x0200)); |
77 | 0 | ip.s_addr = htonl((LINKLOCAL_ADDR + 0x0100) + tmp); |
78 | 0 | return ip; |
79 | 0 | } |
80 | | |
81 | | /** |
82 | | * Return milliseconds of random delay, up to "secs" seconds. |
83 | | */ |
84 | | static inline unsigned random_delay_ms(unsigned secs) |
85 | 0 | { |
86 | 0 | return rand_r(&seed) % (secs * 1000); |
87 | 0 | } |
88 | | |
89 | | static void configure_wait(void) |
90 | 0 | { |
91 | 0 | if (timeout_ms == -1) |
92 | 0 | return; |
93 | | |
94 | | /* poll, being ready to adjust current timeout */ |
95 | 0 | if (!timeout_ms) |
96 | 0 | timeout_ms = random_delay_ms(PROBE_WAIT); |
97 | | |
98 | | /* set deadline_ms to the point in time when we timeout */ |
99 | 0 | deadline_ms = MONOTONIC_MS() + timeout_ms; |
100 | |
|
101 | 0 | debug_cond(DEBUG_DEV_PKT, "...wait %d %s nprobes=%u, nclaims=%u\n", |
102 | 0 | timeout_ms, eth_get_name(), nprobes, nclaims); |
103 | |
|
104 | 0 | net_set_timeout_handler(timeout_ms, link_local_timeout); |
105 | 0 | } |
106 | | |
107 | | void link_local_start(void) |
108 | 0 | { |
109 | 0 | ip = string_to_ip(env_get("llipaddr")); |
110 | 0 | if (ip.s_addr != 0 && |
111 | 0 | (ntohl(ip.s_addr) & IN_CLASSB_NET) != LINKLOCAL_ADDR) { |
112 | 0 | puts("invalid link address"); |
113 | 0 | net_set_state(NETLOOP_FAIL); |
114 | 0 | return; |
115 | 0 | } |
116 | 0 | net_netmask.s_addr = htonl(IN_CLASSB_NET); |
117 | |
|
118 | 0 | seed = seed_mac(); |
119 | 0 | if (ip.s_addr == 0) |
120 | 0 | ip = pick(); |
121 | |
|
122 | 0 | state = PROBE; |
123 | 0 | timeout_ms = 0; |
124 | 0 | conflicts = 0; |
125 | 0 | nprobes = 0; |
126 | 0 | nclaims = 0; |
127 | 0 | ready = 0; |
128 | |
|
129 | 0 | configure_wait(); |
130 | 0 | } |
131 | | |
132 | | static void link_local_timeout(void) |
133 | 0 | { |
134 | 0 | switch (state) { |
135 | 0 | case PROBE: |
136 | | /* timeouts in the PROBE state mean no conflicting ARP packets |
137 | | have been received, so we can progress through the states */ |
138 | 0 | if (nprobes < PROBE_NUM) { |
139 | 0 | struct in_addr zero_ip = {.s_addr = 0}; |
140 | |
|
141 | 0 | nprobes++; |
142 | 0 | debug_cond(DEBUG_LL_STATE, "probe/%u %s@%pI4\n", |
143 | 0 | nprobes, eth_get_name(), &ip); |
144 | 0 | arp_raw_request(zero_ip, net_null_ethaddr, ip); |
145 | 0 | timeout_ms = PROBE_MIN * 1000; |
146 | 0 | timeout_ms += random_delay_ms(PROBE_MAX - PROBE_MIN); |
147 | 0 | } else { |
148 | | /* Switch to announce state */ |
149 | 0 | state = ANNOUNCE; |
150 | 0 | nclaims = 0; |
151 | 0 | debug_cond(DEBUG_LL_STATE, "announce/%u %s@%pI4\n", |
152 | 0 | nclaims, eth_get_name(), &ip); |
153 | 0 | arp_raw_request(ip, net_ethaddr, ip); |
154 | 0 | timeout_ms = ANNOUNCE_INTERVAL * 1000; |
155 | 0 | } |
156 | 0 | break; |
157 | 0 | case RATE_LIMIT_PROBE: |
158 | | /* timeouts in the RATE_LIMIT_PROBE state mean no conflicting |
159 | | ARP packets have been received, so we can move immediately |
160 | | to the announce state */ |
161 | 0 | state = ANNOUNCE; |
162 | 0 | nclaims = 0; |
163 | 0 | debug_cond(DEBUG_LL_STATE, "announce/%u %s@%pI4\n", |
164 | 0 | nclaims, eth_get_name(), &ip); |
165 | 0 | arp_raw_request(ip, net_ethaddr, ip); |
166 | 0 | timeout_ms = ANNOUNCE_INTERVAL * 1000; |
167 | 0 | break; |
168 | 0 | case ANNOUNCE: |
169 | | /* timeouts in the ANNOUNCE state mean no conflicting ARP |
170 | | packets have been received, so we can progress through |
171 | | the states */ |
172 | 0 | if (nclaims < ANNOUNCE_NUM) { |
173 | 0 | nclaims++; |
174 | 0 | debug_cond(DEBUG_LL_STATE, "announce/%u %s@%pI4\n", |
175 | 0 | nclaims, eth_get_name(), &ip); |
176 | 0 | arp_raw_request(ip, net_ethaddr, ip); |
177 | 0 | timeout_ms = ANNOUNCE_INTERVAL * 1000; |
178 | 0 | } else { |
179 | | /* Switch to monitor state */ |
180 | 0 | state = MONITOR; |
181 | 0 | printf("Successfully assigned %pI4\n", &ip); |
182 | 0 | net_copy_ip(&net_ip, &ip); |
183 | 0 | ready = 1; |
184 | 0 | conflicts = 0; |
185 | 0 | timeout_ms = -1; |
186 | | /* Never timeout in the monitor state */ |
187 | 0 | net_set_timeout_handler(0, NULL); |
188 | | |
189 | | /* NOTE: all other exit paths should deconfig ... */ |
190 | 0 | net_set_state(NETLOOP_SUCCESS); |
191 | 0 | return; |
192 | 0 | } |
193 | 0 | break; |
194 | 0 | case DEFEND: |
195 | | /* We won! No ARP replies, so just go back to monitor */ |
196 | 0 | state = MONITOR; |
197 | 0 | timeout_ms = -1; |
198 | 0 | conflicts = 0; |
199 | 0 | break; |
200 | 0 | default: |
201 | | /* Invalid, should never happen. Restart the whole protocol */ |
202 | 0 | state = PROBE; |
203 | 0 | ip = pick(); |
204 | 0 | timeout_ms = 0; |
205 | 0 | nprobes = 0; |
206 | 0 | nclaims = 0; |
207 | 0 | break; |
208 | 0 | } |
209 | 0 | configure_wait(); |
210 | 0 | } |
211 | | |
212 | | void link_local_receive_arp(struct arp_hdr *arp, int len) |
213 | 0 | { |
214 | 0 | int source_ip_conflict; |
215 | 0 | int target_ip_conflict; |
216 | 0 | struct in_addr null_ip = {.s_addr = 0}; |
217 | |
|
218 | 0 | if (state == DISABLED) |
219 | 0 | return; |
220 | | |
221 | | /* We need to adjust the timeout in case we didn't receive a |
222 | | conflicting packet. */ |
223 | 0 | if (timeout_ms > 0) { |
224 | 0 | unsigned diff = deadline_ms - MONOTONIC_MS(); |
225 | 0 | if ((int)(diff) < 0) { |
226 | | /* Current time is greater than the expected timeout |
227 | | time. This should never happen */ |
228 | 0 | debug_cond(DEBUG_LL_STATE, |
229 | 0 | "missed an expected timeout\n"); |
230 | 0 | timeout_ms = 0; |
231 | 0 | } else { |
232 | 0 | debug_cond(DEBUG_INT_STATE, "adjusting timeout\n"); |
233 | 0 | timeout_ms = diff | 1; /* never 0 */ |
234 | 0 | } |
235 | 0 | } |
236 | | #if 0 |
237 | | /* XXX Don't bother with ethernet link just yet */ |
238 | | if ((fds[0].revents & POLLIN) == 0) { |
239 | | if (fds[0].revents & POLLERR) { |
240 | | /* |
241 | | * FIXME: links routinely go down; |
242 | | */ |
243 | | bb_error_msg("iface %s is down", eth_get_name()); |
244 | | if (ready) |
245 | | run(argv, "deconfig", &ip); |
246 | | return EXIT_FAILURE; |
247 | | } |
248 | | continue; |
249 | | } |
250 | | #endif |
251 | |
|
252 | 0 | debug_cond(DEBUG_INT_STATE, "%s recv arp type=%d, op=%d,\n", |
253 | 0 | eth_get_name(), ntohs(arp->ar_pro), |
254 | 0 | ntohs(arp->ar_op)); |
255 | 0 | debug_cond(DEBUG_INT_STATE, "\tsource=%pM %pI4\n", |
256 | 0 | &arp->ar_sha, |
257 | 0 | &arp->ar_spa); |
258 | 0 | debug_cond(DEBUG_INT_STATE, "\ttarget=%pM %pI4\n", |
259 | 0 | &arp->ar_tha, |
260 | 0 | &arp->ar_tpa); |
261 | |
|
262 | 0 | if (arp->ar_op != htons(ARPOP_REQUEST) && |
263 | 0 | arp->ar_op != htons(ARPOP_REPLY)) { |
264 | 0 | configure_wait(); |
265 | 0 | return; |
266 | 0 | } |
267 | | |
268 | 0 | source_ip_conflict = 0; |
269 | 0 | target_ip_conflict = 0; |
270 | |
|
271 | 0 | if (memcmp(&arp->ar_spa, &ip, ARP_PLEN) == 0 && |
272 | 0 | memcmp(&arp->ar_sha, net_ethaddr, ARP_HLEN) != 0) |
273 | 0 | source_ip_conflict = 1; |
274 | | |
275 | | /* |
276 | | * According to RFC 3927, section 2.2.1: |
277 | | * Check if packet is an ARP probe by checking for a null source IP |
278 | | * then check that target IP is equal to ours and source hw addr |
279 | | * is not equal to ours. This condition should cause a conflict only |
280 | | * during probe. |
281 | | */ |
282 | 0 | if (arp->ar_op == htons(ARPOP_REQUEST) && |
283 | 0 | memcmp(&arp->ar_spa, &null_ip, ARP_PLEN) == 0 && |
284 | 0 | memcmp(&arp->ar_tpa, &ip, ARP_PLEN) == 0 && |
285 | 0 | memcmp(&arp->ar_sha, net_ethaddr, ARP_HLEN) != 0) { |
286 | 0 | target_ip_conflict = 1; |
287 | 0 | } |
288 | |
|
289 | 0 | debug_cond(DEBUG_NET_PKT, |
290 | 0 | "state = %d, source ip conflict = %d, target ip conflict = " |
291 | 0 | "%d\n", state, source_ip_conflict, target_ip_conflict); |
292 | 0 | switch (state) { |
293 | 0 | case PROBE: |
294 | 0 | case ANNOUNCE: |
295 | | /* When probing or announcing, check for source IP conflicts |
296 | | and other hosts doing ARP probes (target IP conflicts). */ |
297 | 0 | if (source_ip_conflict || target_ip_conflict) { |
298 | 0 | conflicts++; |
299 | 0 | state = PROBE; |
300 | 0 | if (conflicts >= MAX_CONFLICTS) { |
301 | 0 | debug("%s ratelimit\n", eth_get_name()); |
302 | 0 | timeout_ms = RATE_LIMIT_INTERVAL * 1000; |
303 | 0 | state = RATE_LIMIT_PROBE; |
304 | 0 | } |
305 | | |
306 | | /* restart the whole protocol */ |
307 | 0 | ip = pick(); |
308 | 0 | timeout_ms = 0; |
309 | 0 | nprobes = 0; |
310 | 0 | nclaims = 0; |
311 | 0 | } |
312 | 0 | break; |
313 | 0 | case MONITOR: |
314 | | /* If a conflict, we try to defend with a single ARP probe */ |
315 | 0 | if (source_ip_conflict) { |
316 | 0 | debug("monitor conflict -- defending\n"); |
317 | 0 | state = DEFEND; |
318 | 0 | timeout_ms = DEFEND_INTERVAL * 1000; |
319 | 0 | arp_raw_request(ip, net_ethaddr, ip); |
320 | 0 | } |
321 | 0 | break; |
322 | 0 | case DEFEND: |
323 | | /* Well, we tried. Start over (on conflict) */ |
324 | 0 | if (source_ip_conflict) { |
325 | 0 | state = PROBE; |
326 | 0 | debug("defend conflict -- starting over\n"); |
327 | 0 | ready = 0; |
328 | 0 | net_ip.s_addr = 0; |
329 | | |
330 | | /* restart the whole protocol */ |
331 | 0 | ip = pick(); |
332 | 0 | timeout_ms = 0; |
333 | 0 | nprobes = 0; |
334 | 0 | nclaims = 0; |
335 | 0 | } |
336 | 0 | break; |
337 | 0 | default: |
338 | | /* Invalid, should never happen. Restart the whole protocol */ |
339 | 0 | debug("invalid state -- starting over\n"); |
340 | 0 | state = PROBE; |
341 | 0 | ip = pick(); |
342 | 0 | timeout_ms = 0; |
343 | 0 | nprobes = 0; |
344 | 0 | nclaims = 0; |
345 | 0 | break; |
346 | 0 | } |
347 | 0 | configure_wait(); |
348 | 0 | } |