xref: /minix/minix/net/lwip/lldata.c (revision 9f81acbc)
1 /* LWIP service - lldata.c - link-layer (ARP, NDP) data related routines */
2 /*
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.
8  *
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.
12  *
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.
19  *
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.
27  *
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.
36  */
37 
38 #include "lwip.h"
39 #include "lldata.h"
40 #include "route.h"
41 #include "rtsock.h"
42 
43 #include "lwip/etharp.h"
44 #include "lwip/nd6.h"
45 #include "lwip/priv/nd6_priv.h" /* for neighbor_cache */
46 
47 /*
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.
52  */
53 static int
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)
57 {
58 	const ip4_addr_t *ip4addr;
59 	struct eth_addr ethaddr, *ethptr;
60 	struct netif *netif;
61 	lldata_arp_num_t num;
62 	err_t err;
63 
64 	netif = (ifdev != NULL) ? ifdev_get_netif(ifdev) : NULL;
65 
66 	num = etharp_find_addr(netif, ip_2_ip4(dst_addr), &ethptr, &ip4addr);
67 
68 	if (type != RTM_ADD && num < 0)
69 		return ESRCH;
70 	else if (type == RTM_ADD && num >= 0)
71 		return EEXIST;
72 
73 	switch (type) {
74 	case RTM_CHANGE:
75 		/*
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.
81 		 */
82 		if (gw_addr == NULL)
83 			gw_addr = ethptr;
84 
85 		if (etharp_remove_static_entry(ip_2_ip4(dst_addr)) != ERR_OK)
86 			return EPERM;
87 
88 		/* FALLTHROUGH */
89 	case RTM_ADD:
90 		assert(gw_addr != NULL);
91 
92 		memcpy(&ethaddr, gw_addr, sizeof(ethaddr));
93 
94 		/*
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.
99 		 */
100 		if ((err = etharp_add_static_entry(ip_2_ip4(dst_addr),
101 		    &ethaddr)) != ERR_OK)
102 			return util_convert_err(err);
103 
104 		if ((num = etharp_find_addr(NULL /*netif*/, ip_2_ip4(dst_addr),
105 		    &ethptr, &ip4addr)) < 0)
106 			panic("unable to find just-added static ARP entry");
107 
108 		/* FALLTHROUGH */
109 	case RTM_LOCK:
110 	case RTM_GET:
111 		rtsock_msg_arp(num, type, rtr);
112 
113 		return OK;
114 
115 	case RTM_DELETE:
116 		memcpy(&ethaddr, ethptr, sizeof(ethaddr));
117 
118 		if (etharp_remove_static_entry(ip_2_ip4(dst_addr)) != ERR_OK)
119 			return EPERM;
120 
121 		/*
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.
130 		 */
131 		(void)etharp_add_static_entry(ip_2_ip4(dst_addr), &ethaddr);
132 
133 		num = etharp_find_addr(NULL /*netif*/, ip_2_ip4(dst_addr),
134 		    &ethptr, &ip4addr);
135 		assert(num >= 0);
136 
137 		rtsock_msg_arp(num, type, rtr);
138 
139 		(void)etharp_remove_static_entry(ip_2_ip4(dst_addr));
140 
141 		return OK;
142 
143 	default:
144 		return EINVAL;
145 	}
146 }
147 
148 /*
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.
153  */
154 int
155 lldata_arp_enum(lldata_arp_num_t * num)
156 {
157 	ip4_addr_t *ip4addr;
158 	struct netif *netif;
159 	struct eth_addr *ethaddr;
160 
161 	for (; *num < ARP_TABLE_SIZE; ++*num) {
162 		if (etharp_get_entry(*num, &ip4addr, &netif, &ethaddr))
163 			return TRUE;
164 	}
165 
166 	return FALSE;
167 }
168 
169 /*
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'.
174  */
175 void
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)
179 {
180 	ip_addr_t ipaddr;
181 	ip4_addr_t *ip4addr;
182 	struct netif *netif;
183 	struct ifdev *ifdev;
184 	struct eth_addr *ethaddr;
185 	socklen_t addr_len;
186 
187 	if (!etharp_get_entry(num, &ip4addr, &netif, &ethaddr))
188 		panic("request for invalid ARP entry");
189 
190 	ip_addr_copy_from_ip4(ipaddr, *ip4addr);
191 
192 	assert(netif != NULL);
193 	ifdev = netif_get_ifdev(netif);
194 
195 	addr_len = sizeof(*addr);
196 
197 	addr_put_inet((struct sockaddr *)addr, &addr_len, &ipaddr,
198 	    TRUE /*kame*/, 0 /*port*/);
199 
200 	addr_len = sizeof(*gateway);
201 
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));
205 
206 	*ifdevp = ifdev;
207 
208 	/*
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..
211 	 */
212 	*flagsp = RTF_HOST | RTF_LLINFO | RTF_LLDATA | RTF_STATIC | RTF_CLONED;
213 }
214 
215 /*
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.
230  *
231  * TODO: upstream this function to lwIP.
232  */
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)
237 {
238 
239 	if (i < 0 || i >= LWIP_ND6_NUM_NEIGHBORS ||
240 	    neighbor_cache[i].state == ND6_NO_ENTRY)
241 		return NULL;
242 
243 	if (netif != NULL)
244 		*netif = neighbor_cache[i].netif;
245 
246 	if (lladdr != NULL) {
247 		if (neighbor_cache[i].state != ND6_INCOMPLETE)
248 			*lladdr = neighbor_cache[i].lladdr;
249 		else
250 			*lladdr = NULL;
251 	}
252 
253 	if (state != NULL)
254 		*state = neighbor_cache[i].state;
255 
256 	if (isrouter != NULL)
257 		*isrouter = neighbor_cache[i].isrouter;
258 
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;
263 		else
264 			*probes_sent = 0;
265 	}
266 
267 	if (expire_time != NULL) {
268 		switch (neighbor_cache[i].state) {
269 		case ND6_REACHABLE:
270 			*expire_time =
271 			    neighbor_cache[i].counter.reachable_time /
272 			    ND6_TMR_INTERVAL;
273 			break;
274 		case ND6_DELAY:
275 			*expire_time = neighbor_cache[i].counter.delay_time;
276 			break;
277 		case ND6_INCOMPLETE:
278 		case ND6_PROBE:
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);
283 			break;
284 		default:
285 			/* Stale entries do not expire; they get replaced. */
286 			*expire_time = 0;
287 			break;
288 		}
289 	}
290 
291 	return &neighbor_cache[i].next_hop_address;
292 }
293 
294 /*
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
297  * internal to lwIP.
298  *
299  * TODO: make this function public in lwIP.
300  */
301 static int8_t
302 nd6_find_neighbor_cache_entry(const ip6_addr_t * addr)
303 {
304 	int8_t i;
305 
306 	for (i = 0; i < LWIP_ND6_NUM_NEIGHBORS; i++) {
307 		if (ip6_addr_cmp(addr, &neighbor_cache[i].next_hop_address))
308 			return i;
309 	}
310 
311 	return -1;
312 }
313 
314 /*
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.
318  */
319 int
320 lldata_ndp_find(struct ifdev * ifdev, const struct sockaddr_in6 * addr,
321 	lldata_ndp_num_t * nump)
322 {
323 	ip_addr_t ipaddr;
324 	int8_t i;
325 	int r;
326 
327 	if ((r = addr_get_inet((const struct sockaddr *)addr, sizeof(*addr),
328 	    IPADDR_TYPE_V6, &ipaddr, TRUE /*kame*/, NULL /*port*/)) != OK)
329 		return r;
330 
331 	/*
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
334 	 * given interface.
335 	 */
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));
339 
340 	i = nd6_find_neighbor_cache_entry(ip_2_ip6(&ipaddr));
341 	if (i < 0)
342 		return ESRCH;
343 
344 	/*
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.
349 	 */
350 
351 	*nump = (lldata_ndp_num_t)i;
352 	return OK;
353 }
354 
355 /*
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.
360  */
361 static int
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)
366 {
367 	lldata_ndp_num_t num;
368 
369 	num = (lldata_ndp_num_t)
370 	    nd6_find_neighbor_cache_entry(ip_2_ip6(dst_addr));
371 
372 	if (type != RTM_ADD && num < 0)
373 		return ESRCH;
374 	else if (type == RTM_ADD && num >= 0)
375 		return EEXIST;
376 
377 	switch (type) {
378 	case RTM_LOCK:
379 	case RTM_GET:
380 		rtsock_msg_arp(num, type, rtr);
381 
382 		return OK;
383 
384 	case RTM_ADD:
385 	case RTM_CHANGE:
386 	case RTM_DELETE:
387 		/* TODO: add lwIP support to implement these commands. */
388 		return ENOSYS;
389 
390 	default:
391 		return EINVAL;
392 	}
393 }
394 
395 /*
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.
400  */
401 int
402 lldata_ndp_enum(lldata_ndp_num_t * num)
403 {
404 
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)
409 			return TRUE;
410 	}
411 
412 	return FALSE;
413 }
414 
415 /*
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'.
420  */
421 void
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)
425 {
426 	const ip6_addr_t *ip6addr;
427 	ip_addr_t ipaddr;
428 	struct netif *netif;
429 	struct ifdev *ifdev;
430 	const uint8_t *lladdr;
431 	socklen_t addr_len;
432 
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);
437 
438 	ip_addr_copy_from_ip6(ipaddr, *ip6addr);
439 
440 	ifdev = netif_get_ifdev(netif);
441 	assert(ifdev != NULL);
442 
443 	addr_len = sizeof(*addr);
444 
445 	addr_put_inet((struct sockaddr *)addr, &addr_len, &ipaddr,
446 	    TRUE /*kame*/, 0 /*port*/);
447 
448 	addr_len = sizeof(*gateway);
449 
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));
453 
454 	*ifdevp = ifdev;
455 	*flagsp = RTF_HOST | RTF_LLINFO | RTF_LLDATA | RTF_CLONED;
456 }
457 
458 /*
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.
466  */
467 void
468 lldata_ndp_get_info(lldata_ndp_num_t num, long * asked, int * isrouter,
469 	int * state, int * expire)
470 {
471 	uint32_t nd6_probes_sent = 0 /*gcc*/, nd6_expire_time = 0 /*gcc*/;
472 	uint8_t nd6_state = 0 /*gcc*/, nd6_isrouter = 0 /*gcc*/;
473 
474 	(void)nd6_get_neighbor_cache_entry(num, NULL /*netif*/,
475 	    NULL /*lladdr*/, &nd6_state, &nd6_isrouter, &nd6_probes_sent,
476 	    &nd6_expire_time);
477 
478 	*asked = (long)nd6_probes_sent;
479 
480 	*isrouter = !!nd6_isrouter;
481 
482 	switch (nd6_state) {
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);
489 	}
490 
491 	if (nd6_expire_time != 0)
492 		*expire = clock_time(NULL) +
493 		    (int)nd6_expire_time * (ND6_TMR_INTERVAL / 1000);
494 	else
495 		*expire = 0;
496 }
497 
498 /*
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().
510  */
511 int
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)
515 {
516 	const struct route_entry *route;
517 	struct eth_addr ethaddr, *gw_addr;
518 	int r;
519 
520 	assert(flags & RTF_LLDATA);
521 
522 	/*
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.
526 	 */
527 	if ((flags & ~(RTF_HOST | RTF_LLINFO | RTF_LLDATA | RTF_STATIC |
528 	   RTF_CLONED | RTF_ANNOUNCE)) != 0)
529 		return EINVAL;
530 
531 	gw_addr = NULL;
532 
533 	if (type == RTM_ADD || type == RTM_CHANGE) {
534 		/*
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.
538 		 */
539 		if ((type == RTM_ADD || type == RTM_CHANGE) &&
540 		    !(flags & RTF_HOST))
541 			return EINVAL;
542 
543 		/* lwIP does not support publishing custom entries. */
544 		if (flags & RTF_ANNOUNCE)
545 			return ENOSYS;
546 
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)
552 				return r;
553 
554 			gw_addr = &ethaddr;
555 		}
556 
557 		if (type == RTM_ADD) {
558 			if (gateway == NULL)
559 				return EINVAL;
560 
561 			/*
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.
567 			 */
568 			if (ifdev == NULL) {
569 				if ((route = route_lookup(dst_addr)) != NULL &&
570 				    !(route_get_flags(route) & RTF_GATEWAY))
571 					ifdev = route_get_ifdev(route);
572 				else
573 					return ENETUNREACH;
574 			}
575 		}
576 	}
577 
578 	if (IP_IS_V4(dst_addr))
579 		return lldata_arp_process(type, dst_addr, gw_addr, ifdev,
580 		    flags, rtr);
581 	else
582 		return lldata_ndp_process(type, dst_addr, gw_addr, ifdev,
583 		    flags, rtr);
584 }
585