xref: /xnu-10002.1.13/bsd/net/if_llreach.c (revision 1031c584a5e37aff177559b9f69dbd3c8c3fd30a)
1 /*
2  * Copyright (c) 2011-2020 Apple Inc. All rights reserved.
3  *
4  * @APPLE_OSREFERENCE_LICENSE_HEADER_START@
5  *
6  * This file contains Original Code and/or Modifications of Original Code
7  * as defined in and that are subject to the Apple Public Source License
8  * Version 2.0 (the 'License'). You may not use this file except in
9  * compliance with the License. The rights granted to you under the License
10  * may not be used to create, or enable the creation or redistribution of,
11  * unlawful or unlicensed copies of an Apple operating system, or to
12  * circumvent, violate, or enable the circumvention or violation of, any
13  * terms of an Apple operating system software license agreement.
14  *
15  * Please obtain a copy of the License at
16  * http://www.opensource.apple.com/apsl/ and read it before using this file.
17  *
18  * The Original Code and all software distributed under the License are
19  * distributed on an 'AS IS' basis, WITHOUT WARRANTY OF ANY KIND, EITHER
20  * EXPRESS OR IMPLIED, AND APPLE HEREBY DISCLAIMS ALL SUCH WARRANTIES,
21  * INCLUDING WITHOUT LIMITATION, ANY WARRANTIES OF MERCHANTABILITY,
22  * FITNESS FOR A PARTICULAR PURPOSE, QUIET ENJOYMENT OR NON-INFRINGEMENT.
23  * Please see the License for the specific language governing rights and
24  * limitations under the License.
25  *
26  * @APPLE_OSREFERENCE_LICENSE_HEADER_END@
27  */
28 
29 /*
30  * Link-layer Reachability Record
31  *
32  * Each interface maintains a red-black tree which contains records related
33  * to the on-link nodes which we are interested in communicating with.  Each
34  * record gets allocated and inserted into the tree in the following manner:
35  * upon processing an ARP announcement or reply from a known node (i.e. there
36  * exists a ARP route entry for the node), and if a link-layer reachability
37  * record for the node doesn't yet exist; and, upon processing a ND6 RS/RA/
38  * NS/NA/redirect from a node, and if a link-layer reachability record for the
39  * node doesn't yet exist.
40  *
41  * Each newly created record is then referred to by the resolver route entry;
42  * if a record already exists, its reference count gets increased for the new
43  * resolver entry which now refers to it.  A record gets removed from the tree
44  * and freed once its reference counts drops to zero, i.e. when there is no
45  * more resolver entry referring to it.
46  *
47  * A record contains the link-layer protocol (e.g. Ethertype IP/IPv6), the
48  * HW address of the sender, the "last heard from" timestamp (lr_lastrcvd) and
49  * the number of references made to it (lr_reqcnt).  Because the key for each
50  * record in the red-black tree consists of the link-layer protocol, therefore
51  * the namespace for the records is partitioned based on the type of link-layer
52  * protocol, i.e. an Ethertype IP link-layer record is only referred to by one
53  * or more ARP entries; an Ethernet IPv6 link-layer record is only referred to
54  * by one or more ND6 entries.  Therefore, lr_reqcnt represents the number of
55  * resolver entry references to the record for the same protocol family.
56  *
57  * Upon receiving packets from the network, the protocol's input callback
58  * (e.g. ether_inet{6}_input) informs the corresponding resolver (ARP/ND6)
59  * about the (link-layer) origin of the packet.  This results in searching
60  * for a matching record in the red-black tree for the interface where the
61  * packet arrived on.  If there's no match, no further processing takes place.
62  * Otherwise, the lr_lastrcvd timestamp of the record is updated.
63  *
64  * When an IP/IPv6 packet is transmitted to the resolver (i.e. the destination
65  * is on-link), ARP/ND6 records the "last spoken to" timestamp in the route
66  * entry ({la,ln}_lastused).
67  *
68  * The reachability of the on-link node is determined by the following logic,
69  * upon sending a packet thru the resolver:
70  *
71  *   a) If the record is only used by exactly one resolver entry (lr_reqcnt
72  *	is 1), i.e. the target host does not have IP/IPv6 aliases that we know
73  *	of, check if lr_lastrcvd is "recent."  If so, simply send the packet;
74  *	otherwise, re-resolve the target node.
75  *
76  *   b) If the record is shared by multiple resolver entries (lr_reqcnt is
77  *	greater than 1), i.e. the target host has more than one IP/IPv6 aliases
78  *	on the same network interface, we can't rely on lr_lastrcvd alone, as
79  *	one of the IP/IPv6 aliases could have been silently moved to another
80  *	node for which we don't have a link-layer record.  If lr_lastrcvd is
81  *	not "recent", we re-resolve the target node.  Otherwise, we perform
82  *	an additional check against {la,ln}_lastused to see whether it is also
83  *	"recent", relative to lr_lastrcvd.  If so, simply send the packet;
84  *	otherwise, re-resolve the target node.
85  *
86  * The value for "recent" is configurable by adjusting the basetime value for
87  * net.link.ether.inet.arp_llreach_base or net.inet6.icmp6.nd6_llreach_base.
88  * The default basetime value is 30 seconds, and the actual expiration time
89  * is calculated by multiplying the basetime value with some random factor,
90  * which results in a number between 15 to 45 seconds.  Setting the basetime
91  * value to 0 effectively disables this feature for the corresponding resolver.
92  *
93  * Assumptions:
94  *
95  * The above logic is based upon the following assumptions:
96  *
97  *   i) Network traffics are mostly bi-directional, i.e. the act of sending
98  *	packets to an on-link node would most likely cause us to receive
99  *	packets from that node.
100  *
101  *  ii) If the on-link node's IP/IPv6 address silently moves to another
102  *	on-link node for which we are not aware of, non-unicast packets
103  *	from the old node would trigger the record's lr_lastrcvd to be
104  *	kept recent.
105  *
106  * We can mitigate the above by having the resolver check its {la,ln}_lastused
107  * timestamp at all times, i.e. not only when lr_reqcnt is greater than 1; but
108  * we currently optimize for the common cases.
109  */
110 
111 #include <sys/param.h>
112 #include <sys/systm.h>
113 #include <sys/kernel.h>
114 #include <sys/malloc.h>
115 #include <sys/tree.h>
116 #include <sys/sysctl.h>
117 #include <sys/mcache.h>
118 #include <sys/protosw.h>
119 
120 #include <dev/random/randomdev.h>
121 
122 #include <net/if_dl.h>
123 #include <net/if.h>
124 #include <net/if_var.h>
125 #include <net/if_llreach.h>
126 #include <net/dlil.h>
127 #include <net/kpi_interface.h>
128 #include <net/route.h>
129 
130 #include <kern/assert.h>
131 #include <kern/locks.h>
132 #include <kern/zalloc.h>
133 
134 #include <netinet6/in6_var.h>
135 #include <netinet6/nd6.h>
136 
137 static KALLOC_TYPE_DEFINE(iflr_zone, struct if_llreach, NET_KT_DEFAULT);
138 
139 static struct if_llreach *iflr_alloc(zalloc_flags_t);
140 static void iflr_free(struct if_llreach *);
141 static __inline int iflr_cmp(const struct if_llreach *,
142     const struct if_llreach *);
143 static __inline int iflr_reachable(struct if_llreach *, int, u_int64_t);
144 static int sysctl_llreach_ifinfo SYSCTL_HANDLER_ARGS;
145 
146 /* The following is protected by if_llreach_lock */
147 RB_GENERATE_PREV(ll_reach_tree, if_llreach, lr_link, iflr_cmp);
148 
149 SYSCTL_DECL(_net_link_generic_system);
150 
151 SYSCTL_NODE(_net_link_generic_system, OID_AUTO, llreach_info,
152     CTLFLAG_RD | CTLFLAG_LOCKED, sysctl_llreach_ifinfo,
153     "Per-interface tree of source link-layer reachability records");
154 
155 /*
156  * Link-layer reachability is based off node constants in RFC4861.
157  */
158 #define LL_COMPUTE_RTIME(x)     ND_COMPUTE_RTIME(x)
159 
160 void
ifnet_llreach_ifattach(struct ifnet * ifp,boolean_t reuse)161 ifnet_llreach_ifattach(struct ifnet *ifp, boolean_t reuse)
162 {
163 	lck_rw_lock_exclusive(&ifp->if_llreach_lock);
164 	/* Initialize link-layer source tree (if not already) */
165 	if (!reuse) {
166 		RB_INIT(&ifp->if_ll_srcs);
167 	}
168 	lck_rw_done(&ifp->if_llreach_lock);
169 }
170 
171 void
ifnet_llreach_ifdetach(struct ifnet * ifp)172 ifnet_llreach_ifdetach(struct ifnet *ifp)
173 {
174 #pragma unused(ifp)
175 	/*
176 	 * Nothing to do for now; the link-layer source tree might
177 	 * contain entries at this point, that are still referred
178 	 * to by route entries pointing to this ifp.
179 	 */
180 }
181 
182 /*
183  * Link-layer source tree comparison function.
184  *
185  * An ordered predicate is necessary; bcmp() is not documented to return
186  * an indication of order, memcmp() is, and is an ISO C99 requirement.
187  */
188 static __inline int
iflr_cmp(const struct if_llreach * a,const struct if_llreach * b)189 iflr_cmp(const struct if_llreach *a, const struct if_llreach *b)
190 {
191 	return memcmp(&a->lr_key, &b->lr_key, sizeof(a->lr_key));
192 }
193 
194 static __inline int
iflr_reachable(struct if_llreach * lr,int cmp_delta,u_int64_t tval)195 iflr_reachable(struct if_llreach *lr, int cmp_delta, u_int64_t tval)
196 {
197 	u_int64_t now;
198 	u_int64_t expire;
199 
200 	now = net_uptime();             /* current approx. uptime */
201 	/*
202 	 * No need for lr_lock; atomically read the last rcvd uptime.
203 	 */
204 	expire = lr->lr_lastrcvd + lr->lr_reachable;
205 	/*
206 	 * If we haven't heard back from the local host for over
207 	 * lr_reachable seconds, consider that the host is no
208 	 * longer reachable.
209 	 */
210 	if (!cmp_delta) {
211 		return expire >= now;
212 	}
213 	/*
214 	 * If the caller supplied a reference time, consider the
215 	 * host is reachable if the record hasn't expired (see above)
216 	 * and if the reference time is within the past lr_reachable
217 	 * seconds.
218 	 */
219 	return (expire >= now) && (now - tval) < lr->lr_reachable;
220 }
221 
222 int
ifnet_llreach_reachable(struct if_llreach * lr)223 ifnet_llreach_reachable(struct if_llreach *lr)
224 {
225 	/*
226 	 * Check whether the cache is too old to be trusted.
227 	 */
228 	return iflr_reachable(lr, 0, 0);
229 }
230 
231 int
ifnet_llreach_reachable_delta(struct if_llreach * lr,u_int64_t tval)232 ifnet_llreach_reachable_delta(struct if_llreach *lr, u_int64_t tval)
233 {
234 	/*
235 	 * Check whether the cache is too old to be trusted.
236 	 */
237 	return iflr_reachable(lr, 1, tval);
238 }
239 
240 void
ifnet_llreach_set_reachable(struct ifnet * ifp,u_int16_t llproto,void * addr,unsigned int alen)241 ifnet_llreach_set_reachable(struct ifnet *ifp, u_int16_t llproto, void *addr,
242     unsigned int alen)
243 {
244 	struct if_llreach find, *lr;
245 
246 	VERIFY(alen == IF_LLREACH_MAXLEN);      /* for now */
247 
248 	find.lr_key.proto = llproto;
249 	bcopy(addr, &find.lr_key.addr, IF_LLREACH_MAXLEN);
250 
251 	lck_rw_lock_shared(&ifp->if_llreach_lock);
252 	lr = RB_FIND(ll_reach_tree, &ifp->if_ll_srcs, &find);
253 	if (lr == NULL) {
254 		lck_rw_done(&ifp->if_llreach_lock);
255 		return;
256 	}
257 	/*
258 	 * No need for lr_lock; atomically update the last rcvd uptime.
259 	 */
260 	lr->lr_lastrcvd = net_uptime();
261 	lck_rw_done(&ifp->if_llreach_lock);
262 }
263 
264 struct if_llreach *
ifnet_llreach_alloc(struct ifnet * ifp,u_int16_t llproto,void * addr,unsigned int alen,u_int32_t llreach_base)265 ifnet_llreach_alloc(struct ifnet *ifp, u_int16_t llproto, void *addr,
266     unsigned int alen, u_int32_t llreach_base)
267 {
268 	struct if_llreach find, *lr;
269 	struct timeval cnow;
270 
271 	if (llreach_base == 0) {
272 		return NULL;
273 	}
274 
275 	VERIFY(alen == IF_LLREACH_MAXLEN);      /* for now */
276 
277 	find.lr_key.proto = llproto;
278 	bcopy(addr, &find.lr_key.addr, IF_LLREACH_MAXLEN);
279 
280 	lck_rw_lock_shared(&ifp->if_llreach_lock);
281 	lr = RB_FIND(ll_reach_tree, &ifp->if_ll_srcs, &find);
282 	if (lr != NULL) {
283 found:
284 		IFLR_LOCK(lr);
285 		VERIFY(lr->lr_reqcnt >= 1);
286 		lr->lr_reqcnt++;
287 		VERIFY(lr->lr_reqcnt != 0);
288 		IFLR_ADDREF_LOCKED(lr);         /* for caller */
289 		lr->lr_lastrcvd = net_uptime(); /* current approx. uptime */
290 		IFLR_UNLOCK(lr);
291 		lck_rw_done(&ifp->if_llreach_lock);
292 		return lr;
293 	}
294 
295 	if (!lck_rw_lock_shared_to_exclusive(&ifp->if_llreach_lock)) {
296 		lck_rw_lock_exclusive(&ifp->if_llreach_lock);
297 	}
298 
299 	LCK_RW_ASSERT(&ifp->if_llreach_lock, LCK_RW_ASSERT_EXCLUSIVE);
300 
301 	/* in case things have changed while becoming writer */
302 	lr = RB_FIND(ll_reach_tree, &ifp->if_ll_srcs, &find);
303 	if (lr != NULL) {
304 		goto found;
305 	}
306 
307 	lr = iflr_alloc(Z_WAITOK);
308 
309 	IFLR_LOCK(lr);
310 	lr->lr_reqcnt++;
311 	VERIFY(lr->lr_reqcnt == 1);
312 	IFLR_ADDREF_LOCKED(lr);                 /* for RB tree */
313 	IFLR_ADDREF_LOCKED(lr);                 /* for caller */
314 	lr->lr_lastrcvd = net_uptime();         /* current approx. uptime */
315 	lr->lr_baseup = lr->lr_lastrcvd;        /* base uptime */
316 	getmicrotime(&cnow);
317 	lr->lr_basecal = cnow.tv_sec;           /* base calendar time */
318 	lr->lr_basereachable = llreach_base;
319 	lr->lr_reachable = LL_COMPUTE_RTIME(lr->lr_basereachable * 1000);
320 	lr->lr_debug |= IFD_ATTACHED;
321 	lr->lr_ifp = ifp;
322 	lr->lr_key.proto = llproto;
323 	bcopy(addr, &lr->lr_key.addr, IF_LLREACH_MAXLEN);
324 	lr->lr_rssi = IFNET_RSSI_UNKNOWN;
325 	lr->lr_lqm = IFNET_LQM_THRESH_UNKNOWN;
326 	lr->lr_npm = IFNET_NPM_THRESH_UNKNOWN;
327 	RB_INSERT(ll_reach_tree, &ifp->if_ll_srcs, lr);
328 	IFLR_UNLOCK(lr);
329 	lck_rw_done(&ifp->if_llreach_lock);
330 
331 	return lr;
332 }
333 
334 void
ifnet_llreach_free(struct if_llreach * lr)335 ifnet_llreach_free(struct if_llreach *lr)
336 {
337 	struct ifnet *ifp;
338 
339 	/* no need to lock here; lr_ifp never changes */
340 	ifp = lr->lr_ifp;
341 
342 	lck_rw_lock_exclusive(&ifp->if_llreach_lock);
343 	IFLR_LOCK(lr);
344 	if (lr->lr_reqcnt == 0) {
345 		panic("%s: lr=%p negative reqcnt", __func__, lr);
346 		/* NOTREACHED */
347 	}
348 	--lr->lr_reqcnt;
349 	if (lr->lr_reqcnt > 0) {
350 		IFLR_UNLOCK(lr);
351 		lck_rw_done(&ifp->if_llreach_lock);
352 		IFLR_REMREF(lr);                /* for caller */
353 		return;
354 	}
355 	if (!(lr->lr_debug & IFD_ATTACHED)) {
356 		panic("%s: Attempt to detach an unattached llreach lr=%p",
357 		    __func__, lr);
358 		/* NOTREACHED */
359 	}
360 	lr->lr_debug &= ~IFD_ATTACHED;
361 	RB_REMOVE(ll_reach_tree, &ifp->if_ll_srcs, lr);
362 	IFLR_UNLOCK(lr);
363 	lck_rw_done(&ifp->if_llreach_lock);
364 
365 	IFLR_REMREF(lr);                        /* for RB tree */
366 	IFLR_REMREF(lr);                        /* for caller */
367 }
368 
369 u_int64_t
ifnet_llreach_up2calexp(struct if_llreach * lr,u_int64_t uptime)370 ifnet_llreach_up2calexp(struct if_llreach *lr, u_int64_t uptime)
371 {
372 	u_int64_t calendar = 0;
373 
374 	if (uptime != 0) {
375 		struct timeval cnow;
376 		u_int64_t unow;
377 
378 		getmicrotime(&cnow);    /* current calendar time */
379 		unow = net_uptime();    /* current approx. uptime */
380 		/*
381 		 * Take into account possible calendar time changes;
382 		 * adjust base calendar value if necessary, i.e.
383 		 * the calendar skew should equate to the uptime skew.
384 		 */
385 		lr->lr_basecal += (cnow.tv_sec - lr->lr_basecal) -
386 		    (unow - lr->lr_baseup);
387 
388 		calendar = lr->lr_basecal + lr->lr_reachable +
389 		    (uptime - lr->lr_baseup);
390 	}
391 
392 	return calendar;
393 }
394 
395 u_int64_t
ifnet_llreach_up2upexp(struct if_llreach * lr,u_int64_t uptime)396 ifnet_llreach_up2upexp(struct if_llreach *lr, u_int64_t uptime)
397 {
398 	return lr->lr_reachable + uptime;
399 }
400 
401 int
ifnet_llreach_get_defrouter(struct ifnet * ifp,sa_family_t af,struct ifnet_llreach_info * iflri)402 ifnet_llreach_get_defrouter(struct ifnet *ifp, sa_family_t af,
403     struct ifnet_llreach_info *iflri)
404 {
405 	struct radix_node_head *rnh;
406 	struct sockaddr_storage dst_ss, mask_ss;
407 	struct rtentry *rt;
408 	int error = ESRCH;
409 
410 	VERIFY(ifp != NULL && iflri != NULL &&
411 	    (af == AF_INET || af == AF_INET6));
412 
413 	bzero(iflri, sizeof(*iflri));
414 
415 	if ((rnh = rt_tables[af]) == NULL) {
416 		return error;
417 	}
418 
419 	bzero(&dst_ss, sizeof(dst_ss));
420 	bzero(&mask_ss, sizeof(mask_ss));
421 	dst_ss.ss_family = af;
422 	dst_ss.ss_len = (af == AF_INET) ? sizeof(struct sockaddr_in) :
423 	    sizeof(struct sockaddr_in6);
424 
425 	lck_mtx_lock(rnh_lock);
426 	rt = rt_lookup(TRUE, SA(&dst_ss), SA(&mask_ss), rnh, ifp->if_index);
427 	if (rt != NULL) {
428 		struct rtentry *gwrt;
429 
430 		RT_LOCK(rt);
431 		if ((rt->rt_flags & RTF_GATEWAY) &&
432 		    (gwrt = rt->rt_gwroute) != NULL &&
433 		    rt_key(rt)->sa_family == rt_key(gwrt)->sa_family &&
434 		    (gwrt->rt_flags & RTF_UP)) {
435 			RT_UNLOCK(rt);
436 			RT_LOCK(gwrt);
437 			if (gwrt->rt_llinfo_get_iflri != NULL) {
438 				(*gwrt->rt_llinfo_get_iflri)(gwrt, iflri);
439 				error = 0;
440 			}
441 			RT_UNLOCK(gwrt);
442 		} else {
443 			RT_UNLOCK(rt);
444 		}
445 		rtfree_locked(rt);
446 	}
447 	lck_mtx_unlock(rnh_lock);
448 
449 	return error;
450 }
451 
452 static struct if_llreach *
iflr_alloc(zalloc_flags_t how)453 iflr_alloc(zalloc_flags_t how)
454 {
455 	struct if_llreach *lr = zalloc_flags(iflr_zone, how | Z_ZERO);
456 
457 	if (lr) {
458 		lck_mtx_init(&lr->lr_lock, &ifnet_lock_group, &ifnet_lock_attr);
459 		lr->lr_debug |= IFD_ALLOC;
460 	}
461 	return lr;
462 }
463 
464 static void
iflr_free(struct if_llreach * lr)465 iflr_free(struct if_llreach *lr)
466 {
467 	IFLR_LOCK(lr);
468 	if (lr->lr_debug & IFD_ATTACHED) {
469 		panic("%s: attached lr=%p is being freed", __func__, lr);
470 		/* NOTREACHED */
471 	} else if (!(lr->lr_debug & IFD_ALLOC)) {
472 		panic("%s: lr %p cannot be freed", __func__, lr);
473 		/* NOTREACHED */
474 	} else if (lr->lr_refcnt != 0) {
475 		panic("%s: non-zero refcount lr=%p", __func__, lr);
476 		/* NOTREACHED */
477 	} else if (lr->lr_reqcnt != 0) {
478 		panic("%s: non-zero reqcnt lr=%p", __func__, lr);
479 		/* NOTREACHED */
480 	}
481 	lr->lr_debug &= ~IFD_ALLOC;
482 	IFLR_UNLOCK(lr);
483 
484 	lck_mtx_destroy(&lr->lr_lock, &ifnet_lock_group);
485 	zfree(iflr_zone, lr);
486 }
487 
488 void
iflr_addref(struct if_llreach * lr,int locked)489 iflr_addref(struct if_llreach *lr, int locked)
490 {
491 	if (!locked) {
492 		IFLR_LOCK(lr);
493 	} else {
494 		IFLR_LOCK_ASSERT_HELD(lr);
495 	}
496 
497 	if (++lr->lr_refcnt == 0) {
498 		panic("%s: lr=%p wraparound refcnt", __func__, lr);
499 		/* NOTREACHED */
500 	}
501 	if (!locked) {
502 		IFLR_UNLOCK(lr);
503 	}
504 }
505 
506 void
iflr_remref(struct if_llreach * lr)507 iflr_remref(struct if_llreach *lr)
508 {
509 	IFLR_LOCK(lr);
510 	if (lr->lr_refcnt == 0) {
511 		panic("%s: lr=%p negative refcnt", __func__, lr);
512 		/* NOTREACHED */
513 	}
514 	--lr->lr_refcnt;
515 	if (lr->lr_refcnt > 0) {
516 		IFLR_UNLOCK(lr);
517 		return;
518 	}
519 	IFLR_UNLOCK(lr);
520 
521 	iflr_free(lr);  /* deallocate it */
522 }
523 
524 void
ifnet_lr2ri(struct if_llreach * lr,struct rt_reach_info * ri)525 ifnet_lr2ri(struct if_llreach *lr, struct rt_reach_info *ri)
526 {
527 	struct if_llreach_info lri;
528 
529 	IFLR_LOCK_ASSERT_HELD(lr);
530 
531 	bzero(ri, sizeof(*ri));
532 	ifnet_lr2lri(lr, &lri);
533 	ri->ri_refcnt = lri.lri_refcnt;
534 	ri->ri_probes = lri.lri_probes;
535 	ri->ri_rcv_expire = lri.lri_expire;
536 	ri->ri_rssi = lri.lri_rssi;
537 	ri->ri_lqm = lri.lri_lqm;
538 	ri->ri_npm = lri.lri_npm;
539 }
540 
541 void
ifnet_lr2iflri(struct if_llreach * lr,struct ifnet_llreach_info * iflri)542 ifnet_lr2iflri(struct if_llreach *lr, struct ifnet_llreach_info *iflri)
543 {
544 	IFLR_LOCK_ASSERT_HELD(lr);
545 
546 	bzero(iflri, sizeof(*iflri));
547 	/*
548 	 * Note here we return request count, not actual memory refcnt.
549 	 */
550 	iflri->iflri_refcnt = lr->lr_reqcnt;
551 	iflri->iflri_probes = lr->lr_probes;
552 	iflri->iflri_rcv_expire = ifnet_llreach_up2upexp(lr, lr->lr_lastrcvd);
553 	iflri->iflri_curtime = net_uptime();
554 	switch (lr->lr_key.proto) {
555 	case ETHERTYPE_IP:
556 		iflri->iflri_netproto = PF_INET;
557 		break;
558 	case ETHERTYPE_IPV6:
559 		iflri->iflri_netproto = PF_INET6;
560 		break;
561 	default:
562 		/*
563 		 * This shouldn't be possible for the time being,
564 		 * since link-layer reachability records are only
565 		 * kept for ARP and ND6.
566 		 */
567 		iflri->iflri_netproto = PF_UNSPEC;
568 		break;
569 	}
570 	bcopy(&lr->lr_key.addr, &iflri->iflri_addr, IF_LLREACH_MAXLEN);
571 	iflri->iflri_rssi = lr->lr_rssi;
572 	iflri->iflri_lqm = lr->lr_lqm;
573 	iflri->iflri_npm = lr->lr_npm;
574 }
575 
576 void
ifnet_lr2lri(struct if_llreach * lr,struct if_llreach_info * lri)577 ifnet_lr2lri(struct if_llreach *lr, struct if_llreach_info *lri)
578 {
579 	IFLR_LOCK_ASSERT_HELD(lr);
580 
581 	bzero(lri, sizeof(*lri));
582 	/*
583 	 * Note here we return request count, not actual memory refcnt.
584 	 */
585 	lri->lri_refcnt = lr->lr_reqcnt;
586 	lri->lri_ifindex = lr->lr_ifp->if_index;
587 	lri->lri_probes = lr->lr_probes;
588 	lri->lri_expire = ifnet_llreach_up2calexp(lr, lr->lr_lastrcvd);
589 	lri->lri_proto = lr->lr_key.proto;
590 	bcopy(&lr->lr_key.addr, &lri->lri_addr, IF_LLREACH_MAXLEN);
591 	lri->lri_rssi = lr->lr_rssi;
592 	lri->lri_lqm = lr->lr_lqm;
593 	lri->lri_npm = lr->lr_npm;
594 }
595 
596 static int
597 sysctl_llreach_ifinfo SYSCTL_HANDLER_ARGS
598 {
599 #pragma unused(oidp)
600 	int             *name, retval = 0;
601 	unsigned int    namelen;
602 	uint32_t        ifindex;
603 	struct if_llreach *lr;
604 	struct if_llreach_info lri = {};
605 	struct ifnet    *ifp;
606 
607 	name = (int *)arg1;
608 	namelen = (unsigned int)arg2;
609 
610 	if (req->newptr != USER_ADDR_NULL) {
611 		return EPERM;
612 	}
613 
614 	if (namelen != 1) {
615 		return EINVAL;
616 	}
617 
618 	ifindex = name[0];
619 	ifnet_head_lock_shared();
620 	if (ifindex <= 0 || ifindex > (u_int)if_index) {
621 		printf("%s: ifindex %u out of range\n", __func__, ifindex);
622 		ifnet_head_done();
623 		return ENOENT;
624 	}
625 
626 	ifp = ifindex2ifnet[ifindex];
627 	ifnet_head_done();
628 	if (ifp == NULL) {
629 		printf("%s: no ifp for ifindex %u\n", __func__, ifindex);
630 		return ENOENT;
631 	}
632 
633 	lck_rw_lock_shared(&ifp->if_llreach_lock);
634 	RB_FOREACH(lr, ll_reach_tree, &ifp->if_ll_srcs) {
635 		/* Export to if_llreach_info structure */
636 		IFLR_LOCK(lr);
637 		ifnet_lr2lri(lr, &lri);
638 		IFLR_UNLOCK(lr);
639 
640 		if ((retval = SYSCTL_OUT(req, &lri, sizeof(lri))) != 0) {
641 			break;
642 		}
643 	}
644 	lck_rw_done(&ifp->if_llreach_lock);
645 
646 	return retval;
647 }
648