1 /* LWIP service - lldata.c - link-layer (ARP, NDP) data related routines */
3 * This module is largely isolated from the regular routing code. There are
4 * two reasons for that. First, mixing link-layer routes with regular routes
5 * would not work well due to the fact that lwIP keeps these data structures
6 * entirely separate. Second, as of version 8, NetBSD keeps the IP-layer and
7 * link-layer routing separate as well.
9 * Unfortunately, lwIP does not provide much in the way of implementing the
10 * functionality that would be expected for this module. As such, the current
11 * implementation is very restricted and simple.
13 * For ARP table entries, lwIP only allows for adding and deleting static
14 * entries. Non-static entries cannot be deleted. Incomplete (pending)
15 * entries cannot even be enumerated, nor can (e.g.) expiry information be
16 * obtained. The lwIP ARP datastructures are completely hidden, so there is no
17 * way to overcome these limitations without changing lwIP itself. As a
18 * result, not all functionality of the arp(8) userland utility is supported.
20 * For NDP table entries, lwIP offers no API at all. However, since the data
21 * structures are exposed directly, we can use those to implement full support
22 * for exposing information in a read-only way. However, manipulating data
23 * structures directly from here is too risky, nor does lwIP currently support
24 * the concept of static NDP table entries. Therefore, adding, changing, and
25 * deleting NDP entries is currently not supported, and will also first require
26 * changes to lwIP itself.
28 * The ndp(8) userland utility is also able to show and manipulate various
29 * other neighbor discovery related tables and settings. We support only a
30 * small subset of them. The main reason for this is that the other tables,
31 * in particular the prefix and default router lists, are not relevant: on
32 * MINIX 3, these are always managed fully in userland (usually dhcpcd(8)), and
33 * we even hardcode lwIP not to parse Router Advertisement messages at all, so
34 * even though those tables are still part of lwIP, they are always empty.
35 * Other ndp(8) functionality are unsupported for similar reasons.
43 #include "lwip/etharp.h"
45 #include "lwip/priv/nd6_priv.h" /* for neighbor_cache */
48 * Process a routing command specifically for an ARP table entry. Return OK if
49 * the routing command has been processed successfully and a routing socket
50 * reply message has already been generated. Return a negative error code on
51 * failure, in which case the caller will generate a reply message instead.
54 lldata_arp_process(unsigned int type
, const ip_addr_t
* dst_addr
,
55 const struct eth_addr
* gw_addr
, struct ifdev
* ifdev
,
56 unsigned int flags
, const struct rtsock_request
* rtr
)
58 const ip4_addr_t
*ip4addr
;
59 struct eth_addr ethaddr
, *ethptr
;
64 netif
= (ifdev
!= NULL
) ? ifdev_get_netif(ifdev
) : NULL
;
66 num
= etharp_find_addr(netif
, ip_2_ip4(dst_addr
), ðptr
, &ip4addr
);
68 if (type
!= RTM_ADD
&& num
< 0)
70 else if (type
== RTM_ADD
&& num
>= 0)
76 * This request is not used by arp(8), so keep things simple.
77 * For RTM_ADD we support only static entries; we support only
78 * those too here, and thus we can use delete-and-readd. If
79 * the ethernet address is not being changed, try readding the
80 * entry with the previous ethernet address.
85 if (etharp_remove_static_entry(ip_2_ip4(dst_addr
)) != ERR_OK
)
90 assert(gw_addr
!= NULL
);
92 memcpy(ðaddr
, gw_addr
, sizeof(ethaddr
));
95 * Adding static, permanent, unpublished, non-proxy entries is
96 * all that lwIP supports right now. We also do not get to
97 * specify the interface, and the way lwIP picks the interface
98 * may in fact result in a different one.
100 if ((err
= etharp_add_static_entry(ip_2_ip4(dst_addr
),
101 ðaddr
)) != ERR_OK
)
102 return util_convert_err(err
);
104 if ((num
= etharp_find_addr(NULL
/*netif*/, ip_2_ip4(dst_addr
),
105 ðptr
, &ip4addr
)) < 0)
106 panic("unable to find just-added static ARP entry");
111 rtsock_msg_arp(num
, type
, rtr
);
116 memcpy(ðaddr
, ethptr
, sizeof(ethaddr
));
118 if (etharp_remove_static_entry(ip_2_ip4(dst_addr
)) != ERR_OK
)
122 * FIXME: the following block is a hack, because we cannot
123 * predict whether the above removal will succeed, while at the
124 * same time we need the entry to be present in order to report
125 * the deleted address to the routing socket. We temporarily
126 * readd and then remove the entry just for the purpose of
127 * generating the routing socket reply. There are other ways
128 * to resolve this, but only a better lwIP etharp API would
129 * allow us to resolve this problem cleanly.
131 (void)etharp_add_static_entry(ip_2_ip4(dst_addr
), ðaddr
);
133 num
= etharp_find_addr(NULL
/*netif*/, ip_2_ip4(dst_addr
),
137 rtsock_msg_arp(num
, type
, rtr
);
139 (void)etharp_remove_static_entry(ip_2_ip4(dst_addr
));
149 * Enumerate ARP table entries. Return TRUE if there is at least one more ARP
150 * table entry, of which the number is stored in 'num'. The caller should set
151 * 'num' to 0 initially, and increase it by one between a successful call and
152 * the next call. Return FALSE if there are no more ARP table entries.
155 lldata_arp_enum(lldata_arp_num_t
* num
)
159 struct eth_addr
*ethaddr
;
161 for (; *num
< ARP_TABLE_SIZE
; ++*num
) {
162 if (etharp_get_entry(*num
, &ip4addr
, &netif
, ðaddr
))
170 * Obtain information about the ARP table entry identified by 'num'. The IPv4
171 * address of the entry is stored in 'addr'. Its ethernet address is stored in
172 * 'gateway'. The associated interface is stored in 'ifdevp', and the entry's
173 * routing flags (RTF_) are stored in 'flagsp'.
176 lldata_arp_get(lldata_arp_num_t num
, struct sockaddr_in
* addr
,
177 struct sockaddr_dlx
* gateway
, struct ifdev
** ifdevp
,
178 unsigned int * flagsp
)
184 struct eth_addr
*ethaddr
;
187 if (!etharp_get_entry(num
, &ip4addr
, &netif
, ðaddr
))
188 panic("request for invalid ARP entry");
190 ip_addr_copy_from_ip4(ipaddr
, *ip4addr
);
192 assert(netif
!= NULL
);
193 ifdev
= netif_get_ifdev(netif
);
195 addr_len
= sizeof(*addr
);
197 addr_put_inet((struct sockaddr
*)addr
, &addr_len
, &ipaddr
,
198 TRUE
/*kame*/, 0 /*port*/);
200 addr_len
= sizeof(*gateway
);
202 addr_put_link((struct sockaddr
*)gateway
, &addr_len
,
203 ifdev_get_index(ifdev
), ifdev_get_iftype(ifdev
), NULL
/*name*/,
204 ethaddr
->addr
, sizeof(ethaddr
->addr
));
209 * TODO: this is not necessarily accurate, but lwIP does not provide us
210 * with information as to whether this is a static entry or not..
212 *flagsp
= RTF_HOST
| RTF_LLINFO
| RTF_LLDATA
| RTF_STATIC
| RTF_CLONED
;
216 * Obtain information about the ND6 neighbor cache entry 'i', which must be a
217 * number between 0 (inclusive) and LWIP_ND6_NUM_NEIGHBORS (exclusive). If an
218 * entry with this number exists, return a pointer to its IPv6 address, and
219 * additional information in each of the given pointers if not NULL. The
220 * associated interface is stored in 'netif'. If the entry has an associated
221 * link-layer address, a pointer to it is stored in 'lladdr'. The entry's
222 * state (ND6_{INCOMPLETE,REACHABLE,STALE,DELAY,PROBE}) is stored in 'state'.
223 * The 'isrouter' parameter is filled with a boolean value indicating whether
224 * the entry is for a router. For ND6_INCOMPLETE and ND6_PROBE, the number of
225 * probes sent so far is stored in 'probes_sent'; for other states, the value
226 * is set to zero. For ND6_REACHABLE and ND6_DELAY, the time until expiration
227 * in ND6_TMR_INTERVAL-millisecond units is stored in 'expire_time'; for other
228 * states, the value is set to zero. If an entry with number 'i' does not
229 * exist, NULL is returned.
231 * TODO: upstream this function to lwIP.
233 static const ip6_addr_t
*
234 nd6_get_neighbor_cache_entry(int8_t i
, struct netif
** netif
,
235 const uint8_t ** lladdr
, uint8_t * state
, uint8_t * isrouter
,
236 uint32_t * probes_sent
, uint32_t * expire_time
)
239 if (i
< 0 || i
>= LWIP_ND6_NUM_NEIGHBORS
||
240 neighbor_cache
[i
].state
== ND6_NO_ENTRY
)
244 *netif
= neighbor_cache
[i
].netif
;
246 if (lladdr
!= NULL
) {
247 if (neighbor_cache
[i
].state
!= ND6_INCOMPLETE
)
248 *lladdr
= neighbor_cache
[i
].lladdr
;
254 *state
= neighbor_cache
[i
].state
;
256 if (isrouter
!= NULL
)
257 *isrouter
= neighbor_cache
[i
].isrouter
;
259 if (probes_sent
!= NULL
) {
260 if (neighbor_cache
[i
].state
== ND6_INCOMPLETE
||
261 neighbor_cache
[i
].state
== ND6_PROBE
)
262 *probes_sent
= neighbor_cache
[i
].counter
.probes_sent
;
267 if (expire_time
!= NULL
) {
268 switch (neighbor_cache
[i
].state
) {
271 neighbor_cache
[i
].counter
.reachable_time
/
275 *expire_time
= neighbor_cache
[i
].counter
.delay_time
;
279 /* Probes are sent once per timer tick. */
280 *expire_time
= (LWIP_ND6_MAX_MULTICAST_SOLICIT
+ 1 -
281 neighbor_cache
[i
].counter
.probes_sent
) *
282 (ND6_TMR_INTERVAL
/ 1000);
285 /* Stale entries do not expire; they get replaced. */
291 return &neighbor_cache
[i
].next_hop_address
;
295 * Find a neighbor cache entry by IPv6 address. Return its index number if
296 * found, or -1 if not. This is a reimplementation of the exact same function
299 * TODO: make this function public in lwIP.
302 nd6_find_neighbor_cache_entry(const ip6_addr_t
* addr
)
306 for (i
= 0; i
< LWIP_ND6_NUM_NEIGHBORS
; i
++) {
307 if (ip6_addr_cmp(addr
, &neighbor_cache
[i
].next_hop_address
))
315 * Find an NDP table entry based on the given interface and IPv6 address. On
316 * success, return OK, with the entry's index number stored in 'nump'. On
317 * failure, return an appropriate error code.
320 lldata_ndp_find(struct ifdev
* ifdev
, const struct sockaddr_in6
* addr
,
321 lldata_ndp_num_t
* nump
)
327 if ((r
= addr_get_inet((const struct sockaddr
*)addr
, sizeof(*addr
),
328 IPADDR_TYPE_V6
, &ipaddr
, TRUE
/*kame*/, NULL
/*port*/)) != OK
)
332 * For given link-local addresses, no zone may be provided in the
333 * address at all. In such cases, add the zone ourselves, using the
336 if (ip6_addr_lacks_zone(ip_2_ip6(&ipaddr
), IP6_UNKNOWN
))
337 ip6_addr_assign_zone(ip_2_ip6(&ipaddr
), IP6_UNKNOWN
,
338 ifdev_get_netif(ifdev
));
340 i
= nd6_find_neighbor_cache_entry(ip_2_ip6(&ipaddr
));
345 * We should compare the neighbor cache entry's associated netif to
346 * the given ifdev, but since the lwIP neighbor cache is currently not
347 * keyed by netif anyway (i.e. the internal lookups are purely by IPv6
348 * address as well), doing so makes little sense in practice.
351 *nump
= (lldata_ndp_num_t
)i
;
356 * Process a routing command specifically for an NDP table entry. Return OK if
357 * the routing command has been processed successfully and a routing socket
358 * reply message has already been generated. Return a negative error code on
359 * failure, in which case the caller will generate a reply message instead.
362 lldata_ndp_process(unsigned int type
, const ip_addr_t
* dst_addr
,
363 const struct eth_addr
* gw_addr
,
364 struct ifdev
* ifdev
, unsigned int flags
,
365 const struct rtsock_request
* rtr
)
367 lldata_ndp_num_t num
;
369 num
= (lldata_ndp_num_t
)
370 nd6_find_neighbor_cache_entry(ip_2_ip6(dst_addr
));
372 if (type
!= RTM_ADD
&& num
< 0)
374 else if (type
== RTM_ADD
&& num
>= 0)
380 rtsock_msg_arp(num
, type
, rtr
);
387 /* TODO: add lwIP support to implement these commands. */
396 * Enumerate NDP table entries. Return TRUE if there is at least one more NDP
397 * table entry, of which the number is stored in 'num'. The caller should set
398 * 'num' to 0 initially, and increase it by one between a successful call and
399 * the next call. Return FALSE if there are no more NDP table entries.
402 lldata_ndp_enum(lldata_ndp_num_t
* num
)
405 for (; *num
< LWIP_ND6_NUM_NEIGHBORS
; ++*num
) {
406 if (nd6_get_neighbor_cache_entry(*num
, NULL
/*netif*/,
407 NULL
/*lladdr*/, NULL
/*state*/, NULL
/*isrouter*/,
408 NULL
/*probes_sent*/, NULL
/*expire_time*/) != NULL
)
416 * Obtain information about the NDP table entry identified by 'num'. The IPv6
417 * address of the entry is stored in 'addr'. Its ethernet address is stored in
418 * 'gateway'. The associated interface is stored in 'ifdevp', and the entry's
419 * routing flags (RTF_) are stored in 'flagsp'.
422 lldata_ndp_get(lldata_ndp_num_t num
, struct sockaddr_in6
* addr
,
423 struct sockaddr_dlx
* gateway
, struct ifdev
** ifdevp
,
424 unsigned int * flagsp
)
426 const ip6_addr_t
*ip6addr
;
428 struct netif
*netif
= NULL
/*gcc*/;
430 const uint8_t *lladdr
= NULL
/*gcc*/;
433 ip6addr
= nd6_get_neighbor_cache_entry(num
, &netif
, &lladdr
,
434 NULL
/*state*/, NULL
/*isrouter*/, NULL
/*probes_sent*/,
435 NULL
/*expire_time*/);
436 assert(ip6addr
!= NULL
);
438 ip_addr_copy_from_ip6(ipaddr
, *ip6addr
);
440 ifdev
= netif_get_ifdev(netif
);
441 assert(ifdev
!= NULL
);
443 addr_len
= sizeof(*addr
);
445 addr_put_inet((struct sockaddr
*)addr
, &addr_len
, &ipaddr
,
446 TRUE
/*kame*/, 0 /*port*/);
448 addr_len
= sizeof(*gateway
);
450 addr_put_link((struct sockaddr
*)gateway
, &addr_len
,
451 ifdev_get_index(ifdev
), ifdev_get_iftype(ifdev
), NULL
/*name*/,
452 lladdr
, ifdev_get_hwlen(ifdev
));
455 *flagsp
= RTF_HOST
| RTF_LLINFO
| RTF_LLDATA
| RTF_CLONED
;
459 * Obtain information about the NDP table entry with the number 'num', which
460 * must be obtained through a previous call to lldata_ndp_find(). On return,
461 * 'asked' is filled with the number of probes sent so far (0 if inapplicable),
462 * 'isrouter' is set to 1 or 0 depending on whether the entry is for a router,
463 * 'state' is set to the entry's state (ND6_LLINFO_), and 'expire' is set to
464 * either the UNIX timestamp of expiry for the entry; 0 for permanent entries.
465 * None of the given pointers must be NULL. This function always succeeds.
468 lldata_ndp_get_info(lldata_ndp_num_t num
, long * asked
, int * isrouter
,
469 int * state
, int * expire
)
471 uint32_t nd6_probes_sent
= 0 /*gcc*/, nd6_expire_time
= 0 /*gcc*/;
472 uint8_t nd6_state
= 0 /*gcc*/, nd6_isrouter
= 0 /*gcc*/;
474 (void)nd6_get_neighbor_cache_entry(num
, NULL
/*netif*/,
475 NULL
/*lladdr*/, &nd6_state
, &nd6_isrouter
, &nd6_probes_sent
,
478 *asked
= (long)nd6_probes_sent
;
480 *isrouter
= !!nd6_isrouter
;
483 case ND6_INCOMPLETE
: *state
= ND6_LLINFO_INCOMPLETE
; break;
484 case ND6_REACHABLE
: *state
= ND6_LLINFO_REACHABLE
; break;
485 case ND6_STALE
: *state
= ND6_LLINFO_STALE
; break;
486 case ND6_DELAY
: *state
= ND6_LLINFO_DELAY
; break;
487 case ND6_PROBE
: *state
= ND6_LLINFO_PROBE
; break;
488 default: panic("unknown ND6 state %u", nd6_state
);
491 if (nd6_expire_time
!= 0)
492 *expire
= clock_time(NULL
) +
493 (int)nd6_expire_time
* (ND6_TMR_INTERVAL
/ 1000);
499 * Process a routing command specifically for a link-layer route, as one of the
500 * specific continuations of processing started by route_process(). The RTM_
501 * routing command is given as 'type'. The route destination is given as
502 * 'dst_addr'; its address type determines whether the operation is for ARP or
503 * NDP. The sockaddr structure for 'gateway' is passed on as is and may have
504 * to be parsed here if not NULL. 'ifdev' is the interface to be associated
505 * with the route; it is non-NULL only if an interface name (IFP) or address
506 * (IFA) was given. The RTF_ flags field has been checked against the globally
507 * supported flags, but may have to be checked for flags that do not apply to
508 * ARP/NDP routes. Return OK or a negative error code, following the same
509 * semantics as route_process().
512 lldata_process(unsigned int type
, const ip_addr_t
* dst_addr
,
513 const struct sockaddr
* gateway
, struct ifdev
* ifdev
,
514 unsigned int flags
, const struct rtsock_request
* rtr
)
516 const struct route_entry
*route
;
517 struct eth_addr ethaddr
, *gw_addr
;
520 assert(flags
& RTF_LLDATA
);
523 * It seems that RTF_UP does not apply to link-layer routing entries.
524 * We basically accept any flags that we can return, but we do not
525 * actually check most of them anywhere.
527 if ((flags
& ~(RTF_HOST
| RTF_LLINFO
| RTF_LLDATA
| RTF_STATIC
|
528 RTF_CLONED
| RTF_ANNOUNCE
)) != 0)
533 if (type
== RTM_ADD
|| type
== RTM_CHANGE
) {
535 * Link-layer entries are always host entries. Not all
536 * requests pass in this flag though, so check only when the
537 * flags are supposed to be set.
539 if ((type
== RTM_ADD
|| type
== RTM_CHANGE
) &&
543 /* lwIP does not support publishing custom entries. */
544 if (flags
& RTF_ANNOUNCE
)
547 /* RTF_GATEWAY is always cleared for link-layer entries. */
548 if (gateway
!= NULL
) {
549 if ((r
= addr_get_link(gateway
, gateway
->sa_len
,
550 NULL
/*name*/, 0 /*name_max*/, ethaddr
.addr
,
551 sizeof(ethaddr
.addr
))) != OK
)
557 if (type
== RTM_ADD
) {
562 * If no interface has been specified, see if the
563 * destination address is on a locally connected
564 * network. If so, use that network's interface.
565 * Otherwise reject the request altogether: we must
566 * have an interface to which to associate the entry.
569 if ((route
= route_lookup(dst_addr
)) != NULL
&&
570 !(route_get_flags(route
) & RTF_GATEWAY
))
571 ifdev
= route_get_ifdev(route
);
578 if (IP_IS_V4(dst_addr
))
579 return lldata_arp_process(type
, dst_addr
, gw_addr
, ifdev
,
582 return lldata_ndp_process(type
, dst_addr
, gw_addr
, ifdev
,