Coverage Report

Created: 2026-03-11 06:21

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/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
}