Changeset View
Changeset View
Standalone View
Standalone View
sys/netinet6/in6_lle.c
- This file was added.
Property | Old Value | New Value |
---|---|---|
svn:eol-style | null | native \ No newline at end of property |
svn:keywords | null | FreeBSD=%H \ No newline at end of property |
svn:mime-type | null | text/plain \ No newline at end of property |
/* | |||||
* Copyright (c) 2004 Luigi Rizzo, Alessandro Cerri. All rights reserved. | |||||
* Copyright (c) 2004-2008 Qing Li. All rights reserved. | |||||
* Copyright (c) 2008 Kip Macy. All rights reserved. | |||||
* Copyright (c) 2015-2016 Alexander V. Chernikov. All rights reserved. | |||||
* | |||||
* Redistribution and use in source and binary forms, with or without | |||||
* modification, are permitted provided that the following conditions | |||||
* are met: | |||||
* 1. Redistributions of source code must retain the above copyright | |||||
* notice, this list of conditions and the following disclaimer. | |||||
* 2. Redistributions in binary form must reproduce the above copyright | |||||
* notice, this list of conditions and the following disclaimer in the | |||||
* documentation and/or other materials provided with the distribution. | |||||
* | |||||
* THIS SOFTWARE IS PROVIDED BY AUTHOR AND CONTRIBUTORS ``AS IS'' AND | |||||
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE | |||||
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE | |||||
* ARE DISCLAIMED. IN NO EVENT SHALL AUTHOR OR CONTRIBUTORS BE LIABLE | |||||
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL | |||||
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS | |||||
* OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) | |||||
* HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT | |||||
* LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY | |||||
* OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF | |||||
* SUCH DAMAGE. | |||||
*/ | |||||
#include <sys/cdefs.h> | |||||
__FBSDID("$FreeBSD$"); | |||||
#include "opt_inet.h" | |||||
#include "opt_inet6.h" | |||||
#include <sys/param.h> | |||||
#include <sys/jail.h> | |||||
#include <sys/systm.h> | |||||
#include <sys/callout.h> | |||||
#include <sys/malloc.h> | |||||
#include <sys/mbuf.h> | |||||
#include <sys/socket.h> | |||||
#include <sys/time.h> | |||||
#include <sys/kernel.h> | |||||
#include <sys/proc.h> | |||||
#include <sys/errno.h> | |||||
#include <sys/syslog.h> | |||||
#include <sys/lock.h> | |||||
#include <sys/rwlock.h> | |||||
#include <sys/queue.h> | |||||
#include <sys/sdt.h> | |||||
#include <net/if.h> | |||||
#include <net/if_var.h> | |||||
#include <net/if_dl.h> | |||||
#include <net/if_types.h> | |||||
#include <net/route.h> | |||||
#include <net/vnet.h> | |||||
#include <netinet/in.h> | |||||
#include <net/if_llatbl.h> | |||||
#include <net/if_llatbl_var.h> | |||||
#include <netinet/if_ether.h> | |||||
#include <netinet6/in6_var.h> | |||||
#include <netinet6/in6_fib.h> | |||||
#include <netinet/ip6.h> | |||||
#include <netinet6/ip6_var.h> | |||||
#include <netinet6/scope6_var.h> | |||||
#include <netinet6/nd6.h> | |||||
#include <netinet/icmp6.h> | |||||
#include <sys/limits.h> | |||||
#define SIN6(s) ((const struct sockaddr_in6 *)(s)) | |||||
static eventhandler_tag lle_event_eh, iflladdr_event_eh; | |||||
static void nd6_free(struct llentry *, int); | |||||
static void nd6_free_redirect(const struct llentry *); | |||||
static void nd6_llinfo_timer(void *); | |||||
static void nd6_llinfo_settimer_locked(struct llentry *, long); | |||||
static void clear_llinfo_pqueue(struct llentry *); | |||||
static int nd6_resolve_slow(struct ifnet *, int, struct mbuf *, | |||||
const struct sockaddr_in6 *, u_char *, uint32_t *); | |||||
/* | |||||
* ND6 timer routine to handle ND6 entries | |||||
*/ | |||||
static void | |||||
nd6_llinfo_settimer_locked(struct llentry *ln, long tick) | |||||
{ | |||||
int canceled; | |||||
LLE_WLOCK_ASSERT(ln); | |||||
if (tick < 0) { | |||||
ln->la_expire = 0; | |||||
ln->ln_ntick = 0; | |||||
canceled = callout_stop(&ln->lle_timer); | |||||
} else { | |||||
ln->la_expire = time_uptime + tick / hz; | |||||
LLE_ADDREF(ln); | |||||
if (tick > INT_MAX) { | |||||
ln->ln_ntick = tick - INT_MAX; | |||||
canceled = callout_reset(&ln->lle_timer, INT_MAX, | |||||
nd6_llinfo_timer, ln); | |||||
} else { | |||||
ln->ln_ntick = 0; | |||||
canceled = callout_reset(&ln->lle_timer, tick, | |||||
nd6_llinfo_timer, ln); | |||||
} | |||||
} | |||||
if (canceled > 0) | |||||
LLE_REMREF(ln); | |||||
} | |||||
/* | |||||
* Checks if we need to switch from STALE state. | |||||
* | |||||
* RFC 4861 requires switching from STALE to DELAY state | |||||
* on first packet matching entry, waiting V_nd6_delay and | |||||
* transition to PROBE state (if upper layer confirmation was | |||||
* not received). | |||||
* | |||||
* This code performs a bit differently: | |||||
* On packet hit we don't change state (but desired state | |||||
* can be guessed by control plane). However, after V_nd6_delay | |||||
* seconds code will transition to PROBE state (so DELAY state | |||||
* is kinda skipped in most situations). | |||||
* | |||||
* Typically, V_nd6_gctimer is bigger than V_nd6_delay, so | |||||
* we perform the following upon entering STALE state: | |||||
* | |||||
* 1) Arm timer to run each V_nd6_delay seconds to make sure that | |||||
* if packet was transmitted at the start of given interval, we | |||||
* would be able to switch to PROBE state in V_nd6_delay seconds | |||||
* as user expects. | |||||
* | |||||
* 2) Reschedule timer until original V_nd6_gctimer expires keeping | |||||
* lle in STALE state (remaining timer value stored in lle_remtime). | |||||
* | |||||
* 3) Reschedule timer if packet was transmitted less that V_nd6_delay | |||||
* seconds ago. | |||||
* | |||||
* Returns non-zero value if the entry is still STALE (storing | |||||
* the next timer interval in @pdelay). | |||||
* | |||||
* Returns zero value if original timer expired or we need to switch to | |||||
* PROBE (store that in @do_switch variable). | |||||
*/ | |||||
static int | |||||
nd6_is_stale(struct llentry *lle, long *pdelay, int *do_switch) | |||||
{ | |||||
int nd_delay, nd_gctimer, r_skip_req; | |||||
time_t lle_hittime; | |||||
long delay; | |||||
*do_switch = 0; | |||||
nd_gctimer = V_nd6_gctimer; | |||||
nd_delay = V_nd6_delay; | |||||
LLE_REQ_LOCK(lle); | |||||
r_skip_req = lle->r_skip_req; | |||||
lle_hittime = lle->lle_hittime; | |||||
LLE_REQ_UNLOCK(lle); | |||||
if (r_skip_req > 0) { | |||||
/* | |||||
* Nonzero r_skip_req value was set upon entering | |||||
* STALE state. Since value was not changed, no | |||||
* packets were passed using this lle. Ask for | |||||
* timer reschedule and keep STALE state. | |||||
*/ | |||||
delay = (long)(MIN(nd_gctimer, nd_delay)); | |||||
delay *= hz; | |||||
if (lle->lle_remtime > delay) | |||||
lle->lle_remtime -= delay; | |||||
else { | |||||
delay = lle->lle_remtime; | |||||
lle->lle_remtime = 0; | |||||
} | |||||
if (delay == 0) { | |||||
/* | |||||
* The original ng6_gctime timeout ended, | |||||
* no more rescheduling. | |||||
*/ | |||||
return (0); | |||||
} | |||||
*pdelay = delay; | |||||
return (1); | |||||
} | |||||
/* | |||||
* Packet received. Verify timestamp | |||||
*/ | |||||
delay = (long)(time_uptime - lle_hittime); | |||||
if (delay < nd_delay) { | |||||
/* | |||||
* V_nd6_delay still not passed since the first | |||||
* hit in STALE state. | |||||
* Reshedule timer and return. | |||||
*/ | |||||
*pdelay = (long)(nd_delay - delay) * hz; | |||||
return (1); | |||||
} | |||||
/* Request switching to probe */ | |||||
*do_switch = 1; | |||||
return (0); | |||||
} | |||||
/* | |||||
* Gets source address of the first packet in hold queue | |||||
* and stores it in @src. | |||||
* Returns pointer to @src (if hold queue is not empty) or NULL. | |||||
* | |||||
* Set noinline to be dtrace-friendly | |||||
*/ | |||||
static __noinline struct in6_addr * | |||||
nd6_llinfo_get_holdsrc(struct llentry *ln, struct in6_addr *src) | |||||
{ | |||||
struct ip6_hdr hdr; | |||||
struct mbuf *m; | |||||
if (ln->la_hold == NULL) | |||||
return (NULL); | |||||
/* | |||||
* assume every packet in la_hold has the same IP header | |||||
*/ | |||||
m = ln->la_hold; | |||||
if (sizeof(hdr) > m->m_len) | |||||
return (NULL); | |||||
m_copydata(m, 0, sizeof(hdr), (caddr_t)&hdr); | |||||
*src = hdr.ip6_src; | |||||
return (src); | |||||
} | |||||
/* | |||||
* Switch @lle state to new state optionally arming timers. | |||||
* | |||||
* Set noinline to be dtrace-friendly | |||||
*/ | |||||
__noinline void | |||||
nd6_llinfo_setstate(struct llentry *lle, int newstate) | |||||
{ | |||||
struct ifnet *ifp; | |||||
int nd_gctimer, nd_delay; | |||||
long delay, remtime; | |||||
delay = 0; | |||||
remtime = 0; | |||||
switch (newstate) { | |||||
case ND6_LLINFO_INCOMPLETE: | |||||
ifp = lle->lle_tbl->llt_ifp; | |||||
delay = (long)ND_IFINFO(ifp)->retrans * hz / 1000; | |||||
break; | |||||
case ND6_LLINFO_REACHABLE: | |||||
if (!ND6_LLINFO_PERMANENT(lle)) { | |||||
ifp = lle->lle_tbl->llt_ifp; | |||||
delay = (long)ND_IFINFO(ifp)->reachable * hz; | |||||
} | |||||
break; | |||||
case ND6_LLINFO_STALE: | |||||
/* | |||||
* Notify fast path that we want to know if any packet | |||||
* is transmitted by setting r_skip_req. | |||||
*/ | |||||
LLE_REQ_LOCK(lle); | |||||
lle->r_skip_req = 1; | |||||
LLE_REQ_UNLOCK(lle); | |||||
nd_delay = V_nd6_delay; | |||||
nd_gctimer = V_nd6_gctimer; | |||||
delay = (long)(MIN(nd_gctimer, nd_delay)) * hz; | |||||
remtime = (long)nd_gctimer * hz - delay; | |||||
break; | |||||
case ND6_LLINFO_DELAY: | |||||
lle->la_asked = 0; | |||||
delay = (long)V_nd6_delay * hz; | |||||
break; | |||||
} | |||||
if (delay > 0) | |||||
nd6_llinfo_settimer_locked(lle, delay); | |||||
lle->lle_remtime = remtime; | |||||
lle->ln_state = newstate; | |||||
} | |||||
/* | |||||
* Timer-dependent part of nd state machine. | |||||
* | |||||
* Set noinline to be dtrace-friendly | |||||
*/ | |||||
static __noinline void | |||||
nd6_llinfo_timer(void *arg) | |||||
{ | |||||
struct llentry *ln; | |||||
struct in6_addr *dst, *pdst, *psrc, src; | |||||
struct ifnet *ifp; | |||||
struct nd_ifinfo *ndi = NULL; | |||||
int do_switch, send_ns; | |||||
long delay; | |||||
KASSERT(arg != NULL, ("%s: arg NULL", __func__)); | |||||
ln = (struct llentry *)arg; | |||||
LLE_WLOCK(ln); | |||||
if (callout_pending(&ln->lle_timer)) { | |||||
/* | |||||
* Here we are a bit odd here in the treatment of | |||||
* active/pending. If the pending bit is set, it got | |||||
* rescheduled before I ran. The active | |||||
* bit we ignore, since if it was stopped | |||||
* in ll_tablefree() and was currently running | |||||
* it would have return 0 so the code would | |||||
* not have deleted it since the callout could | |||||
* not be stopped so we want to go through | |||||
* with the delete here now. If the callout | |||||
* was restarted, the pending bit will be back on and | |||||
* we just want to bail since the callout_reset would | |||||
* return 1 and our reference would have been removed | |||||
* by nd6_llinfo_settimer_locked above since canceled | |||||
* would have been 1. | |||||
*/ | |||||
LLE_WUNLOCK(ln); | |||||
return; | |||||
} | |||||
ifp = ln->lle_tbl->llt_ifp; | |||||
CURVNET_SET(ifp->if_vnet); | |||||
ndi = ND_IFINFO(ifp); | |||||
send_ns = 0; | |||||
dst = &ln->r_l3addr.addr6; | |||||
pdst = dst; | |||||
if (ln->ln_ntick > 0) { | |||||
if (ln->ln_ntick > INT_MAX) { | |||||
ln->ln_ntick -= INT_MAX; | |||||
nd6_llinfo_settimer_locked(ln, INT_MAX); | |||||
} else { | |||||
ln->ln_ntick = 0; | |||||
nd6_llinfo_settimer_locked(ln, ln->ln_ntick); | |||||
} | |||||
goto done; | |||||
} | |||||
if (ln->la_flags & LLE_STATIC) { | |||||
goto done; | |||||
} | |||||
if (ln->la_flags & LLE_DELETED) { | |||||
nd6_free(ln, 0); | |||||
ln = NULL; | |||||
goto done; | |||||
} | |||||
switch (ln->ln_state) { | |||||
case ND6_LLINFO_INCOMPLETE: | |||||
if (ln->la_asked < V_nd6_mmaxtries) { | |||||
ln->la_asked++; | |||||
send_ns = 1; | |||||
/* Send NS to multicast address */ | |||||
pdst = NULL; | |||||
} else { | |||||
struct mbuf *m = ln->la_hold; | |||||
if (m) { | |||||
struct mbuf *m0; | |||||
/* | |||||
* assuming every packet in la_hold has the | |||||
* same IP header. Send error after unlock. | |||||
*/ | |||||
m0 = m->m_nextpkt; | |||||
m->m_nextpkt = NULL; | |||||
ln->la_hold = m0; | |||||
clear_llinfo_pqueue(ln); | |||||
} | |||||
EVENTHANDLER_INVOKE(lle_event, ln, LLENTRY_TIMEDOUT); | |||||
nd6_free(ln, 0); | |||||
ln = NULL; | |||||
if (m != NULL) | |||||
icmp6_error2(m, ICMP6_DST_UNREACH, | |||||
ICMP6_DST_UNREACH_ADDR, 0, ifp); | |||||
} | |||||
break; | |||||
case ND6_LLINFO_REACHABLE: | |||||
if (!ND6_LLINFO_PERMANENT(ln)) | |||||
nd6_llinfo_setstate(ln, ND6_LLINFO_STALE); | |||||
break; | |||||
case ND6_LLINFO_STALE: | |||||
if (nd6_is_stale(ln, &delay, &do_switch) != 0) { | |||||
/* | |||||
* No packet has used this entry and GC timeout | |||||
* has not been passed. Reshedule timer and | |||||
* return. | |||||
*/ | |||||
nd6_llinfo_settimer_locked(ln, delay); | |||||
break; | |||||
} | |||||
if (do_switch == 0) { | |||||
/* | |||||
* GC timer has ended and entry hasn't been used. | |||||
* Run Garbage collector (RFC 4861, 5.3) | |||||
*/ | |||||
if (!ND6_LLINFO_PERMANENT(ln)) { | |||||
EVENTHANDLER_INVOKE(lle_event, ln, | |||||
LLENTRY_EXPIRED); | |||||
nd6_free(ln, 1); | |||||
ln = NULL; | |||||
} | |||||
break; | |||||
} | |||||
/* Entry has been used AND delay timer has ended. */ | |||||
/* FALLTHROUGH */ | |||||
case ND6_LLINFO_DELAY: | |||||
if (ndi && (ndi->flags & ND6_IFF_PERFORMNUD) != 0) { | |||||
/* We need NUD */ | |||||
ln->la_asked = 1; | |||||
nd6_llinfo_setstate(ln, ND6_LLINFO_PROBE); | |||||
send_ns = 1; | |||||
} else | |||||
nd6_llinfo_setstate(ln, ND6_LLINFO_STALE); /* XXX */ | |||||
break; | |||||
case ND6_LLINFO_PROBE: | |||||
if (ln->la_asked < V_nd6_umaxtries) { | |||||
ln->la_asked++; | |||||
send_ns = 1; | |||||
} else { | |||||
EVENTHANDLER_INVOKE(lle_event, ln, LLENTRY_EXPIRED); | |||||
nd6_free(ln, 0); | |||||
ln = NULL; | |||||
} | |||||
break; | |||||
default: | |||||
panic("%s: paths in a dark night can be confusing: %d", | |||||
__func__, ln->ln_state); | |||||
} | |||||
done: | |||||
if (send_ns != 0) { | |||||
nd6_llinfo_settimer_locked(ln, (long)ndi->retrans * hz / 1000); | |||||
psrc = nd6_llinfo_get_holdsrc(ln, &src); | |||||
LLE_FREE_LOCKED(ln); | |||||
ln = NULL; | |||||
nd6_ns_output(ifp, psrc, pdst, dst, NULL); | |||||
} | |||||
if (ln != NULL) | |||||
LLE_FREE_LOCKED(ln); | |||||
CURVNET_RESTORE(); | |||||
} | |||||
static void | |||||
clear_llinfo_pqueue(struct llentry *ln) | |||||
{ | |||||
struct mbuf *m_hold, *m_hold_next; | |||||
for (m_hold = ln->la_hold; m_hold; m_hold = m_hold_next) { | |||||
m_hold_next = m_hold->m_nextpkt; | |||||
m_freem(m_hold); | |||||
} | |||||
ln->la_hold = NULL; | |||||
return; | |||||
} | |||||
/* | |||||
* the caller acquires and releases the lock on the lltbls | |||||
* Returns the llentry locked | |||||
*/ | |||||
struct llentry * | |||||
nd6_lookup(const struct in6_addr *addr6, int flags, struct ifnet *ifp) | |||||
{ | |||||
struct sockaddr_in6 sin6; | |||||
struct llentry *ln; | |||||
bzero(&sin6, sizeof(sin6)); | |||||
sin6.sin6_len = sizeof(struct sockaddr_in6); | |||||
sin6.sin6_family = AF_INET6; | |||||
sin6.sin6_addr = *addr6; | |||||
IF_AFDATA_LOCK_ASSERT(ifp); | |||||
ln = lla_lookup(LLTABLE6(ifp), flags, (struct sockaddr *)&sin6); | |||||
return (ln); | |||||
} | |||||
struct llentry * | |||||
nd6_alloc(const struct in6_addr *addr6, int flags, struct ifnet *ifp) | |||||
{ | |||||
struct sockaddr_in6 sin6; | |||||
struct llentry *ln; | |||||
bzero(&sin6, sizeof(sin6)); | |||||
sin6.sin6_len = sizeof(struct sockaddr_in6); | |||||
sin6.sin6_family = AF_INET6; | |||||
sin6.sin6_addr = *addr6; | |||||
ln = lltable_alloc_entry(LLTABLE6(ifp), 0, (struct sockaddr *)&sin6); | |||||
if (ln != NULL) | |||||
ln->ln_state = ND6_LLINFO_NOSTATE; | |||||
return (ln); | |||||
} | |||||
/* | |||||
* Free an nd6 llinfo entry. | |||||
* Since the function would cause significant changes in the kernel, DO NOT | |||||
* make it global, unless you have a strong reason for the change, and are sure | |||||
* that the change is safe. | |||||
* | |||||
* Set noinline to be dtrace-friendly | |||||
*/ | |||||
static __noinline void | |||||
nd6_free(struct llentry *ln, int gc) | |||||
{ | |||||
struct nd_defrouter *dr; | |||||
struct ifnet *ifp; | |||||
LLE_WLOCK_ASSERT(ln); | |||||
/* | |||||
* we used to have pfctlinput(PRC_HOSTDEAD) here. | |||||
* even though it is not harmful, it was not really necessary. | |||||
*/ | |||||
/* cancel timer */ | |||||
nd6_llinfo_settimer_locked(ln, -1); | |||||
ifp = ln->lle_tbl->llt_ifp; | |||||
if (ND_IFINFO(ifp)->flags & ND6_IFF_ACCEPT_RTADV) { | |||||
dr = defrouter_lookup(&ln->r_l3addr.addr6, ifp); | |||||
if (dr != NULL && dr->expire && | |||||
ln->ln_state == ND6_LLINFO_STALE && gc) { | |||||
/* | |||||
* If the reason for the deletion is just garbage | |||||
* collection, and the neighbor is an active default | |||||
* router, do not delete it. Instead, reset the GC | |||||
* timer using the router's lifetime. | |||||
* Simply deleting the entry would affect default | |||||
* router selection, which is not necessarily a good | |||||
* thing, especially when we're using router preference | |||||
* values. | |||||
* XXX: the check for ln_state would be redundant, | |||||
* but we intentionally keep it just in case. | |||||
*/ | |||||
if (dr->expire > time_uptime) | |||||
nd6_llinfo_settimer_locked(ln, | |||||
(dr->expire - time_uptime) * hz); | |||||
else | |||||
nd6_llinfo_settimer_locked(ln, | |||||
(long)V_nd6_gctimer * hz); | |||||
LLE_REMREF(ln); | |||||
LLE_WUNLOCK(ln); | |||||
return; | |||||
} | |||||
if (dr) { | |||||
/* | |||||
* Unreachablity of a router might affect the default | |||||
* router selection and on-link detection of advertised | |||||
* prefixes. | |||||
*/ | |||||
/* | |||||
* Temporarily fake the state to choose a new default | |||||
* router and to perform on-link determination of | |||||
* prefixes correctly. | |||||
* Below the state will be set correctly, | |||||
* or the entry itself will be deleted. | |||||
*/ | |||||
ln->ln_state = ND6_LLINFO_INCOMPLETE; | |||||
} | |||||
if (ln->ln_router || dr) { | |||||
/* | |||||
* We need to unlock to avoid a LOR with rt6_flush() with the | |||||
* rnh and for the calls to pfxlist_onlink_check() and | |||||
* defrouter_select() in the block further down for calls | |||||
* into nd6_lookup(). We still hold a ref. | |||||
*/ | |||||
LLE_WUNLOCK(ln); | |||||
/* | |||||
* rt6_flush must be called whether or not the neighbor | |||||
* is in the Default Router List. | |||||
* See a corresponding comment in nd6_na_input(). | |||||
*/ | |||||
rt6_flush(&ln->r_l3addr.addr6, ifp); | |||||
} | |||||
if (dr) { | |||||
/* | |||||
* Since defrouter_select() does not affect the | |||||
* on-link determination and MIP6 needs the check | |||||
* before the default router selection, we perform | |||||
* the check now. | |||||
*/ | |||||
pfxlist_onlink_check(); | |||||
/* | |||||
* Refresh default router list. | |||||
*/ | |||||
defrouter_select(); | |||||
} | |||||
/* | |||||
* If this entry was added by an on-link redirect, remove the | |||||
* corresponding host route. | |||||
*/ | |||||
if (ln->la_flags & LLE_REDIRECT) | |||||
nd6_free_redirect(ln); | |||||
if (ln->ln_router || dr) | |||||
LLE_WLOCK(ln); | |||||
} | |||||
/* | |||||
* Save to unlock. We still hold an extra reference and will not | |||||
* free(9) in llentry_free() if someone else holds one as well. | |||||
*/ | |||||
LLE_WUNLOCK(ln); | |||||
IF_AFDATA_LOCK(ifp); | |||||
LLE_WLOCK(ln); | |||||
/* Guard against race with other llentry_free(). */ | |||||
if (ln->la_flags & LLE_LINKED) { | |||||
/* Remove callout reference */ | |||||
LLE_REMREF(ln); | |||||
lltable_unlink_entry(ln->lle_tbl, ln); | |||||
} | |||||
IF_AFDATA_UNLOCK(ifp); | |||||
llentry_free(ln); | |||||
} | |||||
static int | |||||
nd6_isdynrte(const struct rtentry *rt, void *xap) | |||||
{ | |||||
if (rt->rt_flags == (RTF_UP | RTF_HOST | RTF_DYNAMIC)) | |||||
return (1); | |||||
return (0); | |||||
} | |||||
/* | |||||
* Remove the rtentry for the given llentry, | |||||
* both of which were installed by a redirect. | |||||
*/ | |||||
static void | |||||
nd6_free_redirect(const struct llentry *ln) | |||||
{ | |||||
int fibnum; | |||||
struct sockaddr_in6 sin6; | |||||
struct rt_addrinfo info; | |||||
lltable_fill_sa_entry(ln, (struct sockaddr *)&sin6); | |||||
memset(&info, 0, sizeof(info)); | |||||
info.rti_info[RTAX_DST] = (struct sockaddr *)&sin6; | |||||
info.rti_filter = nd6_isdynrte; | |||||
for (fibnum = 0; fibnum < rt_numfibs; fibnum++) | |||||
rtrequest1_fib(RTM_DELETE, &info, NULL, fibnum); | |||||
} | |||||
/* | |||||
* Lookup link headerfor @sa_dst address. Stores found | |||||
* data in @desten buffer. Copy of lle ln_flags can be also | |||||
* saved in @pflags if @pflags is non-NULL. | |||||
* | |||||
* If destination LLE does not exists or lle state modification | |||||
* is required, call "slow" version. | |||||
* | |||||
* Return values: | |||||
* - 0 on success (address copied to buffer). | |||||
* - EWOULDBLOCK (no local error, but address is still unresolved) | |||||
* - other errors (alloc failure, etc) | |||||
*/ | |||||
int | |||||
nd6_resolve(struct ifnet *ifp, int is_gw, struct mbuf *m, | |||||
const struct sockaddr *sa_dst, u_char *desten, uint32_t *pflags) | |||||
{ | |||||
struct llentry *ln = NULL; | |||||
const struct sockaddr_in6 *dst6; | |||||
if (pflags != NULL) | |||||
*pflags = 0; | |||||
dst6 = (const struct sockaddr_in6 *)sa_dst; | |||||
/* discard the packet if IPv6 operation is disabled on the interface */ | |||||
if ((ND_IFINFO(ifp)->flags & ND6_IFF_IFDISABLED)) { | |||||
m_freem(m); | |||||
return (ENETDOWN); /* better error? */ | |||||
} | |||||
if (m != NULL && m->m_flags & M_MCAST) { | |||||
switch (ifp->if_type) { | |||||
case IFT_ETHER: | |||||
case IFT_FDDI: | |||||
case IFT_L2VLAN: | |||||
case IFT_IEEE80211: | |||||
case IFT_BRIDGE: | |||||
case IFT_ISO88025: | |||||
ETHER_MAP_IPV6_MULTICAST(&dst6->sin6_addr, | |||||
desten); | |||||
return (0); | |||||
default: | |||||
m_freem(m); | |||||
return (EAFNOSUPPORT); | |||||
} | |||||
} | |||||
IF_AFDATA_RLOCK(ifp); | |||||
ln = nd6_lookup(&dst6->sin6_addr, LLE_UNLOCKED, ifp); | |||||
if (ln != NULL && (ln->r_flags & RLLE_VALID) != 0) { | |||||
/* Entry found, let's copy lle info */ | |||||
bcopy(ln->r_linkdata, desten, ln->r_hdrlen); | |||||
if (pflags != NULL) | |||||
*pflags = LLE_VALID | (ln->r_flags & RLLE_IFADDR); | |||||
/* Check if we have feedback request from nd6 timer */ | |||||
if (ln->r_skip_req != 0) { | |||||
LLE_REQ_LOCK(ln); | |||||
ln->r_skip_req = 0; /* Notify that entry was used */ | |||||
ln->lle_hittime = time_uptime; | |||||
LLE_REQ_UNLOCK(ln); | |||||
} | |||||
IF_AFDATA_RUNLOCK(ifp); | |||||
return (0); | |||||
} | |||||
IF_AFDATA_RUNLOCK(ifp); | |||||
return (nd6_resolve_slow(ifp, 0, m, dst6, desten, pflags)); | |||||
} | |||||
/* | |||||
* Do L2 address resolution for @sa_dst address. Stores found | |||||
* address in @desten buffer. Copy of lle ln_flags can be also | |||||
* saved in @pflags if @pflags is non-NULL. | |||||
* | |||||
* Heavy version. | |||||
* Function assume that destination LLE does not exist, | |||||
* is invalid or stale, so LLE_EXCLUSIVE lock needs to be acquired. | |||||
* | |||||
* Set noinline to be dtrace-friendly | |||||
*/ | |||||
static __noinline int | |||||
nd6_resolve_slow(struct ifnet *ifp, int flags, struct mbuf *m, | |||||
const struct sockaddr_in6 *dst, u_char *desten, uint32_t *pflags) | |||||
{ | |||||
struct llentry *lle = NULL, *lle_tmp; | |||||
struct in6_addr *psrc, src; | |||||
int send_ns, ll_len; | |||||
char *lladdr; | |||||
/* | |||||
* Address resolution or Neighbor Unreachability Detection | |||||
* for the next hop. | |||||
* At this point, the destination of the packet must be a unicast | |||||
* or an anycast address(i.e. not a multicast). | |||||
*/ | |||||
if (lle == NULL) { | |||||
IF_AFDATA_RLOCK(ifp); | |||||
lle = nd6_lookup(&dst->sin6_addr, LLE_EXCLUSIVE, ifp); | |||||
IF_AFDATA_RUNLOCK(ifp); | |||||
if ((lle == NULL) && nd6_is_addr_neighbor(dst, ifp)) { | |||||
/* | |||||
* Since nd6_is_addr_neighbor() internally calls nd6_lookup(), | |||||
* the condition below is not very efficient. But we believe | |||||
* it is tolerable, because this should be a rare case. | |||||
*/ | |||||
lle = nd6_alloc(&dst->sin6_addr, 0, ifp); | |||||
if (lle == NULL) { | |||||
char ip6buf[INET6_ADDRSTRLEN]; | |||||
log(LOG_DEBUG, | |||||
"nd6_output: can't allocate llinfo for %s " | |||||
"(ln=%p)\n", | |||||
ip6_sprintf(ip6buf, &dst->sin6_addr), lle); | |||||
m_freem(m); | |||||
return (ENOBUFS); | |||||
} | |||||
IF_AFDATA_WLOCK(ifp); | |||||
LLE_WLOCK(lle); | |||||
/* Prefer any existing entry over newly-created one */ | |||||
lle_tmp = nd6_lookup(&dst->sin6_addr, LLE_EXCLUSIVE, ifp); | |||||
if (lle_tmp == NULL) | |||||
lltable_link_entry(LLTABLE6(ifp), lle); | |||||
IF_AFDATA_WUNLOCK(ifp); | |||||
if (lle_tmp != NULL) { | |||||
lltable_free_entry(LLTABLE6(ifp), lle); | |||||
lle = lle_tmp; | |||||
lle_tmp = NULL; | |||||
} | |||||
} | |||||
} | |||||
if (lle == NULL) { | |||||
if (!(ND_IFINFO(ifp)->flags & ND6_IFF_PERFORMNUD)) { | |||||
m_freem(m); | |||||
return (ENOBUFS); | |||||
} | |||||
if (m != NULL) | |||||
m_freem(m); | |||||
return (ENOBUFS); | |||||
} | |||||
LLE_WLOCK_ASSERT(lle); | |||||
/* | |||||
* The first time we send a packet to a neighbor whose entry is | |||||
* STALE, we have to change the state to DELAY and a sets a timer to | |||||
* expire in DELAY_FIRST_PROBE_TIME seconds to ensure do | |||||
* neighbor unreachability detection on expiration. | |||||
* (RFC 2461 7.3.3) | |||||
*/ | |||||
if (lle->ln_state == ND6_LLINFO_STALE) | |||||
nd6_llinfo_setstate(lle, ND6_LLINFO_DELAY); | |||||
/* | |||||
* If the neighbor cache entry has a state other than INCOMPLETE | |||||
* (i.e. its link-layer address is already resolved), just | |||||
* send the packet. | |||||
*/ | |||||
if (lle->ln_state > ND6_LLINFO_INCOMPLETE) { | |||||
if (flags & LLE_ADDRONLY) { | |||||
lladdr = lle->ll_addr; | |||||
ll_len = ifp->if_addrlen; | |||||
} else { | |||||
lladdr = lle->r_linkdata; | |||||
ll_len = lle->r_hdrlen; | |||||
} | |||||
bcopy(lladdr, desten, ll_len); | |||||
if (pflags != NULL) | |||||
*pflags = lle->la_flags; | |||||
LLE_WUNLOCK(lle); | |||||
return (0); | |||||
} | |||||
/* | |||||
* There is a neighbor cache entry, but no ethernet address | |||||
* response yet. Append this latest packet to the end of the | |||||
* packet queue in the mbuf, unless the number of the packet | |||||
* does not exceed nd6_maxqueuelen. When it exceeds nd6_maxqueuelen, | |||||
* the oldest packet in the queue will be removed. | |||||
*/ | |||||
if (lle->la_hold != NULL) { | |||||
struct mbuf *m_hold; | |||||
int i; | |||||
i = 0; | |||||
for (m_hold = lle->la_hold; m_hold; m_hold = m_hold->m_nextpkt){ | |||||
i++; | |||||
if (m_hold->m_nextpkt == NULL) { | |||||
m_hold->m_nextpkt = m; | |||||
break; | |||||
} | |||||
} | |||||
while (i >= V_nd6_maxqueuelen) { | |||||
m_hold = lle->la_hold; | |||||
lle->la_hold = lle->la_hold->m_nextpkt; | |||||
m_freem(m_hold); | |||||
i--; | |||||
} | |||||
} else { | |||||
lle->la_hold = m; | |||||
} | |||||
/* | |||||
* If there has been no NS for the neighbor after entering the | |||||
* INCOMPLETE state, send the first solicitation. | |||||
* Note that for newly-created lle la_asked will be 0, | |||||
* so we will transition from ND6_LLINFO_NOSTATE to | |||||
* ND6_LLINFO_INCOMPLETE state here. | |||||
*/ | |||||
psrc = NULL; | |||||
send_ns = 0; | |||||
if (lle->la_asked == 0) { | |||||
lle->la_asked++; | |||||
send_ns = 1; | |||||
psrc = nd6_llinfo_get_holdsrc(lle, &src); | |||||
nd6_llinfo_setstate(lle, ND6_LLINFO_INCOMPLETE); | |||||
} | |||||
LLE_WUNLOCK(lle); | |||||
if (send_ns != 0) | |||||
nd6_ns_output(ifp, psrc, NULL, &dst->sin6_addr, NULL); | |||||
return (EWOULDBLOCK); | |||||
} | |||||
/* | |||||
* Do L2 address resolution for @sa_dst address. Stores found | |||||
* address in @desten buffer. Copy of lle ln_flags can be also | |||||
* saved in @pflags if @pflags is non-NULL. | |||||
* | |||||
* Return values: | |||||
* - 0 on success (address copied to buffer). | |||||
* - EWOULDBLOCK (no local error, but address is still unresolved) | |||||
* - other errors (alloc failure, etc) | |||||
*/ | |||||
int | |||||
nd6_resolve_addr(struct ifnet *ifp, int flags, const struct sockaddr *dst, | |||||
char *desten, uint32_t *pflags) | |||||
{ | |||||
int error; | |||||
flags |= LLE_ADDRONLY; | |||||
error = nd6_resolve_slow(ifp, flags, NULL, | |||||
(const struct sockaddr_in6 *)dst, desten, pflags); | |||||
return (error); | |||||
} | |||||
/* | |||||
* Add pernament ND6 link-layer record for given | |||||
* interface address. | |||||
* | |||||
* Very similar to IPv4 arp_ifinit(), but: | |||||
* 1) IPv6 DAD is performed in different place | |||||
* 2) It is called by IPv6 protocol stack in contrast to | |||||
* arp_ifinit() which is typically called in SIOCSIFADDR | |||||
* driver ioctl handler. | |||||
* | |||||
*/ | |||||
int | |||||
nd6_add_ifa_lle(struct in6_ifaddr *ia) | |||||
{ | |||||
struct ifnet *ifp; | |||||
struct llentry *ln, *ln_tmp; | |||||
struct sockaddr *dst; | |||||
ifp = ia->ia_ifa.ifa_ifp; | |||||
if (nd6_need_cache(ifp) == 0) | |||||
return (0); | |||||
ia->ia_ifa.ifa_rtrequest = nd6_rtrequest; | |||||
dst = (struct sockaddr *)&ia->ia_addr; | |||||
ln = lltable_alloc_entry(LLTABLE6(ifp), LLE_IFADDR, dst); | |||||
if (ln == NULL) | |||||
return (ENOBUFS); | |||||
IF_AFDATA_WLOCK(ifp); | |||||
LLE_WLOCK(ln); | |||||
/* Unlink any entry if exists */ | |||||
ln_tmp = lla_lookup(LLTABLE6(ifp), LLE_EXCLUSIVE, dst); | |||||
if (ln_tmp != NULL) | |||||
lltable_unlink_entry(LLTABLE6(ifp), ln_tmp); | |||||
lltable_link_entry(LLTABLE6(ifp), ln); | |||||
IF_AFDATA_WUNLOCK(ifp); | |||||
if (ln_tmp != NULL) | |||||
EVENTHANDLER_INVOKE(lle_event, ln_tmp, LLENTRY_EXPIRED); | |||||
EVENTHANDLER_INVOKE(lle_event, ln, LLENTRY_RESOLVED); | |||||
LLE_WUNLOCK(ln); | |||||
if (ln_tmp != NULL) | |||||
llentry_free(ln_tmp); | |||||
return (0); | |||||
} | |||||
/* | |||||
* Removes either all lle entries for given @ia, or lle | |||||
* corresponding to @ia address. | |||||
*/ | |||||
void | |||||
nd6_rem_ifa_lle(struct in6_ifaddr *ia, int all) | |||||
{ | |||||
struct sockaddr_in6 mask, addr; | |||||
struct sockaddr *saddr, *smask; | |||||
struct ifnet *ifp; | |||||
ifp = ia->ia_ifa.ifa_ifp; | |||||
memcpy(&addr, &ia->ia_addr, sizeof(ia->ia_addr)); | |||||
memcpy(&mask, &ia->ia_prefixmask, sizeof(ia->ia_prefixmask)); | |||||
saddr = (struct sockaddr *)&addr; | |||||
smask = (struct sockaddr *)&mask; | |||||
if (all != 0) | |||||
lltable_prefix_free(AF_INET6, saddr, smask, LLE_STATIC); | |||||
else | |||||
lltable_delete_addr(LLTABLE6(ifp), LLE_IFADDR, saddr); | |||||
} | |||||
static void | |||||
nd6_lle_event(void *arg __unused, struct llentry *lle, int evt) | |||||
{ | |||||
struct rt_addrinfo rtinfo; | |||||
struct sockaddr_in6 dst; | |||||
struct sockaddr_dl gw; | |||||
struct ifnet *ifp; | |||||
int type; | |||||
LLE_WLOCK_ASSERT(lle); | |||||
if (lltable_get_af(lle->lle_tbl) != AF_INET6) | |||||
return; | |||||
switch (evt) { | |||||
case LLENTRY_RESOLVED: | |||||
type = RTM_ADD; | |||||
KASSERT(lle->la_flags & LLE_VALID, | |||||
("%s: %p resolved but not valid?", __func__, lle)); | |||||
break; | |||||
case LLENTRY_EXPIRED: | |||||
type = RTM_DELETE; | |||||
break; | |||||
default: | |||||
return; | |||||
} | |||||
ifp = lltable_get_ifp(lle->lle_tbl); | |||||
bzero(&dst, sizeof(dst)); | |||||
bzero(&gw, sizeof(gw)); | |||||
bzero(&rtinfo, sizeof(rtinfo)); | |||||
lltable_fill_sa_entry(lle, (struct sockaddr *)&dst); | |||||
dst.sin6_scope_id = in6_getscopezone(ifp, | |||||
in6_addrscope(&dst.sin6_addr)); | |||||
gw.sdl_len = sizeof(struct sockaddr_dl); | |||||
gw.sdl_family = AF_LINK; | |||||
gw.sdl_alen = ifp->if_addrlen; | |||||
gw.sdl_index = ifp->if_index; | |||||
gw.sdl_type = ifp->if_type; | |||||
if (evt == LLENTRY_RESOLVED) | |||||
bcopy(lle->ll_addr, gw.sdl_data, ifp->if_addrlen); | |||||
rtinfo.rti_info[RTAX_DST] = (struct sockaddr *)&dst; | |||||
rtinfo.rti_info[RTAX_GATEWAY] = (struct sockaddr *)&gw; | |||||
rtinfo.rti_addrs = RTA_DST | RTA_GATEWAY; | |||||
rt_missmsg_fib(type, &rtinfo, RTF_HOST | RTF_LLDATA | ( | |||||
type == RTM_ADD ? RTF_UP: 0), 0, RT_DEFAULT_FIB); | |||||
} | |||||
/* | |||||
* A handler for interface link layer address change event. | |||||
*/ | |||||
static void | |||||
nd6_iflladdr(void *arg __unused, struct ifnet *ifp) | |||||
{ | |||||
lltable_update_ifaddr(LLTABLE6(ifp)); | |||||
} | |||||
void | |||||
lle6_init(void) | |||||
{ | |||||
if (IS_DEFAULT_VNET(curvnet)) { | |||||
lle_event_eh = EVENTHANDLER_REGISTER(lle_event, nd6_lle_event, | |||||
NULL, EVENTHANDLER_PRI_ANY); | |||||
iflladdr_event_eh = EVENTHANDLER_REGISTER(iflladdr_event, | |||||
nd6_iflladdr, NULL, EVENTHANDLER_PRI_ANY); | |||||
} | |||||
} | |||||
void | |||||
lle6_destroy(void) | |||||
{ | |||||
if (IS_DEFAULT_VNET(curvnet)) { | |||||
EVENTHANDLER_DEREGISTER(lle_event, lle_event_eh); | |||||
EVENTHANDLER_DEREGISTER(iflladdr_event, iflladdr_event_eh); | |||||
} | |||||
} | |||||
struct in6_llentry { | |||||
struct llentry base; | |||||
}; | |||||
#define IN6_LLTBL_DEFAULT_HSIZE 32 | |||||
#define IN6_LLTBL_HASH(k, h) \ | |||||
(((((((k >> 8) ^ k) >> 8) ^ k) >> 8) ^ k) & ((h) - 1)) | |||||
/* | |||||
* Do actual deallocation of @lle. | |||||
* Called by LLE_FREE_LOCKED when number of references | |||||
* drops to zero. | |||||
*/ | |||||
static void | |||||
in6_lltable_destroy_lle(struct llentry *lle) | |||||
{ | |||||
LLE_WUNLOCK(lle); | |||||
LLE_LOCK_DESTROY(lle); | |||||
LLE_REQ_DESTROY(lle); | |||||
free(lle, M_LLTABLE); | |||||
} | |||||
static struct llentry * | |||||
in6_lltable_new(const struct in6_addr *addr6, u_int flags) | |||||
{ | |||||
struct in6_llentry *lle; | |||||
lle = malloc(sizeof(struct in6_llentry), M_LLTABLE, M_NOWAIT | M_ZERO); | |||||
if (lle == NULL) /* NB: caller generates msg */ | |||||
return NULL; | |||||
lle->base.r_l3addr.addr6 = *addr6; | |||||
lle->base.lle_refcnt = 1; | |||||
lle->base.lle_free = in6_lltable_destroy_lle; | |||||
LLE_LOCK_INIT(&lle->base); | |||||
LLE_REQ_INIT(&lle->base); | |||||
callout_init(&lle->base.lle_timer, 1); | |||||
return (&lle->base); | |||||
} | |||||
static int | |||||
in6_lltable_match_prefix(const struct sockaddr *saddr, | |||||
const struct sockaddr *smask, u_int flags, struct llentry *lle) | |||||
{ | |||||
const struct in6_addr *addr, *mask, *lle_addr; | |||||
addr = &((const struct sockaddr_in6 *)saddr)->sin6_addr; | |||||
mask = &((const struct sockaddr_in6 *)smask)->sin6_addr; | |||||
lle_addr = &lle->r_l3addr.addr6; | |||||
if (IN6_ARE_MASKED_ADDR_EQUAL(lle_addr, addr, mask) == 0) | |||||
return (0); | |||||
if (lle->la_flags & LLE_IFADDR) { | |||||
/* | |||||
* Delete LLE_IFADDR records IFF address & flag matches. | |||||
* Note that addr is the interface address within prefix | |||||
* being matched. | |||||
*/ | |||||
if (IN6_ARE_ADDR_EQUAL(addr, lle_addr) && | |||||
(flags & LLE_STATIC) != 0) | |||||
return (1); | |||||
return (0); | |||||
} | |||||
/* flags & LLE_STATIC means deleting both dynamic and static entries */ | |||||
if ((flags & LLE_STATIC) || !(lle->la_flags & LLE_STATIC)) | |||||
return (1); | |||||
return (0); | |||||
} | |||||
static void | |||||
in6_lltable_free_entry(struct lltable *llt, struct llentry *lle) | |||||
{ | |||||
struct ifnet *ifp; | |||||
LLE_WLOCK_ASSERT(lle); | |||||
KASSERT(llt != NULL, ("lltable is NULL")); | |||||
/* Unlink entry from table */ | |||||
if ((lle->la_flags & LLE_LINKED) != 0) { | |||||
ifp = llt->llt_ifp; | |||||
IF_AFDATA_WLOCK_ASSERT(ifp); | |||||
lltable_unlink_entry(llt, lle); | |||||
} | |||||
if (callout_stop(&lle->lle_timer) > 0) | |||||
LLE_REMREF(lle); | |||||
llentry_free(lle); | |||||
} | |||||
static int | |||||
in6_lltable_rtcheck(struct ifnet *ifp, | |||||
u_int flags, | |||||
const struct sockaddr *l3addr) | |||||
{ | |||||
const struct sockaddr_in6 *sin6; | |||||
struct nhop6_basic nh6; | |||||
struct in6_addr dst; | |||||
uint32_t scopeid; | |||||
int error; | |||||
char ip6buf[INET6_ADDRSTRLEN]; | |||||
KASSERT(l3addr->sa_family == AF_INET6, | |||||
("sin_family %d", l3addr->sa_family)); | |||||
/* Our local addresses are always only installed on the default FIB. */ | |||||
sin6 = (const struct sockaddr_in6 *)l3addr; | |||||
in6_splitscope(&sin6->sin6_addr, &dst, &scopeid); | |||||
error = fib6_lookup_nh_basic(RT_DEFAULT_FIB, &dst, scopeid, 0, 0, &nh6); | |||||
if (error != 0 || (nh6.nh_flags & NHF_GATEWAY) || nh6.nh_ifp != ifp) { | |||||
struct ifaddr *ifa; | |||||
/* | |||||
* Create an ND6 cache for an IPv6 neighbor | |||||
* that is not covered by our own prefix. | |||||
*/ | |||||
ifa = ifaof_ifpforaddr(l3addr, ifp); | |||||
if (ifa != NULL) { | |||||
ifa_free(ifa); | |||||
return 0; | |||||
} | |||||
log(LOG_INFO, "IPv6 address: \"%s\" is not on the network\n", | |||||
ip6_sprintf(ip6buf, &sin6->sin6_addr)); | |||||
return EINVAL; | |||||
} | |||||
return 0; | |||||
} | |||||
static inline uint32_t | |||||
in6_lltable_hash_dst(const struct in6_addr *dst, uint32_t hsize) | |||||
{ | |||||
return (IN6_LLTBL_HASH(dst->s6_addr32[3], hsize)); | |||||
} | |||||
static uint32_t | |||||
in6_lltable_hash(const struct llentry *lle, uint32_t hsize) | |||||
{ | |||||
return (in6_lltable_hash_dst(&lle->r_l3addr.addr6, hsize)); | |||||
} | |||||
static void | |||||
in6_lltable_fill_sa_entry(const struct llentry *lle, struct sockaddr *sa) | |||||
{ | |||||
struct sockaddr_in6 *sin6; | |||||
sin6 = (struct sockaddr_in6 *)sa; | |||||
bzero(sin6, sizeof(*sin6)); | |||||
sin6->sin6_family = AF_INET6; | |||||
sin6->sin6_len = sizeof(*sin6); | |||||
sin6->sin6_addr = lle->r_l3addr.addr6; | |||||
} | |||||
static inline struct llentry * | |||||
in6_lltable_find_dst(struct lltable *llt, const struct in6_addr *dst) | |||||
{ | |||||
struct llentry *lle; | |||||
struct llentries *lleh; | |||||
u_int hashidx; | |||||
hashidx = in6_lltable_hash_dst(dst, llt->llt_hsize); | |||||
lleh = &llt->lle_head[hashidx]; | |||||
LIST_FOREACH(lle, lleh, lle_next) { | |||||
if (lle->la_flags & LLE_DELETED) | |||||
continue; | |||||
if (IN6_ARE_ADDR_EQUAL(&lle->r_l3addr.addr6, dst)) | |||||
break; | |||||
} | |||||
return (lle); | |||||
} | |||||
static void | |||||
in6_lltable_delete_entry(struct lltable *llt, struct llentry *lle) | |||||
{ | |||||
lle->la_flags |= LLE_DELETED; | |||||
EVENTHANDLER_INVOKE(lle_event, lle, LLENTRY_DELETED); | |||||
#ifdef DIAGNOSTIC | |||||
log(LOG_INFO, "ifaddr cache = %p is deleted\n", lle); | |||||
#endif | |||||
llentry_free(lle); | |||||
} | |||||
static struct llentry * | |||||
in6_lltable_alloc(struct lltable *llt, u_int flags, | |||||
const struct sockaddr *l3addr) | |||||
{ | |||||
const struct sockaddr_in6 *sin6 = (const struct sockaddr_in6 *)l3addr; | |||||
struct ifnet *ifp = llt->llt_ifp; | |||||
struct llentry *lle; | |||||
char linkhdr[LLE_MAX_LINKHDR]; | |||||
size_t linkhdrsize; | |||||
int lladdr_off; | |||||
KASSERT(l3addr->sa_family == AF_INET6, | |||||
("sin_family %d", l3addr->sa_family)); | |||||
/* | |||||
* A route that covers the given address must have | |||||
* been installed 1st because we are doing a resolution, | |||||
* verify this. | |||||
*/ | |||||
if (!(flags & LLE_IFADDR) && | |||||
in6_lltable_rtcheck(ifp, flags, l3addr) != 0) | |||||
return (NULL); | |||||
lle = in6_lltable_new(&sin6->sin6_addr, flags); | |||||
if (lle == NULL) { | |||||
log(LOG_INFO, "lla_lookup: new lle malloc failed\n"); | |||||
return (NULL); | |||||
} | |||||
lle->la_flags = flags; | |||||
if ((flags & LLE_IFADDR) == LLE_IFADDR) { | |||||
linkhdrsize = LLE_MAX_LINKHDR; | |||||
if (lltable_calc_llheader(ifp, AF_INET6, IF_LLADDR(ifp), | |||||
linkhdr, &linkhdrsize, &lladdr_off) != 0) | |||||
return (NULL); | |||||
lltable_set_entry_addr(ifp, lle, linkhdr, linkhdrsize, | |||||
lladdr_off); | |||||
lle->la_flags |= LLE_STATIC; | |||||
} | |||||
if ((lle->la_flags & LLE_STATIC) != 0) | |||||
lle->ln_state = ND6_LLINFO_REACHABLE; | |||||
return (lle); | |||||
} | |||||
static struct llentry * | |||||
in6_lltable_lookup(struct lltable *llt, u_int flags, | |||||
const struct sockaddr *l3addr) | |||||
{ | |||||
const struct sockaddr_in6 *sin6 = (const struct sockaddr_in6 *)l3addr; | |||||
struct llentry *lle; | |||||
IF_AFDATA_LOCK_ASSERT(llt->llt_ifp); | |||||
KASSERT(l3addr->sa_family == AF_INET6, | |||||
("sin_family %d", l3addr->sa_family)); | |||||
lle = in6_lltable_find_dst(llt, &sin6->sin6_addr); | |||||
if (lle == NULL) | |||||
return (NULL); | |||||
KASSERT((flags & (LLE_UNLOCKED|LLE_EXCLUSIVE)) != | |||||
(LLE_UNLOCKED|LLE_EXCLUSIVE),("wrong lle request flags: 0x%X", | |||||
flags)); | |||||
if (flags & LLE_UNLOCKED) | |||||
return (lle); | |||||
if (flags & LLE_EXCLUSIVE) | |||||
LLE_WLOCK(lle); | |||||
else | |||||
LLE_RLOCK(lle); | |||||
return (lle); | |||||
} | |||||
static int | |||||
in6_lltable_dump_entry(struct lltable *llt, struct llentry *lle, | |||||
struct sysctl_req *wr) | |||||
{ | |||||
struct ifnet *ifp = llt->llt_ifp; | |||||
/* XXX stack use */ | |||||
struct { | |||||
struct rt_msghdr rtm; | |||||
struct sockaddr_in6 sin6; | |||||
/* | |||||
* ndp.c assumes that sdl is word aligned | |||||
*/ | |||||
#ifdef __LP64__ | |||||
uint32_t pad; | |||||
#endif | |||||
struct sockaddr_dl sdl; | |||||
} ndpc; | |||||
struct sockaddr_dl *sdl; | |||||
int error; | |||||
bzero(&ndpc, sizeof(ndpc)); | |||||
/* skip deleted entries */ | |||||
if ((lle->la_flags & LLE_DELETED) == LLE_DELETED) | |||||
return (0); | |||||
/* Skip if jailed and not a valid IP of the prison. */ | |||||
lltable_fill_sa_entry(lle, | |||||
(struct sockaddr *)&ndpc.sin6); | |||||
if (prison_if(wr->td->td_ucred, | |||||
(struct sockaddr *)&ndpc.sin6) != 0) | |||||
return (0); | |||||
/* | |||||
* produce a msg made of: | |||||
* struct rt_msghdr; | |||||
* struct sockaddr_in6 (IPv6) | |||||
* struct sockaddr_dl; | |||||
*/ | |||||
ndpc.rtm.rtm_msglen = sizeof(ndpc); | |||||
ndpc.rtm.rtm_version = RTM_VERSION; | |||||
ndpc.rtm.rtm_type = RTM_GET; | |||||
ndpc.rtm.rtm_flags = RTF_UP; | |||||
ndpc.rtm.rtm_addrs = RTA_DST | RTA_GATEWAY; | |||||
if (V_deembed_scopeid) | |||||
sa6_recoverscope(&ndpc.sin6); | |||||
/* publish */ | |||||
if (lle->la_flags & LLE_PUB) | |||||
ndpc.rtm.rtm_flags |= RTF_ANNOUNCE; | |||||
sdl = &ndpc.sdl; | |||||
sdl->sdl_family = AF_LINK; | |||||
sdl->sdl_len = sizeof(*sdl); | |||||
sdl->sdl_alen = ifp->if_addrlen; | |||||
sdl->sdl_index = ifp->if_index; | |||||
sdl->sdl_type = ifp->if_type; | |||||
bcopy(&lle->ll_addr, LLADDR(sdl), ifp->if_addrlen); | |||||
if (lle->la_expire != 0) | |||||
ndpc.rtm.rtm_rmx.rmx_expire = lle->la_expire + | |||||
lle->lle_remtime / hz + | |||||
time_second - time_uptime; | |||||
ndpc.rtm.rtm_flags |= (RTF_HOST | RTF_LLDATA); | |||||
if (lle->la_flags & LLE_STATIC) | |||||
ndpc.rtm.rtm_flags |= RTF_STATIC; | |||||
if (lle->la_flags & LLE_IFADDR) | |||||
ndpc.rtm.rtm_flags |= RTF_PINNED; | |||||
if (lle->ln_router != 0) | |||||
ndpc.rtm.rtm_flags |= RTF_GATEWAY; | |||||
ndpc.rtm.rtm_rmx.rmx_pksent = lle->la_asked; | |||||
/* Store state in rmx_weight value */ | |||||
ndpc.rtm.rtm_rmx.rmx_state = lle->ln_state; | |||||
ndpc.rtm.rtm_index = ifp->if_index; | |||||
error = SYSCTL_OUT(wr, &ndpc, sizeof(ndpc)); | |||||
return (error); | |||||
} | |||||
struct lltable * | |||||
in6_lltattach(struct ifnet *ifp) | |||||
{ | |||||
struct lltable *llt; | |||||
llt = lltable_allocate_htbl(IN6_LLTBL_DEFAULT_HSIZE); | |||||
llt->llt_af = AF_INET6; | |||||
llt->llt_ifp = ifp; | |||||
llt->llt_lookup = in6_lltable_lookup; | |||||
llt->llt_alloc_entry = in6_lltable_alloc; | |||||
llt->llt_delete_entry = in6_lltable_delete_entry; | |||||
llt->llt_dump_entry = in6_lltable_dump_entry; | |||||
llt->llt_hash = in6_lltable_hash; | |||||
llt->llt_fill_sa_entry = in6_lltable_fill_sa_entry; | |||||
llt->llt_free_entry = in6_lltable_free_entry; | |||||
llt->llt_match_prefix = in6_lltable_match_prefix; | |||||
lltable_link(llt); | |||||
return (llt); | |||||
} | |||||