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
137static KALLOC_TYPE_DEFINE(iflr_zone, struct if_llreach, NET_KT_DEFAULT);
138
139static struct if_llreach *iflr_alloc(zalloc_flags_t);
140static void iflr_free(struct if_llreach *);
141static __inline int iflr_cmp(const struct if_llreach *,
142 const struct if_llreach *);
143static __inline int iflr_reachable(struct if_llreach *, int, u_int64_t);
144static int sysctl_llreach_ifinfo SYSCTL_HANDLER_ARGS;
145
146/* The following is protected by if_llreach_lock */
147RB_GENERATE_PREV(ll_reach_tree, if_llreach, lr_link, iflr_cmp);
148
149SYSCTL_DECL(_net_link_generic_system);
150
151SYSCTL_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
160void
161ifnet_llreach_ifattach(struct ifnet *ifp, boolean_t reuse)
162{
163 lck_rw_lock_exclusive(lck: &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(lck: &ifp->if_llreach_lock);
169}
170
171void
172ifnet_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 */
188static __inline int
189iflr_cmp(const struct if_llreach *a, const struct if_llreach *b)
190{
191 return memcmp(s1: &a->lr_key, s2: &b->lr_key, n: sizeof(a->lr_key));
192}
193
194static __inline int
195iflr_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
222int
223ifnet_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, cmp_delta: 0, tval: 0);
229}
230
231int
232ifnet_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, cmp_delta: 1, tval);
238}
239
240void
241ifnet_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(src: addr, dst: &find.lr_key.addr, IF_LLREACH_MAXLEN);
250
251 lck_rw_lock_shared(lck: &ifp->if_llreach_lock);
252 lr = RB_FIND(ll_reach_tree, &ifp->if_ll_srcs, &find);
253 if (lr == NULL) {
254 lck_rw_done(lck: &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(lck: &ifp->if_llreach_lock);
262}
263
264struct if_llreach *
265ifnet_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(src: addr, dst: &find.lr_key.addr, IF_LLREACH_MAXLEN);
279
280 lck_rw_lock_shared(lck: &ifp->if_llreach_lock);
281 lr = RB_FIND(ll_reach_tree, &ifp->if_ll_srcs, &find);
282 if (lr != NULL) {
283found:
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(lck: &ifp->if_llreach_lock);
292 return lr;
293 }
294
295 if (!lck_rw_lock_shared_to_exclusive(lck: &ifp->if_llreach_lock)) {
296 lck_rw_lock_exclusive(lck: &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(src: addr, dst: &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(lck: &ifp->if_llreach_lock);
330
331 return lr;
332}
333
334void
335ifnet_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(lck: &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(lck: &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(lck: &ifp->if_llreach_lock);
364
365 IFLR_REMREF(lr); /* for RB tree */
366 IFLR_REMREF(lr); /* for caller */
367}
368
369u_int64_t
370ifnet_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
395u_int64_t
396ifnet_llreach_up2upexp(struct if_llreach *lr, u_int64_t uptime)
397{
398 return lr->lr_reachable + uptime;
399}
400
401int
402ifnet_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(s: iflri, n: sizeof(*iflri));
414
415 if ((rnh = rt_tables[af]) == NULL) {
416 return error;
417 }
418
419 bzero(s: &dst_ss, n: sizeof(dst_ss));
420 bzero(s: &mask_ss, n: 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
452static struct if_llreach *
453iflr_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(lck: &lr->lr_lock, grp: &ifnet_lock_group, attr: &ifnet_lock_attr);
459 lr->lr_debug |= IFD_ALLOC;
460 }
461 return lr;
462}
463
464static void
465iflr_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(lck: &lr->lr_lock, grp: &ifnet_lock_group);
485 zfree(iflr_zone, lr);
486}
487
488void
489iflr_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
506void
507iflr_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
524void
525ifnet_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(s: ri, n: 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
541void
542ifnet_lr2iflri(struct if_llreach *lr, struct ifnet_llreach_info *iflri)
543{
544 IFLR_LOCK_ASSERT_HELD(lr);
545
546 bzero(s: iflri, n: 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, uptime: 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(src: &lr->lr_key.addr, dst: &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
576void
577ifnet_lr2lri(struct if_llreach *lr, struct if_llreach_info *lri)
578{
579 IFLR_LOCK_ASSERT_HELD(lr);
580
581 bzero(s: lri, n: 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, uptime: lr->lr_lastrcvd);
589 lri->lri_proto = lr->lr_key.proto;
590 bcopy(src: &lr->lr_key.addr, dst: &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
596static int
597sysctl_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(lck: &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: &lri);
638 IFLR_UNLOCK(lr);
639
640 if ((retval = SYSCTL_OUT(req, &lri, sizeof(lri))) != 0) {
641 break;
642 }
643 }
644 lck_rw_done(lck: &ifp->if_llreach_lock);
645
646 return retval;
647}
648