#include <sys/param.h>
#include <sys/systm.h>
#include <sys/malloc.h>
#include <sys/mbuf.h>
#include <sys/socket.h>
#include <sys/sockio.h>
#include <sys/time.h>
#include <sys/kernel.h>
#include <sys/sysctl.h>
#include <sys/errno.h>
#include <sys/syslog.h>
#include <sys/protosw.h>
#include <sys/proc.h>
#include <sys/mcache.h>
#include <kern/queue.h>
#include <kern/zalloc.h>
#define DONT_WARN_OBSOLETE
#include <net/if.h>
#include <net/if_dl.h>
#include <net/if_types.h>
#include <net/if_llreach.h>
#include <net/route.h>
#include <net/dlil.h>
#include <net/ntstat.h>
#include <netinet/in.h>
#include <netinet/in_arp.h>
#include <netinet/if_ether.h>
#include <netinet6/in6_var.h>
#include <netinet/ip6.h>
#include <netinet6/ip6_var.h>
#include <netinet6/nd6.h>
#include <netinet6/scope6_var.h>
#include <netinet/icmp6.h>
#include "loop.h"
#include <net/net_osdep.h>
#define ND6_SLOWTIMER_INTERVAL (60 * 60)
#define ND6_RECALC_REACHTM_INTERVAL (60 * 120)
#define equal(a1, a2) (bcmp((caddr_t)(a1), (caddr_t)(a2), (a1)->sa_len) == 0)
int nd6_prune = 1;
int nd6_delay = 5;
int nd6_umaxtries = 3;
int nd6_mmaxtries = 3;
int nd6_useloopback = 1;
int nd6_gctimer = (60 * 60 * 24);
int nd6_maxndopt = 10;
int nd6_maxnudhint = 0;
int nd6_maxqueuelen = 1;
#if ND6_DEBUG
int nd6_debug = 1;
#else
int nd6_debug = 0;
#endif
int nd6_optimistic_dad =
(ND6_OPTIMISTIC_DAD_LINKLOCAL|ND6_OPTIMISTIC_DAD_AUTOCONF|
ND6_OPTIMISTIC_DAD_TEMPORARY|ND6_OPTIMISTIC_DAD_DYNAMIC);
static int nd6_is_new_addr_neighbor (struct sockaddr_in6 *, struct ifnet *);
static int nd6_inuse, nd6_allocated;
struct llinfo_nd6 llinfo_nd6 = {
&llinfo_nd6, &llinfo_nd6, NULL, NULL, 0, 0, 0, 0, 0, 0, NULL, 0
};
size_t nd_ifinfo_indexlim = 32;
struct nd_ifinfo *nd_ifinfo = NULL;
static lck_grp_attr_t *nd_if_lock_grp_attr;
static lck_grp_t *nd_if_lock_grp;
static lck_attr_t *nd_if_lock_attr;
decl_lck_rw_data(, nd_if_rwlock_data);
lck_rw_t *nd_if_rwlock = &nd_if_rwlock_data;
struct nd_drhead nd_defrouter;
struct nd_prhead nd_prefix = { 0 };
static boolean_t nd6_drain_busy;
static void *nd6_drain_waitchan = &nd6_drain_busy;
static int nd6_drain_waiters = 0;
int nd6_recalc_reachtm_interval = ND6_RECALC_REACHTM_INTERVAL;
static struct sockaddr_in6 all1_sa;
static int regen_tmpaddr(struct in6_ifaddr *);
extern lck_mtx_t *nd6_mutex;
static void nd6_slowtimo(void *ignored_arg);
static struct llinfo_nd6 *nd6_llinfo_alloc(void);
static void nd6_llinfo_free(void *);
static void nd6_llinfo_purge(struct rtentry *);
static void nd6_llinfo_get_ri(struct rtentry *, struct rt_reach_info *);
static void nd6_llinfo_get_iflri(struct rtentry *, struct ifnet_llreach_info *);
static int nd6_siocgdrlst(void *, int);
static int nd6_siocgprlst(void *, int);
#define LN_DEQUEUE(_ln) do { \
lck_mtx_assert(rnh_lock, LCK_MTX_ASSERT_OWNED); \
RT_LOCK_ASSERT_HELD((_ln)->ln_rt); \
(_ln)->ln_next->ln_prev = (_ln)->ln_prev; \
(_ln)->ln_prev->ln_next = (_ln)->ln_next; \
(_ln)->ln_prev = (_ln)->ln_next = NULL; \
(_ln)->ln_flags &= ~ND6_LNF_IN_USE; \
} while (0)
#define LN_INSERTHEAD(_ln) do { \
lck_mtx_assert(rnh_lock, LCK_MTX_ASSERT_OWNED); \
RT_LOCK_ASSERT_HELD((_ln)->ln_rt); \
(_ln)->ln_next = llinfo_nd6.ln_next; \
llinfo_nd6.ln_next = (_ln); \
(_ln)->ln_prev = &llinfo_nd6; \
(_ln)->ln_next->ln_prev = (_ln); \
(_ln)->ln_flags |= ND6_LNF_IN_USE; \
} while (0)
static struct zone *llinfo_nd6_zone;
#define LLINFO_ND6_ZONE_MAX 256
#define LLINFO_ND6_ZONE_NAME "llinfo_nd6"
void
nd6_init()
{
static int nd6_init_done = 0;
int i;
if (nd6_init_done) {
log(LOG_NOTICE, "nd6_init called more than once (ignored)\n");
return;
}
all1_sa.sin6_family = AF_INET6;
all1_sa.sin6_len = sizeof(struct sockaddr_in6);
for (i = 0; i < sizeof(all1_sa.sin6_addr); i++)
all1_sa.sin6_addr.s6_addr[i] = 0xff;
TAILQ_INIT(&nd_defrouter);
nd_if_lock_grp_attr = lck_grp_attr_alloc_init();
nd_if_lock_grp = lck_grp_alloc_init("nd_if_lock", nd_if_lock_grp_attr);
nd_if_lock_attr = lck_attr_alloc_init();
lck_rw_init(nd_if_rwlock, nd_if_lock_grp, nd_if_lock_attr);
llinfo_nd6_zone = zinit(sizeof (struct llinfo_nd6),
LLINFO_ND6_ZONE_MAX * sizeof (struct llinfo_nd6), 0,
LLINFO_ND6_ZONE_NAME);
if (llinfo_nd6_zone == NULL)
panic("%s: failed allocating llinfo_nd6_zone", __func__);
zone_change(llinfo_nd6_zone, Z_EXPAND, TRUE);
zone_change(llinfo_nd6_zone, Z_CALLERACCT, FALSE);
nd6_nbr_init();
nd6_rtr_init();
nd6_prproxy_init();
nd6_init_done = 1;
timeout(nd6_slowtimo, (caddr_t)0, ND6_SLOWTIMER_INTERVAL * hz);
}
static struct llinfo_nd6 *
nd6_llinfo_alloc(void)
{
return (zalloc(llinfo_nd6_zone));
}
static void
nd6_llinfo_free(void *arg)
{
struct llinfo_nd6 *ln = arg;
if (ln->ln_next != NULL || ln->ln_prev != NULL) {
panic("%s: trying to free %p when it is in use", __func__, ln);
}
if (ln->ln_hold != NULL) {
m_freem(ln->ln_hold);
ln->ln_hold = NULL;
}
VERIFY(ln->ln_rt->rt_llinfo == ln);
if (ln->ln_rt->rt_llinfo_purge != NULL)
ln->ln_rt->rt_llinfo_purge(ln->ln_rt);
zfree(llinfo_nd6_zone, ln);
}
static void
nd6_llinfo_purge(struct rtentry *rt)
{
struct llinfo_nd6 *ln = rt->rt_llinfo;
RT_LOCK_ASSERT_HELD(rt);
VERIFY(rt->rt_llinfo_purge == nd6_llinfo_purge && ln != NULL);
if (ln->ln_llreach != NULL) {
RT_CONVERT_LOCK(rt);
ifnet_llreach_free(ln->ln_llreach);
ln->ln_llreach = NULL;
}
ln->ln_lastused = 0;
}
static void
nd6_llinfo_get_ri(struct rtentry *rt, struct rt_reach_info *ri)
{
struct llinfo_nd6 *ln = rt->rt_llinfo;
struct if_llreach *lr = ln->ln_llreach;
if (lr == NULL) {
bzero(ri, sizeof (*ri));
ri->ri_rssi = IFNET_RSSI_UNKNOWN;
ri->ri_lqm = IFNET_LQM_THRESH_OFF;
ri->ri_npm = IFNET_NPM_THRESH_UNKNOWN;
} else {
IFLR_LOCK(lr);
ifnet_lr2ri(lr, ri);
ri->ri_snd_expire =
ifnet_llreach_up2calexp(lr, ln->ln_lastused);
IFLR_UNLOCK(lr);
}
}
static void
nd6_llinfo_get_iflri(struct rtentry *rt, struct ifnet_llreach_info *iflri)
{
struct llinfo_nd6 *ln = rt->rt_llinfo;
struct if_llreach *lr = ln->ln_llreach;
if (lr == NULL) {
bzero(iflri, sizeof (*iflri));
iflri->iflri_rssi = IFNET_RSSI_UNKNOWN;
iflri->iflri_lqm = IFNET_LQM_THRESH_OFF;
iflri->iflri_npm = IFNET_NPM_THRESH_UNKNOWN;
} else {
IFLR_LOCK(lr);
ifnet_lr2iflri(lr, iflri);
iflri->iflri_snd_expire =
ifnet_llreach_up2upexp(lr, ln->ln_lastused);
IFLR_UNLOCK(lr);
}
}
int
nd6_ifattach(struct ifnet *ifp)
{
lck_rw_lock_exclusive(nd_if_rwlock);
if (nd_ifinfo == NULL || if_index >= nd_ifinfo_indexlim) {
size_t n;
caddr_t q;
size_t newlim = nd_ifinfo_indexlim;
while (if_index >= newlim)
newlim <<= 1;
n = newlim * sizeof(struct nd_ifinfo);
q = (caddr_t)_MALLOC(n, M_IP6NDP, M_WAITOK);
if (q == NULL) {
lck_rw_done(nd_if_rwlock);
return (ENOBUFS);
}
bzero(q, n);
nd_ifinfo_indexlim = newlim;
if (nd_ifinfo) {
bcopy((caddr_t)nd_ifinfo, q, n/2);
FREE((caddr_t)nd_ifinfo, M_IP6NDP);
}
nd_ifinfo = (struct nd_ifinfo *)(void *)q;
}
#define ND nd_ifinfo[ifp->if_index]
if (ND.initialized) {
lck_rw_done(nd_if_rwlock);
return (0);
}
lck_mtx_init(&ND.lock, nd_if_lock_grp, nd_if_lock_attr);
ND.initialized = TRUE;
ND.linkmtu = ifp->if_mtu;
ND.chlim = IPV6_DEFHLIM;
ND.basereachable = REACHABLE_TIME;
ND.reachable = ND_COMPUTE_RTIME(ND.basereachable);
ND.retrans = RETRANS_TIMER;
ND.flags = ND6_IFF_PERFORMNUD;
lck_rw_done(nd_if_rwlock);
#undef ND
nd6_setmtu(ifp);
return (0);
}
void
nd6_setmtu(struct ifnet *ifp)
{
struct nd_ifinfo *ndi;
u_int32_t oldmaxmtu, maxmtu;
lck_rw_lock_shared(nd_if_rwlock);
if (ifp->if_index >= nd_ifinfo_indexlim ||
!nd_ifinfo[ifp->if_index].initialized) {
lck_rw_done(nd_if_rwlock);
return;
}
ndi = &nd_ifinfo[ifp->if_index];
VERIFY(ndi->initialized);
lck_mtx_lock(&ndi->lock);
oldmaxmtu = ndi->maxmtu;
maxmtu = ndi->maxmtu = ifp->if_mtu;
if (oldmaxmtu >= IPV6_MMTU && ndi->maxmtu < IPV6_MMTU) {
log(LOG_NOTICE, "nd6_setmtu: "
"new link MTU on %s%d (%u) is too small for IPv6\n",
ifp->if_name, ifp->if_unit, (uint32_t)ndi->maxmtu);
}
ndi->linkmtu = ifp->if_mtu;
lck_mtx_unlock(&ndi->lock);
lck_rw_done(nd_if_rwlock);
if (maxmtu > in6_maxmtu)
in6_setmaxmtu();
}
void
nd6_option_init(
void *opt,
int icmp6len,
union nd_opts *ndopts)
{
bzero(ndopts, sizeof(*ndopts));
ndopts->nd_opts_search = (struct nd_opt_hdr *)opt;
ndopts->nd_opts_last
= (struct nd_opt_hdr *)(((u_char *)opt) + icmp6len);
if (icmp6len == 0) {
ndopts->nd_opts_done = 1;
ndopts->nd_opts_search = NULL;
}
}
struct nd_opt_hdr *
nd6_option(
union nd_opts *ndopts)
{
struct nd_opt_hdr *nd_opt;
int olen;
if (!ndopts)
panic("ndopts == NULL in nd6_option\n");
if (!ndopts->nd_opts_last)
panic("uninitialized ndopts in nd6_option\n");
if (!ndopts->nd_opts_search)
return NULL;
if (ndopts->nd_opts_done)
return NULL;
nd_opt = ndopts->nd_opts_search;
if ((caddr_t)&nd_opt->nd_opt_len >= (caddr_t)ndopts->nd_opts_last) {
bzero(ndopts, sizeof(*ndopts));
return NULL;
}
olen = nd_opt->nd_opt_len << 3;
if (olen == 0) {
bzero(ndopts, sizeof(*ndopts));
return NULL;
}
ndopts->nd_opts_search = (struct nd_opt_hdr *)((caddr_t)nd_opt + olen);
if (ndopts->nd_opts_search > ndopts->nd_opts_last) {
bzero(ndopts, sizeof(*ndopts));
return NULL;
} else if (ndopts->nd_opts_search == ndopts->nd_opts_last) {
ndopts->nd_opts_done = 1;
ndopts->nd_opts_search = NULL;
}
return nd_opt;
}
int
nd6_options(
union nd_opts *ndopts)
{
struct nd_opt_hdr *nd_opt;
int i = 0;
if (ndopts == NULL)
panic("ndopts == NULL in nd6_options");
if (ndopts->nd_opts_last == NULL)
panic("uninitialized ndopts in nd6_options");
if (ndopts->nd_opts_search == NULL)
return 0;
while (1) {
nd_opt = nd6_option(ndopts);
if (nd_opt == NULL && ndopts->nd_opts_last == NULL) {
icmp6stat.icp6s_nd_badopt++;
bzero(ndopts, sizeof(*ndopts));
return -1;
}
if (nd_opt == NULL)
goto skip1;
switch (nd_opt->nd_opt_type) {
case ND_OPT_SOURCE_LINKADDR:
case ND_OPT_TARGET_LINKADDR:
case ND_OPT_MTU:
case ND_OPT_REDIRECTED_HEADER:
if (ndopts->nd_opt_array[nd_opt->nd_opt_type]) {
nd6log((LOG_INFO,
"duplicated ND6 option found (type=%d)\n",
nd_opt->nd_opt_type));
} else {
ndopts->nd_opt_array[nd_opt->nd_opt_type]
= nd_opt;
}
break;
case ND_OPT_PREFIX_INFORMATION:
if (ndopts->nd_opt_array[nd_opt->nd_opt_type] == 0) {
ndopts->nd_opt_array[nd_opt->nd_opt_type]
= nd_opt;
}
ndopts->nd_opts_pi_end =
(struct nd_opt_prefix_info *)nd_opt;
break;
case ND_OPT_RDNSS:
break;
default:
nd6log((LOG_DEBUG,
"nd6_options: unsupported option %d - "
"option ignored\n", nd_opt->nd_opt_type));
}
skip1:
i++;
if (i > nd6_maxndopt) {
icmp6stat.icp6s_nd_toomanyopt++;
nd6log((LOG_INFO, "too many loop in nd opt\n"));
break;
}
if (ndopts->nd_opts_done)
break;
}
return 0;
}
void
nd6_drain(__unused void *ignored_arg)
{
struct llinfo_nd6 *ln;
struct nd_defrouter *dr;
struct nd_prefix *pr;
struct ifnet *ifp = NULL;
struct in6_ifaddr *ia6, *nia6;
struct in6_addrlifetime *lt6;
struct timeval timenow;
getmicrotime(&timenow);
again:
lck_mtx_lock(rnh_lock);
ln = llinfo_nd6.ln_next;
while (ln != NULL && ln != &llinfo_nd6) {
struct rtentry *rt;
struct sockaddr_in6 *dst;
struct llinfo_nd6 *next;
struct nd_ifinfo *ndi;
u_int32_t retrans, flags;
next = ln->ln_next;
rt = ln->ln_rt;
RT_LOCK(rt);
if (ln->ln_flags & ND6_LNF_TIMER_SKIP) {
RT_UNLOCK(rt);
ln = next;
continue;
}
if ((ifp = rt->rt_ifp) == NULL) {
panic("%s: ln(%p) rt(%p) rt_ifp == NULL", __func__,
ln, rt);
}
if ((struct llinfo_nd6 *)rt->rt_llinfo != ln) {
panic("%s: rt_llinfo(%p) is not equal to ln(%p)",
__func__, rt->rt_llinfo, ln);
}
dst = (struct sockaddr_in6 *)(void *)rt_key(rt);
if (dst == NULL) {
panic("%s: rt(%p) key is NULL ln(%p)", __func__,
rt, ln);
}
ln->ln_flags |= ND6_LNF_TIMER_SKIP;
if (ln->ln_expire > timenow.tv_sec) {
RT_UNLOCK(rt);
ln = next;
continue;
}
lck_rw_lock_shared(nd_if_rwlock);
if (ifp->if_index >= nd_ifinfo_indexlim) {
lck_rw_done(nd_if_rwlock);
RT_UNLOCK(rt);
ln = next;
continue;
}
ndi = ND_IFINFO(ifp);
VERIFY(ndi->initialized);
lck_mtx_lock(&ndi->lock);
retrans = ndi->retrans;
flags = ndi->flags;
lck_mtx_unlock(&ndi->lock);
lck_rw_done(nd_if_rwlock);
RT_LOCK_ASSERT_HELD(rt);
switch (ln->ln_state) {
case ND6_LLINFO_INCOMPLETE:
if (ln->ln_asked < nd6_mmaxtries) {
ln->ln_asked++;
ln->ln_expire = timenow.tv_sec + retrans / 1000;
RT_ADDREF_LOCKED(rt);
RT_UNLOCK(rt);
lck_mtx_unlock(rnh_lock);
if (ip6_forwarding) {
nd6_prproxy_ns_output(ifp, NULL,
&dst->sin6_addr, ln);
} else {
nd6_ns_output(ifp, NULL,
&dst->sin6_addr, ln, 0);
}
RT_REMREF(rt);
} else {
struct mbuf *m = ln->ln_hold;
ln->ln_hold = NULL;
if (m != NULL) {
m->m_pkthdr.rcvif = ifp;
RT_UNLOCK(rt);
lck_mtx_unlock(rnh_lock);
icmp6_error(m, ICMP6_DST_UNREACH,
ICMP6_DST_UNREACH_ADDR, 0);
} else {
RT_UNLOCK(rt);
lck_mtx_unlock(rnh_lock);
}
nd6_free(rt);
}
lck_mtx_assert(rnh_lock, LCK_MTX_ASSERT_NOTOWNED);
goto again;
case ND6_LLINFO_REACHABLE:
if (ln->ln_expire) {
ln->ln_state = ND6_LLINFO_STALE;
ln->ln_expire = rt_expiry(rt, timenow.tv_sec,
nd6_gctimer);
}
RT_UNLOCK(rt);
break;
case ND6_LLINFO_STALE:
case ND6_LLINFO_PURGE:
if (ln->ln_expire) {
RT_UNLOCK(rt);
lck_mtx_unlock(rnh_lock);
nd6_free(rt);
lck_mtx_assert(rnh_lock,
LCK_MTX_ASSERT_NOTOWNED);
goto again;
} else {
RT_UNLOCK(rt);
}
break;
case ND6_LLINFO_DELAY:
if ((flags & ND6_IFF_PERFORMNUD) != 0) {
ln->ln_asked = 1;
ln->ln_state = ND6_LLINFO_PROBE;
ln->ln_expire = timenow.tv_sec + retrans / 1000;
RT_ADDREF_LOCKED(rt);
RT_UNLOCK(rt);
lck_mtx_unlock(rnh_lock);
nd6_ns_output(ifp, &dst->sin6_addr,
&dst->sin6_addr, ln, 0);
lck_mtx_assert(rnh_lock,
LCK_MTX_ASSERT_NOTOWNED);
RT_REMREF(rt);
goto again;
}
ln->ln_state = ND6_LLINFO_STALE;
ln->ln_expire = rt_expiry(rt, timenow.tv_sec,
nd6_gctimer);
RT_UNLOCK(rt);
break;
case ND6_LLINFO_PROBE:
if (ln->ln_asked < nd6_umaxtries) {
ln->ln_asked++;
ln->ln_expire = timenow.tv_sec + retrans / 1000;
RT_ADDREF_LOCKED(rt);
RT_UNLOCK(rt);
lck_mtx_unlock(rnh_lock);
nd6_ns_output(ifp, &dst->sin6_addr,
&dst->sin6_addr, ln, 0);
RT_REMREF(rt);
} else {
RT_UNLOCK(rt);
lck_mtx_unlock(rnh_lock);
nd6_free(rt);
}
lck_mtx_assert(rnh_lock, LCK_MTX_ASSERT_NOTOWNED);
goto again;
default:
RT_UNLOCK(rt);
break;
}
ln = next;
}
lck_mtx_assert(rnh_lock, LCK_MTX_ASSERT_OWNED);
ln = llinfo_nd6.ln_next;
while (ln != NULL && ln != &llinfo_nd6) {
struct rtentry *rt = ln->ln_rt;
struct llinfo_nd6 *next = ln->ln_next;
RT_LOCK_SPIN(rt);
if (ln->ln_flags & ND6_LNF_TIMER_SKIP)
ln->ln_flags &= ~ND6_LNF_TIMER_SKIP;
RT_UNLOCK(rt);
ln = next;
}
lck_mtx_unlock(rnh_lock);
lck_mtx_lock(nd6_mutex);
dr = TAILQ_FIRST(&nd_defrouter);
while (dr) {
if (dr->expire && dr->expire < timenow.tv_sec) {
struct nd_defrouter *t;
t = TAILQ_NEXT(dr, dr_entry);
defrtrlist_del(dr);
dr = t;
} else {
dr = TAILQ_NEXT(dr, dr_entry);
}
}
lck_mtx_unlock(nd6_mutex);
addrloop:
lck_rw_lock_exclusive(&in6_ifaddr_rwlock);
for (ia6 = in6_ifaddrs; ia6; ia6 = nia6) {
nia6 = ia6->ia_next;
IFA_LOCK(&ia6->ia_ifa);
IFA_ADDREF_LOCKED(&ia6->ia_ifa);
lt6 = &ia6->ia6_lifetime;
if (IFA6_IS_INVALID(ia6)) {
if (ip6_use_tempaddr &&
(ia6->ia6_flags & IN6_IFF_TEMPORARY) != 0) {
IFA_UNLOCK(&ia6->ia_ifa);
lck_rw_done(&in6_ifaddr_rwlock);
(void) regen_tmpaddr(ia6);
} else {
IFA_UNLOCK(&ia6->ia_ifa);
lck_rw_done(&in6_ifaddr_rwlock);
}
in6_purgeaddr(&ia6->ia_ifa);
IFA_REMREF(&ia6->ia_ifa);
goto addrloop;
}
IFA_LOCK_ASSERT_HELD(&ia6->ia_ifa);
if (IFA6_IS_DEPRECATED(ia6)) {
int oldflags = ia6->ia6_flags;
ia6->ia6_flags |= IN6_IFF_DEPRECATED;
if (ip6_use_tempaddr &&
(ia6->ia6_flags & IN6_IFF_TEMPORARY) != 0 &&
(oldflags & IN6_IFF_DEPRECATED) == 0) {
IFA_UNLOCK(&ia6->ia_ifa);
lck_rw_done(&in6_ifaddr_rwlock);
if (regen_tmpaddr(ia6) == 0) {
IFA_REMREF(&ia6->ia_ifa);
goto addrloop;
}
lck_rw_lock_exclusive(&in6_ifaddr_rwlock);
} else {
IFA_UNLOCK(&ia6->ia_ifa);
}
} else {
ia6->ia6_flags &= ~IN6_IFF_DEPRECATED;
IFA_UNLOCK(&ia6->ia_ifa);
}
lck_rw_assert(&in6_ifaddr_rwlock, LCK_RW_ASSERT_EXCLUSIVE);
IFA_REMREF(&ia6->ia_ifa);
}
lck_rw_done(&in6_ifaddr_rwlock);
lck_mtx_lock(nd6_mutex);
while (nd6_drain_busy) {
nd6_drain_waiters++;
msleep(nd6_drain_waitchan, nd6_mutex, (PZERO-1),
__func__, NULL);
lck_mtx_assert(nd6_mutex, LCK_MTX_ASSERT_OWNED);
}
nd6_drain_busy = TRUE;
pr = nd_prefix.lh_first;
while (pr) {
NDPR_LOCK(pr);
if (pr->ndpr_stateflags & NDPRF_PROCESSED) {
NDPR_UNLOCK(pr);
pr = pr->ndpr_next;
continue;
}
if (pr->ndpr_expire && pr->ndpr_expire < timenow.tv_sec) {
pr->ndpr_stateflags |= NDPRF_PROCESSED;
NDPR_ADDREF_LOCKED(pr);
prelist_remove(pr);
NDPR_UNLOCK(pr);
NDPR_REMREF(pr);
pr = nd_prefix.lh_first;
} else {
pr->ndpr_stateflags |= NDPRF_PROCESSED;
NDPR_UNLOCK(pr);
pr = pr->ndpr_next;
}
}
LIST_FOREACH(pr, &nd_prefix, ndpr_entry) {
NDPR_LOCK(pr);
pr->ndpr_stateflags &= ~NDPRF_PROCESSED;
NDPR_UNLOCK(pr);
}
nd6_drain_busy = FALSE;
if (nd6_drain_waiters > 0) {
nd6_drain_waiters = 0;
wakeup(nd6_drain_waitchan);
}
lck_mtx_unlock(nd6_mutex);
}
void
nd6_post_msg(u_int32_t code, struct nd_prefix_list *prefix_list,
u_int32_t list_length, u_int32_t mtu, char *dl_addr, u_int32_t dl_addr_len)
{
struct kev_msg ev_msg;
struct kev_nd6_ra_data nd6_ra_msg_data;
struct nd_prefix_list *itr = prefix_list;
bzero(&ev_msg, sizeof(struct kev_msg));
ev_msg.vendor_code = KEV_VENDOR_APPLE;
ev_msg.kev_class = KEV_NETWORK_CLASS;
ev_msg.kev_subclass = KEV_ND6_SUBCLASS;
ev_msg.event_code = code;
bzero(&nd6_ra_msg_data, sizeof(nd6_ra_msg_data));
nd6_ra_msg_data.lladdrlen = (dl_addr_len <= ND6_ROUTER_LL_SIZE) ?
dl_addr_len : ND6_ROUTER_LL_SIZE;
bcopy(dl_addr, &nd6_ra_msg_data.lladdr, nd6_ra_msg_data.lladdrlen);
if (mtu > 0 && mtu >= IPV6_MMTU) {
nd6_ra_msg_data.mtu = mtu;
nd6_ra_msg_data.flags |= KEV_ND6_DATA_VALID_MTU;
}
if (list_length > 0 && prefix_list != NULL) {
nd6_ra_msg_data.list_length = list_length;
nd6_ra_msg_data.flags |= KEV_ND6_DATA_VALID_PREFIX;
}
while (itr != NULL && nd6_ra_msg_data.list_index < list_length) {
bcopy(&itr->pr.ndpr_prefix, &nd6_ra_msg_data.prefix.prefix,
sizeof (nd6_ra_msg_data.prefix.prefix));
nd6_ra_msg_data.prefix.raflags = itr->pr.ndpr_raf;
nd6_ra_msg_data.prefix.prefixlen = itr->pr.ndpr_plen;
nd6_ra_msg_data.prefix.origin = PR_ORIG_RA;
nd6_ra_msg_data.prefix.vltime = itr->pr.ndpr_vltime;
nd6_ra_msg_data.prefix.pltime = itr->pr.ndpr_pltime;
nd6_ra_msg_data.prefix.expire = itr->pr.ndpr_expire;
nd6_ra_msg_data.prefix.flags = itr->pr.ndpr_stateflags;
nd6_ra_msg_data.prefix.refcnt = itr->pr.ndpr_addrcnt;
nd6_ra_msg_data.prefix.if_index = itr->pr.ndpr_ifp->if_index;
ev_msg.dv[0].data_ptr = &nd6_ra_msg_data;
ev_msg.dv[0].data_length = sizeof(nd6_ra_msg_data);
ev_msg.dv[1].data_length = 0;
kev_post_msg(&ev_msg);
bzero(&nd6_ra_msg_data.prefix, sizeof(nd6_ra_msg_data.prefix));
itr = itr->next;
nd6_ra_msg_data.list_index++;
}
}
void
nd6_timer(__unused void *ignored_arg)
{
nd6_drain(NULL);
timeout(nd6_timer, (caddr_t)0, nd6_prune * hz);
}
static int
regen_tmpaddr(
struct in6_ifaddr *ia6)
{
struct ifaddr *ifa;
struct ifnet *ifp;
struct in6_ifaddr *public_ifa6 = NULL;
struct timeval timenow;
getmicrotime(&timenow);
ifp = ia6->ia_ifa.ifa_ifp;
ifnet_lock_shared(ifp);
for (ifa = ifp->if_addrlist.tqh_first; ifa;
ifa = ifa->ifa_list.tqe_next)
{
struct in6_ifaddr *it6;
IFA_LOCK(ifa);
if (ifa->ifa_addr->sa_family != AF_INET6) {
IFA_UNLOCK(ifa);
continue;
}
it6 = (struct in6_ifaddr *)ifa;
if ((it6->ia6_flags & IN6_IFF_AUTOCONF) == 0) {
IFA_UNLOCK(ifa);
continue;
}
if (it6->ia6_ndpr == NULL || it6->ia6_ndpr != ia6->ia6_ndpr) {
IFA_UNLOCK(ifa);
continue;
}
if ((it6->ia6_flags & IN6_IFF_TEMPORARY) != 0 &&
!IFA6_IS_DEPRECATED(it6)) {
IFA_UNLOCK(ifa);
if (public_ifa6 != NULL)
IFA_REMREF(&public_ifa6->ia_ifa);
public_ifa6 = NULL;
break;
}
if (!IFA6_IS_DEPRECATED(it6)) {
IFA_ADDREF_LOCKED(ifa);
IFA_UNLOCK(ifa);
if (public_ifa6 != NULL)
IFA_REMREF(&public_ifa6->ia_ifa);
public_ifa6 = it6;
} else {
IFA_UNLOCK(ifa);
}
}
ifnet_lock_done(ifp);
if (public_ifa6 != NULL) {
int e;
if ((e = in6_tmpifadd(public_ifa6, 0, M_WAITOK)) != 0) {
log(LOG_NOTICE, "regen_tmpaddr: failed to create a new"
" tmp addr,errno=%d\n", e);
IFA_REMREF(&public_ifa6->ia_ifa);
return(-1);
}
IFA_REMREF(&public_ifa6->ia_ifa);
return(0);
}
return(-1);
}
void
nd6_purge(
struct ifnet *ifp)
{
struct llinfo_nd6 *ln;
struct nd_defrouter *dr, *ndr;
struct nd_prefix *pr, *npr;
lck_mtx_lock(nd6_mutex);
if ((dr = TAILQ_FIRST(&nd_defrouter)) != NULL) {
for (dr = TAILQ_NEXT(dr, dr_entry); dr; dr = ndr) {
ndr = TAILQ_NEXT(dr, dr_entry);
if (dr->stateflags & NDDRF_INSTALLED)
continue;
if (dr->ifp == ifp)
defrtrlist_del(dr);
}
dr = TAILQ_FIRST(&nd_defrouter);
if (dr->ifp == ifp)
defrtrlist_del(dr);
}
for (dr = TAILQ_FIRST(&nd_defrouter); dr; dr = ndr) {
ndr = TAILQ_NEXT(dr, dr_entry);
if (!(dr->stateflags & NDDRF_INSTALLED))
continue;
if (dr->ifp == ifp)
defrtrlist_del(dr);
}
for (pr = nd_prefix.lh_first; pr; pr = npr) {
npr = pr->ndpr_next;
NDPR_LOCK(pr);
if (pr->ndpr_ifp == ifp) {
pr->ndpr_addrcnt = 0;
NDPR_ADDREF_LOCKED(pr);
prelist_remove(pr);
NDPR_UNLOCK(pr);
NDPR_REMREF(pr);
} else {
NDPR_UNLOCK(pr);
}
}
lck_mtx_unlock(nd6_mutex);
if (nd6_defifindex == ifp->if_index) {
nd6_setdefaultiface(0);
}
if (ip6_doscopedroute || !ip6_forwarding) {
lck_mtx_lock(nd6_mutex);
defrouter_select(ifp);
lck_mtx_unlock(nd6_mutex);
}
again:
lck_mtx_lock(rnh_lock);
ln = llinfo_nd6.ln_next;
while (ln != NULL && ln != &llinfo_nd6) {
struct rtentry *rt;
struct llinfo_nd6 *nln;
nln = ln->ln_next;
rt = ln->ln_rt;
RT_LOCK(rt);
if (rt->rt_gateway != NULL &&
rt->rt_gateway->sa_family == AF_LINK &&
SDL(rt->rt_gateway)->sdl_index == ifp->if_index) {
RT_UNLOCK(rt);
lck_mtx_unlock(rnh_lock);
nd6_free(rt);
lck_mtx_assert(rnh_lock, LCK_MTX_ASSERT_NOTOWNED);
goto again;
} else {
RT_UNLOCK(rt);
}
ln = nln;
}
lck_mtx_unlock(rnh_lock);
}
struct rtentry *
nd6_lookup(
struct in6_addr *addr6,
int create,
struct ifnet *ifp,
int rt_locked)
{
struct rtentry *rt;
struct sockaddr_in6 sin6;
unsigned int ifscope;
bzero(&sin6, sizeof(sin6));
sin6.sin6_len = sizeof(struct sockaddr_in6);
sin6.sin6_family = AF_INET6;
sin6.sin6_addr = *addr6;
ifscope = (ifp != NULL) ? ifp->if_index : IFSCOPE_NONE;
if (rt_locked) {
lck_mtx_assert(rnh_lock, LCK_MTX_ASSERT_OWNED);
rt = rtalloc1_scoped_locked((struct sockaddr *)&sin6,
create, 0, ifscope);
} else {
rt = rtalloc1_scoped((struct sockaddr *)&sin6,
create, 0, ifscope);
}
if (rt != NULL) {
RT_LOCK(rt);
if ((rt->rt_flags & RTF_LLINFO) == 0) {
if (create) {
RT_UNLOCK(rt);
if (rt_locked)
rtfree_locked(rt);
else
rtfree(rt);
rt = NULL;
}
}
}
if (rt == NULL) {
if (create && ifp) {
struct ifaddr *ifa;
u_int32_t ifa_flags;
int e;
ifa = ifaof_ifpforaddr((struct sockaddr *)&sin6, ifp);
if (ifa == NULL)
return(NULL);
if (!rt_locked)
lck_mtx_lock(rnh_lock);
IFA_LOCK_SPIN(ifa);
ifa_flags = ifa->ifa_flags;
IFA_UNLOCK(ifa);
if ((e = rtrequest_scoped_locked(RTM_ADD,
(struct sockaddr *)&sin6, ifa->ifa_addr,
(struct sockaddr *)&all1_sa,
(ifa_flags | RTF_HOST | RTF_LLINFO) &
~RTF_CLONING, &rt, ifscope)) != 0) {
if (e != EEXIST)
log(LOG_ERR, "%s: failed to add route "
"for a neighbor(%s), errno=%d\n",
__func__, ip6_sprintf(addr6), e);
}
if (!rt_locked)
lck_mtx_unlock(rnh_lock);
IFA_REMREF(ifa);
if (rt == NULL)
return(NULL);
RT_LOCK(rt);
if (rt->rt_llinfo) {
struct llinfo_nd6 *ln = rt->rt_llinfo;
ln->ln_state = ND6_LLINFO_NOSTATE;
}
} else {
return(NULL);
}
}
RT_LOCK_ASSERT_HELD(rt);
if ((rt->rt_flags & RTF_GATEWAY) || (rt->rt_flags & RTF_LLINFO) == 0 ||
rt->rt_gateway->sa_family != AF_LINK || rt->rt_llinfo == NULL ||
(ifp && rt->rt_ifa->ifa_ifp != ifp &&
!(rt->rt_flags & RTF_PROXY))) {
RT_REMREF_LOCKED(rt);
RT_UNLOCK(rt);
if (create) {
log(LOG_DEBUG, "%s: failed to lookup %s "
"(if = %s)\n", __func__, ip6_sprintf(addr6),
ifp ? if_name(ifp) : "unspec");
}
return(NULL);
}
return(rt);
}
static int
nd6_is_new_addr_neighbor(
struct sockaddr_in6 *addr,
struct ifnet *ifp)
{
struct nd_prefix *pr;
struct ifaddr *dstaddr;
lck_mtx_assert(nd6_mutex, LCK_MTX_ASSERT_OWNED);
if (IN6_IS_ADDR_LINKLOCAL(&addr->sin6_addr)) {
struct sockaddr_in6 sin6_copy;
u_int32_t zone;
sin6_copy = *addr;
if (sa6_recoverscope(&sin6_copy, FALSE))
return (0);
if (in6_setscope(&sin6_copy.sin6_addr, ifp, &zone))
return (0);
if (sin6_copy.sin6_scope_id == zone)
return (1);
else
return (0);
}
for (pr = nd_prefix.lh_first; pr; pr = pr->ndpr_next) {
NDPR_LOCK(pr);
if (pr->ndpr_ifp != ifp) {
NDPR_UNLOCK(pr);
continue;
}
if (!(pr->ndpr_stateflags & NDPRF_ONLINK)) {
NDPR_UNLOCK(pr);
continue;
}
if (IN6_ARE_MASKED_ADDR_EQUAL(&pr->ndpr_prefix.sin6_addr,
&addr->sin6_addr, &pr->ndpr_mask)) {
NDPR_UNLOCK(pr);
return (1);
}
NDPR_UNLOCK(pr);
}
dstaddr = ifa_ifwithdstaddr((struct sockaddr *)addr);
if (dstaddr != NULL) {
if (dstaddr->ifa_ifp == ifp) {
IFA_REMREF(dstaddr);
return (1);
}
IFA_REMREF(dstaddr);
dstaddr = NULL;
}
if (!ip6_doscopedroute && !ip6_forwarding &&
TAILQ_FIRST(&nd_defrouter) == NULL &&
nd6_defifindex == ifp->if_index) {
return (1);
}
return (0);
}
int
nd6_is_addr_neighbor(struct sockaddr_in6 *addr, struct ifnet *ifp, int rt_locked)
{
struct rtentry *rt;
lck_mtx_assert(nd6_mutex, LCK_MTX_ASSERT_NOTOWNED);
lck_mtx_lock(nd6_mutex);
if (nd6_is_new_addr_neighbor(addr, ifp)) {
lck_mtx_unlock(nd6_mutex);
return (1);
}
lck_mtx_unlock(nd6_mutex);
if ((rt = nd6_lookup(&addr->sin6_addr, 0, ifp, rt_locked)) != NULL) {
RT_LOCK_ASSERT_HELD(rt);
RT_REMREF_LOCKED(rt);
RT_UNLOCK(rt);
return (1);
}
return (0);
}
void
nd6_free(
struct rtentry *rt)
{
struct llinfo_nd6 *ln;
struct in6_addr in6;
struct nd_defrouter *dr;
lck_mtx_assert(rnh_lock, LCK_MTX_ASSERT_NOTOWNED);
RT_LOCK_ASSERT_NOTHELD(rt);
lck_mtx_lock(nd6_mutex);
RT_LOCK(rt);
RT_ADDREF_LOCKED(rt);
ln = rt->rt_llinfo;
in6 = ((struct sockaddr_in6 *)(void *)rt_key(rt))->sin6_addr;
rt->rt_flags |= RTF_CONDEMNED;
if (ip6_doscopedroute || !ip6_forwarding) {
dr = defrouter_lookup(&((struct sockaddr_in6 *)(void *)
rt_key(rt))->sin6_addr, rt->rt_ifp);
if ((ln && ln->ln_router) || dr) {
RT_UNLOCK(rt);
lck_mtx_unlock(nd6_mutex);
rt6_flush(&in6, rt->rt_ifp);
lck_mtx_lock(nd6_mutex);
} else {
RT_UNLOCK(rt);
}
if (dr) {
NDDR_REMREF(dr);
RT_LOCK_SPIN(rt);
ln->ln_state = ND6_LLINFO_INCOMPLETE;
RT_UNLOCK(rt);
pfxlist_onlink_check();
defrouter_select(rt->rt_ifp);
}
RT_LOCK_ASSERT_NOTHELD(rt);
} else {
RT_UNLOCK(rt);
}
lck_mtx_unlock(nd6_mutex);
(void) rtrequest(RTM_DELETE, rt_key(rt), (struct sockaddr *)0,
rt_mask(rt), 0, (struct rtentry **)0);
rtfree(rt);
}
void
nd6_nud_hint(
struct rtentry *rt,
struct in6_addr *dst6,
int force)
{
struct llinfo_nd6 *ln;
struct timeval timenow;
getmicrotime(&timenow);
if (!rt) {
if (!dst6)
return;
if ((rt = nd6_lookup(dst6, 0, NULL, 0)) == NULL)
return;
RT_LOCK_ASSERT_HELD(rt);
} else {
RT_LOCK(rt);
RT_ADDREF_LOCKED(rt);
}
if ((rt->rt_flags & RTF_GATEWAY) != 0 ||
(rt->rt_flags & RTF_LLINFO) == 0 ||
!rt->rt_llinfo || !rt->rt_gateway ||
rt->rt_gateway->sa_family != AF_LINK) {
goto done;
}
ln = rt->rt_llinfo;
if (ln->ln_state < ND6_LLINFO_REACHABLE)
goto done;
if (!force) {
ln->ln_byhint++;
if (ln->ln_byhint > nd6_maxnudhint)
goto done;
}
ln->ln_state = ND6_LLINFO_REACHABLE;
if (ln->ln_expire) {
struct nd_ifinfo *ndi;
lck_rw_lock_shared(nd_if_rwlock);
ndi = ND_IFINFO(rt->rt_ifp);
VERIFY(ndi != NULL && ndi->initialized);
lck_mtx_lock(&ndi->lock);
ln->ln_expire = timenow.tv_sec + ndi->reachable;
lck_mtx_unlock(&ndi->lock);
lck_rw_done(nd_if_rwlock);
}
done:
RT_REMREF_LOCKED(rt);
RT_UNLOCK(rt);
}
void
nd6_rtrequest(
int req,
struct rtentry *rt,
__unused struct sockaddr *sa)
{
struct sockaddr *gate = rt->rt_gateway;
struct llinfo_nd6 *ln = rt->rt_llinfo;
static struct sockaddr_dl null_sdl = {sizeof(null_sdl), AF_LINK, 0, 0, 0, 0, 0,
{0,0,0,0,0,0,0,0,0,0,0,0,} };
struct ifnet *ifp = rt->rt_ifp;
struct ifaddr *ifa;
struct timeval timenow;
lck_mtx_assert(rnh_lock, LCK_MTX_ASSERT_OWNED);
RT_LOCK_ASSERT_HELD(rt);
if ((rt->rt_flags & RTF_GATEWAY))
return;
if (nd6_need_cache(ifp) == 0 && (rt->rt_flags & RTF_HOST) == 0) {
return;
}
if (req == RTM_RESOLVE) {
int no_nd_cache;
if (!nd6_need_cache(ifp)) {
no_nd_cache = 1;
} else {
struct sockaddr_in6 sin6;
rtkey_to_sa6(rt, &sin6);
RT_ADDREF_LOCKED(rt);
RT_UNLOCK(rt);
no_nd_cache = !nd6_is_addr_neighbor(&sin6, ifp, 1);
RT_LOCK(rt);
RT_REMREF_LOCKED(rt);
}
if (no_nd_cache) {
rt->rt_flags &= ~RTF_LLINFO;
return;
}
}
getmicrotime(&timenow);
switch (req) {
case RTM_ADD:
if ((rt->rt_flags & RTF_CLONING) ||
((rt->rt_flags & RTF_LLINFO) && ln == NULL)) {
if (rt_setgate(rt, rt_key(rt),
(struct sockaddr *)&null_sdl) == 0) {
gate = rt->rt_gateway;
SDL(gate)->sdl_type = ifp->if_type;
SDL(gate)->sdl_index = ifp->if_index;
if (ln != NULL)
ln->ln_expire =
(ifp->if_eflags & IFEF_IPV6_ND6ALT)
? 0 : MAX(timenow.tv_sec, 1);
}
if ((rt->rt_flags & RTF_CLONING))
break;
}
case RTM_RESOLVE:
if ((ifp->if_flags & (IFF_POINTOPOINT | IFF_LOOPBACK)) == 0) {
if (gate->sa_family != AF_LINK ||
gate->sa_len < sizeof(null_sdl)) {
if (req == RTM_RESOLVE) {
log(LOG_DEBUG,
"nd6_rtrequest: bad gateway "
"value: %s\n", if_name(ifp));
}
break;
}
SDL(gate)->sdl_type = ifp->if_type;
SDL(gate)->sdl_index = ifp->if_index;
}
if (ln != NULL)
break;
rt->rt_llinfo = ln = nd6_llinfo_alloc();
if (ln == NULL) {
log(LOG_DEBUG, "nd6_rtrequest: malloc failed\n");
break;
}
rt->rt_llinfo_get_ri = nd6_llinfo_get_ri;
rt->rt_llinfo_get_iflri = nd6_llinfo_get_iflri;
rt->rt_llinfo_purge = nd6_llinfo_purge;
rt->rt_llinfo_free = nd6_llinfo_free;
nd6_inuse++;
nd6_allocated++;
Bzero(ln, sizeof(*ln));
ln->ln_rt = rt;
if (req == RTM_ADD) {
ln->ln_state = ND6_LLINFO_REACHABLE;
ln->ln_byhint = 0;
} else {
ln->ln_state = ND6_LLINFO_NOSTATE;
ln->ln_expire = (ifp->if_eflags & IFEF_IPV6_ND6ALT)
? 0 : MAX(timenow.tv_sec, 1);
}
rt->rt_flags |= RTF_LLINFO;
LN_INSERTHEAD(ln);
if (ip6_neighborgcthresh >= 0 &&
nd6_inuse >= ip6_neighborgcthresh) {
int i;
for (i = 0; i < 10 && llinfo_nd6.ln_prev != ln; i++) {
struct llinfo_nd6 *ln_end = llinfo_nd6.ln_prev;
struct rtentry *rt_end = ln_end->ln_rt;
RT_LOCK(rt_end);
LN_DEQUEUE(ln_end);
LN_INSERTHEAD(ln_end);
if (ln_end->ln_expire == 0) {
RT_UNLOCK(rt_end);
continue;
}
if (ln_end->ln_state > ND6_LLINFO_INCOMPLETE)
ln_end->ln_state = ND6_LLINFO_STALE;
else
ln_end->ln_state = ND6_LLINFO_PURGE;
ln_end->ln_expire = timenow.tv_sec;
RT_UNLOCK(rt_end);
}
}
ifa = (struct ifaddr *)in6ifa_ifpwithaddr(rt->rt_ifp,
&SIN6(rt_key(rt))->sin6_addr);
if (ifa) {
caddr_t macp = nd6_ifptomac(ifp);
ln->ln_expire = 0;
ln->ln_state = ND6_LLINFO_REACHABLE;
ln->ln_byhint = 0;
if (macp) {
Bcopy(macp, LLADDR(SDL(gate)), ifp->if_addrlen);
SDL(gate)->sdl_alen = ifp->if_addrlen;
}
if (nd6_useloopback) {
if (rt->rt_ifp != lo_ifp) {
if (rt->rt_llinfo_purge != NULL)
rt->rt_llinfo_purge(rt);
if (rt->rt_if_ref_fn != NULL) {
rt->rt_if_ref_fn(lo_ifp, 1);
rt->rt_if_ref_fn(rt->rt_ifp, -1);
}
}
rt->rt_ifp = lo_ifp;
if (ifa != rt->rt_ifa) {
rtsetifa(rt, ifa);
}
}
IFA_REMREF(ifa);
} else if (rt->rt_flags & RTF_ANNOUNCE) {
ln->ln_expire = 0;
ln->ln_state = ND6_LLINFO_REACHABLE;
ln->ln_byhint = 0;
if (ifp->if_flags & IFF_MULTICAST) {
struct in6_addr llsol;
struct in6_multi *in6m;
int error;
llsol = SIN6(rt_key(rt))->sin6_addr;
llsol.s6_addr32[0] = IPV6_ADDR_INT32_MLL;
llsol.s6_addr32[1] = 0;
llsol.s6_addr32[2] = htonl(1);
llsol.s6_addr8[12] = 0xff;
if (in6_setscope(&llsol, ifp, NULL))
break;
error = in6_mc_join(ifp, &llsol, NULL, &in6m, 0);
if (error) {
nd6log((LOG_ERR, "%s: failed to join "
"%s (errno=%d)\n", if_name(ifp),
ip6_sprintf(&llsol), error));
} else {
IN6M_REMREF(in6m);
}
}
}
break;
case RTM_DELETE:
if (ln == NULL)
break;
if ((rt->rt_flags & RTF_ANNOUNCE) != 0 &&
(ifp->if_flags & IFF_MULTICAST) != 0) {
struct in6_addr llsol;
struct in6_multi *in6m;
llsol = SIN6(rt_key(rt))->sin6_addr;
llsol.s6_addr32[0] = IPV6_ADDR_INT32_MLL;
llsol.s6_addr32[1] = 0;
llsol.s6_addr32[2] = htonl(1);
llsol.s6_addr8[12] = 0xff;
if (in6_setscope(&llsol, ifp, NULL) == 0) {
in6_multihead_lock_shared();
IN6_LOOKUP_MULTI(&llsol, ifp, in6m);
in6_multihead_lock_done();
if (in6m != NULL) {
in6_mc_leave(in6m, NULL);
IN6M_REMREF(in6m);
}
}
}
nd6_inuse--;
if (ln->ln_flags & ND6_LNF_IN_USE)
LN_DEQUEUE(ln);
if (rt->rt_llinfo_purge != NULL)
rt->rt_llinfo_purge(rt);
rt->rt_flags &= ~RTF_LLINFO;
if (ln->ln_hold != NULL) {
m_freem(ln->ln_hold);
ln->ln_hold = NULL;
}
}
}
static int
nd6_siocgdrlst(void *data, int data_is_64)
{
struct in6_drlist_32 *drl_32;
struct nd_defrouter *dr;
int i = 0;
lck_mtx_assert(nd6_mutex, LCK_MTX_ASSERT_OWNED);
dr = TAILQ_FIRST(&nd_defrouter);
if (data_is_64) {
struct in6_drlist_64 *drl_64;
drl_64 = _MALLOC(sizeof (*drl_64), M_TEMP, M_WAITOK|M_ZERO);
if (drl_64 == NULL)
return (ENOMEM);
bcopy(data, drl_64, sizeof (drl_64->ifname));
while (dr && i < DRLSTSIZ) {
drl_64->defrouter[i].rtaddr = dr->rtaddr;
if (IN6_IS_ADDR_LINKLOCAL(&drl_64->defrouter[i].rtaddr)) {
drl_64->defrouter[i].rtaddr.s6_addr16[1] = 0;
} else {
log(LOG_ERR,
"default router list contains a "
"non-linklocal address(%s)\n",
ip6_sprintf(&drl_64->defrouter[i].rtaddr));
}
drl_64->defrouter[i].flags = dr->flags;
drl_64->defrouter[i].rtlifetime = dr->rtlifetime;
drl_64->defrouter[i].expire = dr->expire;
drl_64->defrouter[i].if_index = dr->ifp->if_index;
i++;
dr = TAILQ_NEXT(dr, dr_entry);
}
bcopy(drl_64, data, sizeof (*drl_64));
_FREE(drl_64, M_TEMP);
return (0);
}
drl_32 = _MALLOC(sizeof (*drl_32), M_TEMP, M_WAITOK|M_ZERO);
if (drl_32 == NULL)
return (ENOMEM);
bcopy(data, drl_32, sizeof (drl_32->ifname));
while (dr && i < DRLSTSIZ) {
drl_32->defrouter[i].rtaddr = dr->rtaddr;
if (IN6_IS_ADDR_LINKLOCAL(&drl_32->defrouter[i].rtaddr)) {
drl_32->defrouter[i].rtaddr.s6_addr16[1] = 0;
} else {
log(LOG_ERR,
"default router list contains a "
"non-linklocal address(%s)\n",
ip6_sprintf(&drl_32->defrouter[i].rtaddr));
}
drl_32->defrouter[i].flags = dr->flags;
drl_32->defrouter[i].rtlifetime = dr->rtlifetime;
drl_32->defrouter[i].expire = dr->expire;
drl_32->defrouter[i].if_index = dr->ifp->if_index;
i++;
dr = TAILQ_NEXT(dr, dr_entry);
}
bcopy(drl_32, data, sizeof (*drl_32));
_FREE(drl_32, M_TEMP);
return (0);
}
static int
nd6_siocgprlst(void *data, int data_is_64)
{
struct in6_prlist_32 *prl_32;
struct nd_prefix *pr;
int i = 0;
lck_mtx_assert(nd6_mutex, LCK_MTX_ASSERT_OWNED);
pr = nd_prefix.lh_first;
if (data_is_64) {
struct in6_prlist_64 *prl_64;
prl_64 = _MALLOC(sizeof (*prl_64), M_TEMP, M_WAITOK|M_ZERO);
if (prl_64 == NULL)
return (ENOMEM);
bcopy(data, prl_64, sizeof (prl_64->ifname));
while (pr && i < PRLSTSIZ) {
struct nd_pfxrouter *pfr;
int j;
NDPR_LOCK(pr);
(void) in6_embedscope(&prl_64->prefix[i].prefix,
&pr->ndpr_prefix, NULL, NULL, NULL);
prl_64->prefix[i].raflags = pr->ndpr_raf;
prl_64->prefix[i].prefixlen = pr->ndpr_plen;
prl_64->prefix[i].vltime = pr->ndpr_vltime;
prl_64->prefix[i].pltime = pr->ndpr_pltime;
prl_64->prefix[i].if_index = pr->ndpr_ifp->if_index;
prl_64->prefix[i].expire = pr->ndpr_expire;
pfr = pr->ndpr_advrtrs.lh_first;
j = 0;
while (pfr) {
if (j < DRLSTSIZ) {
#define RTRADDR prl_64->prefix[i].advrtr[j]
RTRADDR = pfr->router->rtaddr;
if (IN6_IS_ADDR_LINKLOCAL(&RTRADDR)) {
RTRADDR.s6_addr16[1] = 0;
} else {
log(LOG_ERR,
"a router(%s) advertises "
"a prefix with "
"non-link local address\n",
ip6_sprintf(&RTRADDR));
}
#undef RTRADDR
}
j++;
pfr = pfr->pfr_next;
}
prl_64->prefix[i].advrtrs = j;
prl_64->prefix[i].origin = PR_ORIG_RA;
NDPR_UNLOCK(pr);
i++;
pr = pr->ndpr_next;
}
bcopy(prl_64, data, sizeof (*prl_64));
_FREE(prl_64, M_TEMP);
return (0);
}
prl_32 = _MALLOC(sizeof (*prl_32), M_TEMP, M_WAITOK|M_ZERO);
if (prl_32 == NULL)
return (ENOMEM);
bcopy(data, prl_32, sizeof (prl_32->ifname));
while (pr && i < PRLSTSIZ) {
struct nd_pfxrouter *pfr;
int j;
NDPR_LOCK(pr);
(void) in6_embedscope(&prl_32->prefix[i].prefix,
&pr->ndpr_prefix, NULL, NULL, NULL);
prl_32->prefix[i].raflags = pr->ndpr_raf;
prl_32->prefix[i].prefixlen = pr->ndpr_plen;
prl_32->prefix[i].vltime = pr->ndpr_vltime;
prl_32->prefix[i].pltime = pr->ndpr_pltime;
prl_32->prefix[i].if_index = pr->ndpr_ifp->if_index;
prl_32->prefix[i].expire = pr->ndpr_expire;
pfr = pr->ndpr_advrtrs.lh_first;
j = 0;
while (pfr) {
if (j < DRLSTSIZ) {
#define RTRADDR prl_32->prefix[i].advrtr[j]
RTRADDR = pfr->router->rtaddr;
if (IN6_IS_ADDR_LINKLOCAL(&RTRADDR)) {
RTRADDR.s6_addr16[1] = 0;
} else {
log(LOG_ERR,
"a router(%s) advertises "
"a prefix with "
"non-link local address\n",
ip6_sprintf(&RTRADDR));
}
#undef RTRADDR
}
j++;
pfr = pfr->pfr_next;
}
prl_32->prefix[i].advrtrs = j;
prl_32->prefix[i].origin = PR_ORIG_RA;
NDPR_UNLOCK(pr);
i++;
pr = pr->ndpr_next;
}
bcopy(prl_32, data, sizeof (*prl_32));
_FREE(prl_32, M_TEMP);
return (0);
}
int
nd6_ioctl(u_long cmd, caddr_t data, struct ifnet *ifp)
{
struct nd_defrouter *dr;
struct nd_prefix *pr;
struct rtentry *rt;
int i = ifp->if_index, error = 0;
switch (cmd) {
case SIOCGDRLST_IN6_32:
case SIOCGDRLST_IN6_64:
lck_mtx_lock(nd6_mutex);
error = nd6_siocgdrlst(data, cmd == SIOCGDRLST_IN6_64);
lck_mtx_unlock(nd6_mutex);
break;
case SIOCGPRLST_IN6_32:
case SIOCGPRLST_IN6_64:
lck_mtx_lock(nd6_mutex);
error = nd6_siocgprlst(data, cmd == SIOCGPRLST_IN6_64);
lck_mtx_unlock(nd6_mutex);
break;
case OSIOCGIFINFO_IN6:
case SIOCGIFINFO_IN6: {
u_int32_t linkmtu;
struct in6_ondireq *ondi = (struct in6_ondireq *)(void *)data;
struct nd_ifinfo *ndi;
lck_rw_lock_shared(nd_if_rwlock);
ndi = ND_IFINFO(ifp);
if (!nd_ifinfo || i >= nd_ifinfo_indexlim ||
!ndi->initialized) {
lck_rw_done(nd_if_rwlock);
error = EINVAL;
break;
}
lck_mtx_lock(&ndi->lock);
linkmtu = IN6_LINKMTU(ifp);
bcopy(&linkmtu, &ondi->ndi.linkmtu, sizeof (linkmtu));
bcopy(&nd_ifinfo[i].maxmtu, &ondi->ndi.maxmtu,
sizeof (u_int32_t));
bcopy(&nd_ifinfo[i].basereachable, &ondi->ndi.basereachable,
sizeof (u_int32_t));
bcopy(&nd_ifinfo[i].reachable, &ondi->ndi.reachable,
sizeof (u_int32_t));
bcopy(&nd_ifinfo[i].retrans, &ondi->ndi.retrans,
sizeof (u_int32_t));
bcopy(&nd_ifinfo[i].flags, &ondi->ndi.flags,
sizeof (u_int32_t));
bcopy(&nd_ifinfo[i].recalctm, &ondi->ndi.recalctm,
sizeof (int));
ondi->ndi.chlim = nd_ifinfo[i].chlim;
ondi->ndi.receivedra = 0;
lck_mtx_unlock(&ndi->lock);
lck_rw_done(nd_if_rwlock);
break;
}
case SIOCSIFINFO_FLAGS: {
struct in6_ndireq *cndi = (struct in6_ndireq *)(void *)data;
u_int32_t oflags, flags;
struct nd_ifinfo *ndi;
lck_rw_lock_shared(nd_if_rwlock);
ndi = ND_IFINFO(ifp);
if (!nd_ifinfo || i >= nd_ifinfo_indexlim ||
!ndi->initialized) {
lck_rw_done(nd_if_rwlock);
error = EINVAL;
break;
}
lck_mtx_lock(&ndi->lock);
oflags = nd_ifinfo[i].flags;
bcopy(&cndi->ndi.flags, &nd_ifinfo[i].flags, sizeof (flags));
flags = nd_ifinfo[i].flags;
lck_mtx_unlock(&ndi->lock);
lck_rw_done(nd_if_rwlock);
if (oflags == flags)
break;
error = nd6_setifinfo(ifp, oflags, flags);
break;
}
case SIOCSNDFLUSH_IN6:
lck_mtx_lock(nd6_mutex);
defrouter_reset();
defrouter_select(ifp);
lck_mtx_unlock(nd6_mutex);
break;
case SIOCSPFXFLUSH_IN6: {
struct nd_prefix *next;
lck_mtx_lock(nd6_mutex);
for (pr = nd_prefix.lh_first; pr; pr = next) {
struct in6_ifaddr *ia;
next = pr->ndpr_next;
NDPR_LOCK(pr);
if (IN6_IS_ADDR_LINKLOCAL(&pr->ndpr_prefix.sin6_addr)) {
NDPR_UNLOCK(pr);
continue;
}
if (ifp != lo_ifp && pr->ndpr_ifp != ifp) {
NDPR_UNLOCK(pr);
continue;
}
NDPR_ADDREF_LOCKED(pr);
NDPR_UNLOCK(pr);
lck_rw_lock_exclusive(&in6_ifaddr_rwlock);
ia = in6_ifaddrs;
while (ia != NULL) {
IFA_LOCK(&ia->ia_ifa);
if ((ia->ia6_flags & IN6_IFF_AUTOCONF) == 0) {
IFA_UNLOCK(&ia->ia_ifa);
ia = ia->ia_next;
continue;
}
if (ia->ia6_ndpr == pr) {
IFA_ADDREF_LOCKED(&ia->ia_ifa);
IFA_UNLOCK(&ia->ia_ifa);
lck_rw_done(&in6_ifaddr_rwlock);
lck_mtx_unlock(nd6_mutex);
in6_purgeaddr(&ia->ia_ifa);
IFA_REMREF(&ia->ia_ifa);
lck_mtx_lock(nd6_mutex);
lck_rw_lock_exclusive(&in6_ifaddr_rwlock);
ia = in6_ifaddrs;
next = nd_prefix.lh_first;
continue;
}
IFA_UNLOCK(&ia->ia_ifa);
ia = ia->ia_next;
}
lck_rw_done(&in6_ifaddr_rwlock);
NDPR_LOCK(pr);
prelist_remove(pr);
NDPR_UNLOCK(pr);
if (pr == next)
next = NULL;
NDPR_REMREF(pr);
}
lck_mtx_unlock(nd6_mutex);
break;
}
case SIOCSRTRFLUSH_IN6: {
struct nd_defrouter *next;
lck_mtx_lock(nd6_mutex);
if ((dr = TAILQ_FIRST(&nd_defrouter)) != NULL) {
for (dr = TAILQ_NEXT(dr, dr_entry); dr; dr = next) {
next = TAILQ_NEXT(dr, dr_entry);
if (ifp == lo_ifp || dr->ifp == ifp)
defrtrlist_del(dr);
}
if (ifp == lo_ifp ||
TAILQ_FIRST(&nd_defrouter)->ifp == ifp)
defrtrlist_del(TAILQ_FIRST(&nd_defrouter));
}
lck_mtx_unlock(nd6_mutex);
break;
}
case SIOCGNBRINFO_IN6_32: {
struct llinfo_nd6 *ln;
struct in6_nbrinfo_32 nbi_32;
struct in6_addr nb_addr;
bcopy(data, &nbi_32, sizeof (nbi_32));
nb_addr = nbi_32.addr;
if (IN6_IS_ADDR_LINKLOCAL(&nbi_32.addr) ||
IN6_IS_ADDR_MC_LINKLOCAL(&nbi_32.addr)) {
u_int16_t *idp =
(u_int16_t *)(void *)&nb_addr.s6_addr[2];
if (*idp == 0)
*idp = htons(ifp->if_index);
}
if ((rt = nd6_lookup(&nb_addr, 0, ifp, 0)) == NULL) {
error = EINVAL;
break;
}
RT_LOCK_ASSERT_HELD(rt);
ln = rt->rt_llinfo;
nbi_32.state = ln->ln_state;
nbi_32.asked = ln->ln_asked;
nbi_32.isrouter = ln->ln_router;
nbi_32.expire = ln->ln_expire;
RT_REMREF_LOCKED(rt);
RT_UNLOCK(rt);
bcopy(&nbi_32, data, sizeof (nbi_32));
break;
}
case SIOCGNBRINFO_IN6_64: {
struct llinfo_nd6 *ln;
struct in6_nbrinfo_64 nbi_64;
struct in6_addr nb_addr;
bcopy(data, &nbi_64, sizeof (nbi_64));
nb_addr = nbi_64.addr;
if (IN6_IS_ADDR_LINKLOCAL(&nbi_64.addr) ||
IN6_IS_ADDR_MC_LINKLOCAL(&nbi_64.addr)) {
u_int16_t *idp =
(u_int16_t *)(void *)&nb_addr.s6_addr[2];
if (*idp == 0)
*idp = htons(ifp->if_index);
}
if ((rt = nd6_lookup(&nb_addr, 0, ifp, 0)) == NULL) {
error = EINVAL;
break;
}
RT_LOCK_ASSERT_HELD(rt);
ln = rt->rt_llinfo;
nbi_64.state = ln->ln_state;
nbi_64.asked = ln->ln_asked;
nbi_64.isrouter = ln->ln_router;
nbi_64.expire = ln->ln_expire;
RT_REMREF_LOCKED(rt);
RT_UNLOCK(rt);
bcopy(&nbi_64, data, sizeof (nbi_64));
break;
}
case SIOCGDEFIFACE_IN6_32:
case SIOCGDEFIFACE_IN6_64: {
struct in6_ndifreq_64 *ndif_64 =
(struct in6_ndifreq_64 *)(void *)data;
struct in6_ndifreq_32 *ndif_32 =
(struct in6_ndifreq_32 *)(void *)data;
if (cmd == SIOCGDEFIFACE_IN6_64) {
u_int64_t j = nd6_defifindex;
bcopy(&j, &ndif_64->ifindex, sizeof (j));
} else {
bcopy(&nd6_defifindex, &ndif_32->ifindex,
sizeof (u_int32_t));
}
break;
}
case SIOCSDEFIFACE_IN6_32:
case SIOCSDEFIFACE_IN6_64: {
struct in6_ndifreq_64 *ndif_64 =
(struct in6_ndifreq_64 *)(void *)data;
struct in6_ndifreq_32 *ndif_32 =
(struct in6_ndifreq_32 *)(void *)data;
u_int32_t idx;
if (cmd == SIOCSDEFIFACE_IN6_64) {
u_int64_t j;
bcopy(&ndif_64->ifindex, &j, sizeof (j));
idx = (u_int32_t)j;
} else {
bcopy(&ndif_32->ifindex, &idx, sizeof (idx));
}
error = nd6_setdefaultiface(idx);
return (error);
}
}
return (error);
}
void
nd6_cache_lladdr(
struct ifnet *ifp,
struct in6_addr *from,
char *lladdr,
__unused int lladdrlen,
int type,
int code)
{
struct rtentry *rt = NULL;
struct llinfo_nd6 *ln = NULL;
int is_newentry;
struct sockaddr_dl *sdl = NULL;
int do_update;
int olladdr;
int llchange;
int newstate = 0;
struct timeval timenow;
if (ifp == NULL)
panic("ifp == NULL in nd6_cache_lladdr");
if (from == NULL)
panic("from == NULL in nd6_cache_lladdr");
if (IN6_IS_ADDR_UNSPECIFIED(from))
return;
getmicrotime(&timenow);
rt = nd6_lookup(from, 0, ifp, 0);
if (rt == NULL) {
if ((rt = nd6_lookup(from, 1, ifp, 0)) == NULL)
return;
RT_LOCK_ASSERT_HELD(rt);
is_newentry = 1;
} else {
RT_LOCK_ASSERT_HELD(rt);
if (rt->rt_flags & RTF_STATIC) {
RT_REMREF_LOCKED(rt);
RT_UNLOCK(rt);
return;
}
is_newentry = 0;
}
if (rt == NULL)
return;
if ((rt->rt_flags & (RTF_GATEWAY | RTF_LLINFO)) != RTF_LLINFO) {
fail:
RT_UNLOCK(rt);
nd6_free(rt);
rtfree(rt);
return;
}
ln = (struct llinfo_nd6 *)rt->rt_llinfo;
if (ln == NULL)
goto fail;
if (rt->rt_gateway == NULL)
goto fail;
if (rt->rt_gateway->sa_family != AF_LINK)
goto fail;
sdl = SDL(rt->rt_gateway);
olladdr = (sdl->sdl_alen) ? 1 : 0;
if (olladdr && lladdr) {
if (bcmp(lladdr, LLADDR(sdl), ifp->if_addrlen))
llchange = 1;
else
llchange = 0;
} else
llchange = 0;
if (lladdr) {
sdl->sdl_alen = ifp->if_addrlen;
bcopy(lladdr, LLADDR(sdl), ifp->if_addrlen);
nd6_llreach_alloc(rt, ifp, LLADDR(sdl), sdl->sdl_alen, FALSE);
}
if (!is_newentry) {
if ((!olladdr && lladdr != NULL) ||
(olladdr && lladdr != NULL && llchange)) {
do_update = 1;
newstate = ND6_LLINFO_STALE;
} else
do_update = 0;
} else {
do_update = 1;
if (lladdr == NULL)
newstate = ND6_LLINFO_NOSTATE;
else
newstate = ND6_LLINFO_STALE;
}
if (do_update) {
ln->ln_state = newstate;
if (ln->ln_state == ND6_LLINFO_STALE) {
struct mbuf *m = ln->ln_hold;
ln->ln_expire = timenow.tv_sec + nd6_gctimer;
ln->ln_hold = NULL;
if (m != NULL) {
struct sockaddr_in6 sin6;
rtkey_to_sa6(rt, &sin6);
RT_UNLOCK(rt);
nd6_output(ifp, ifp, m, &sin6, rt, NULL);
RT_LOCK(rt);
}
} else if (ln->ln_state == ND6_LLINFO_INCOMPLETE) {
ln->ln_expire = timenow.tv_sec;
}
}
switch (type & 0xff) {
case ND_NEIGHBOR_SOLICIT:
if (is_newentry)
ln->ln_router = 0;
break;
case ND_REDIRECT:
if (code == ND_REDIRECT_ROUTER)
ln->ln_router = 1;
else if (is_newentry)
ln->ln_router = 0;
break;
case ND_ROUTER_SOLICIT:
ln->ln_router = 0;
break;
case ND_ROUTER_ADVERT:
if ((!is_newentry && (olladdr || lladdr)) ||
(is_newentry && lladdr)) {
ln->ln_router = 1;
}
break;
}
if (do_update && ln->ln_router &&
(ip6_doscopedroute || !ip6_forwarding)) {
RT_REMREF_LOCKED(rt);
RT_UNLOCK(rt);
lck_mtx_lock(nd6_mutex);
defrouter_select(ifp);
lck_mtx_unlock(nd6_mutex);
} else {
RT_REMREF_LOCKED(rt);
RT_UNLOCK(rt);
}
}
static void
nd6_slowtimo(
__unused void *ignored_arg)
{
int i;
struct nd_ifinfo *nd6if;
lck_rw_lock_shared(nd_if_rwlock);
for (i = 1; i < if_index + 1; i++) {
if (!nd_ifinfo || i >= nd_ifinfo_indexlim)
break;
nd6if = &nd_ifinfo[i];
if (!nd6if->initialized)
break;
lck_mtx_lock(&nd6if->lock);
if (nd6if->basereachable &&
(nd6if->recalctm -= ND6_SLOWTIMER_INTERVAL) <= 0) {
nd6if->recalctm = nd6_recalc_reachtm_interval;
nd6if->reachable = ND_COMPUTE_RTIME(nd6if->basereachable);
}
lck_mtx_unlock(&nd6if->lock);
}
lck_rw_done(nd_if_rwlock);
timeout(nd6_slowtimo, (caddr_t)0, ND6_SLOWTIMER_INTERVAL * hz);
}
#define senderr(e) { error = (e); goto bad;}
int
nd6_output(struct ifnet *ifp, struct ifnet *origifp, struct mbuf *m0,
struct sockaddr_in6 *dst, struct rtentry *hint0, struct flowadv *adv)
{
struct mbuf *m = m0;
struct rtentry *rt = hint0, *hint = hint0;
struct llinfo_nd6 *ln = NULL;
int error = 0;
struct timeval timenow;
struct rtentry *rtrele = NULL;
struct nd_ifinfo *ndi;
if (rt != NULL) {
RT_LOCK_SPIN(rt);
RT_ADDREF_LOCKED(rt);
}
if (IN6_IS_ADDR_MULTICAST(&dst->sin6_addr) || !nd6_need_cache(ifp)) {
if (rt != NULL)
RT_UNLOCK(rt);
goto sendpkt;
}
if (rt != NULL) {
RT_LOCK_ASSERT_HELD(rt);
if (!(rt->rt_flags & RTF_UP)) {
RT_REMREF_LOCKED(rt);
RT_UNLOCK(rt);
if ((hint = rt = rtalloc1_scoped((struct sockaddr *)dst,
1, 0, ifp->if_index)) != NULL) {
RT_LOCK_SPIN(rt);
if (rt->rt_ifp != ifp) {
RT_UNLOCK(rt);
error = nd6_output(ifp, origifp, m0,
dst, rt, adv);
rtfree(rt);
return (error);
}
} else {
senderr(EHOSTUNREACH);
}
}
if (rt->rt_flags & RTF_GATEWAY) {
struct rtentry *gwrt;
struct in6_ifaddr *ia6 = NULL;
struct sockaddr_in6 gw6;
rtgw_to_sa6(rt, &gw6);
RT_UNLOCK(rt);
if (!nd6_is_addr_neighbor(&gw6, ifp, 0) ||
(ia6 = in6ifa_ifpwithaddr(ifp, &gw6.sin6_addr))) {
if (ia6 != NULL)
IFA_REMREF(&ia6->ia_ifa);
if ((ifp->if_flags & IFF_POINTOPOINT) == 0)
senderr(EHOSTUNREACH);
goto sendpkt;
}
RT_LOCK_SPIN(rt);
gw6 = *((struct sockaddr_in6 *)(void *)rt->rt_gateway);
if (!(rt->rt_flags & RTF_UP)) {
RT_UNLOCK(rt);
senderr(EHOSTUNREACH);
}
if ((gwrt = rt->rt_gwroute) == NULL) {
RT_UNLOCK(rt);
goto lookup;
}
RT_CONVERT_LOCK(rt);
RT_LOCK_SPIN(gwrt);
if (!(gwrt->rt_flags & RTF_UP)) {
rt->rt_gwroute = NULL;
RT_UNLOCK(gwrt);
RT_UNLOCK(rt);
rtfree(gwrt);
lookup:
lck_mtx_lock(rnh_lock);
gwrt = rtalloc1_scoped_locked(
(struct sockaddr *)&gw6, 1, 0,
ifp->if_index);
RT_LOCK(rt);
if (!(rt->rt_flags & RTF_UP) ||
gwrt == NULL || gwrt == rt ||
!equal(SA(&gw6), rt->rt_gateway)) {
if (gwrt == rt) {
RT_REMREF_LOCKED(gwrt);
gwrt = NULL;
}
RT_UNLOCK(rt);
if (gwrt != NULL)
rtfree_locked(gwrt);
lck_mtx_unlock(rnh_lock);
senderr(EHOSTUNREACH);
}
VERIFY(gwrt != NULL);
rt_set_gwroute(rt, rt_key(rt), gwrt);
RT_UNLOCK(rt);
lck_mtx_unlock(rnh_lock);
rtrele = rt;
rt = gwrt;
} else {
RT_ADDREF_LOCKED(gwrt);
RT_UNLOCK(gwrt);
RT_UNLOCK(rt);
rtrele = rt;
rt = gwrt;
}
VERIFY(rt == gwrt);
RT_LOCK_SPIN(hint);
if ((hint->rt_flags & (RTF_WASCLONED | RTF_UP)) ==
(RTF_WASCLONED | RTF_UP)) {
struct rtentry *prt = hint->rt_parent;
VERIFY(prt != NULL);
RT_CONVERT_LOCK(hint);
RT_ADDREF(prt);
RT_UNLOCK(hint);
rt_revalidate_gwroute(prt, rt);
RT_REMREF(prt);
} else {
RT_UNLOCK(hint);
}
RT_LOCK_SPIN(rt);
if (!(rt->rt_flags & RTF_UP)) {
RT_UNLOCK(rt);
rtfree(rt);
rt = NULL;
senderr(EHOSTUNREACH);
}
}
RT_CONVERT_LOCK(rt);
}
if (rt && (rt->rt_flags & RTF_LLINFO) != 0) {
ln = rt->rt_llinfo;
} else {
struct sockaddr_in6 sin6;
sin6 = *dst;
sin6.sin6_scope_id = 0;
if (rt != NULL)
RT_UNLOCK(rt);
if (nd6_is_addr_neighbor(&sin6, ifp, 0)) {
if (rt != NULL) {
if (rt == hint0)
RT_REMREF(rt);
else
rtfree(rt);
}
rt = nd6_lookup(&dst->sin6_addr, 1, ifp, 0);
if (rt != NULL) {
RT_LOCK_ASSERT_HELD(rt);
ln = rt->rt_llinfo;
}
} else if (rt != NULL) {
RT_LOCK(rt);
}
}
if (!ln || !rt) {
if (rt != NULL)
RT_UNLOCK(rt);
lck_rw_lock_shared(nd_if_rwlock);
ndi = ND_IFINFO(ifp);
VERIFY(ndi != NULL && ndi->initialized);
lck_mtx_lock(&ndi->lock);
if ((ifp->if_flags & IFF_POINTOPOINT) == 0 &&
!(ndi->flags & ND6_IFF_PERFORMNUD)) {
lck_mtx_unlock(&ndi->lock);
lck_rw_done(nd_if_rwlock);
log(LOG_DEBUG,
"nd6_output: can't allocate llinfo for %s "
"(ln=%p, rt=%p)\n",
ip6_sprintf(&dst->sin6_addr), ln, rt);
senderr(EIO);
}
lck_mtx_unlock(&ndi->lock);
lck_rw_done(nd_if_rwlock);
goto sendpkt;
}
getmicrotime(&timenow);
if ((ifp->if_flags & IFF_POINTOPOINT) != 0 &&
ln->ln_state < ND6_LLINFO_REACHABLE) {
ln->ln_state = ND6_LLINFO_STALE;
ln->ln_expire = rt_expiry(rt, timenow.tv_sec, nd6_gctimer);
}
if (ln->ln_state == ND6_LLINFO_STALE) {
ln->ln_asked = 0;
ln->ln_state = ND6_LLINFO_DELAY;
ln->ln_expire = rt_expiry(rt, timenow.tv_sec, nd6_delay);
}
if (ln->ln_state > ND6_LLINFO_INCOMPLETE) {
RT_UNLOCK(rt);
lck_mtx_lock(rnh_lock);
RT_LOCK_SPIN(rt);
if (ln->ln_flags & ND6_LNF_IN_USE) {
LN_DEQUEUE(ln);
LN_INSERTHEAD(ln);
}
RT_UNLOCK(rt);
lck_mtx_unlock(rnh_lock);
goto sendpkt;
}
if (ln->ln_state == ND6_LLINFO_NOSTATE)
ln->ln_state = ND6_LLINFO_INCOMPLETE;
if (ln->ln_hold)
m_freem(ln->ln_hold);
ln->ln_hold = m;
if (ln->ln_expire && ln->ln_asked < nd6_mmaxtries &&
ln->ln_expire < timenow.tv_sec) {
ln->ln_asked++;
lck_rw_lock_shared(nd_if_rwlock);
ndi = ND_IFINFO(ifp);
VERIFY(ndi != NULL && ndi->initialized);
lck_mtx_lock(&ndi->lock);
ln->ln_expire = timenow.tv_sec + ndi->retrans / 1000;
lck_mtx_unlock(&ndi->lock);
lck_rw_done(nd_if_rwlock);
RT_UNLOCK(rt);
if (ip6_forwarding)
nd6_prproxy_ns_output(ifp, NULL, &dst->sin6_addr, ln);
else
nd6_ns_output(ifp, NULL, &dst->sin6_addr, ln, 0);
} else {
RT_UNLOCK(rt);
}
lck_mtx_lock(rnh_lock);
RT_LOCK_SPIN(rt);
if (ln->ln_flags & ND6_LNF_IN_USE) {
LN_DEQUEUE(ln);
LN_INSERTHEAD(ln);
}
if (rt == hint0) {
RT_REMREF_LOCKED(rt);
RT_UNLOCK(rt);
} else {
RT_UNLOCK(rt);
rtfree_locked(rt);
}
rt = NULL;
lck_mtx_unlock(rnh_lock);
error = 0;
goto release;
sendpkt:
if (rt != NULL)
RT_LOCK_ASSERT_NOTHELD(rt);
lck_rw_lock_shared(nd_if_rwlock);
ndi = ND_IFINFO(ifp);
VERIFY(ndi != NULL && ndi->initialized);
if (ndi->flags & ND6_IFF_IFDISABLED) {
lck_rw_done(nd_if_rwlock);
error = ENETDOWN;
goto bad;
}
lck_rw_done(nd_if_rwlock);
if ((ifp->if_flags & IFF_LOOPBACK) != 0) {
m->m_pkthdr.rcvif = origifp;
error = dlil_output(origifp, PF_INET6, m, (caddr_t)rt,
(struct sockaddr *)dst, 0, adv);
goto release;
} else {
struct ip6_hdr *ip6 = mtod(m, struct ip6_hdr *);
if ((IN6_IS_ADDR_LOOPBACK(&ip6->ip6_src) ||
IN6_IS_ADDR_LOOPBACK(&ip6->ip6_dst))) {
ip6stat.ip6s_badscope++;
error = ENETUNREACH;
goto bad;
}
}
if (rt != NULL) {
RT_LOCK_SPIN(rt);
if (rt->rt_llinfo != NULL)
nd6_llreach_use(rt->rt_llinfo);
RT_UNLOCK(rt);
}
if (hint && nstat_collect)
nstat_route_tx(hint, 1, m->m_pkthdr.len, 0);
m->m_pkthdr.rcvif = NULL;
error = dlil_output(ifp, PF_INET6, m, (caddr_t)rt,
(struct sockaddr *)dst, 0, adv);
goto release;
bad:
if (m != NULL)
m_freem(m);
release:
if (rt != NULL) {
RT_LOCK_SPIN(rt);
if (rt == hint0) {
RT_REMREF_LOCKED(rt);
RT_UNLOCK(rt);
} else {
RT_UNLOCK(rt);
rtfree(rt);
}
}
if (rtrele != NULL) {
RT_LOCK_SPIN(rtrele);
if (rtrele == hint0) {
RT_REMREF_LOCKED(rtrele);
RT_UNLOCK(rtrele);
} else {
RT_UNLOCK(rtrele);
rtfree(rtrele);
}
}
return (error);
}
#undef senderr
int
nd6_need_cache(
struct ifnet *ifp)
{
switch (ifp->if_type) {
case IFT_ARCNET:
case IFT_ETHER:
case IFT_FDDI:
case IFT_IEEE1394:
case IFT_L2VLAN:
case IFT_IEEE8023ADLAG:
#if IFT_IEEE80211
case IFT_IEEE80211:
#endif
case IFT_GIF:
case IFT_PPP:
#if IFT_TUNNEL
case IFT_TUNNEL:
#endif
case IFT_BRIDGE:
case IFT_CELLULAR:
return(1);
default:
return(0);
}
}
int
nd6_storelladdr(
struct ifnet *ifp,
struct rtentry *rt,
struct mbuf *m,
struct sockaddr *dst,
u_char *desten)
{
int i;
struct sockaddr_dl *sdl;
if (m->m_flags & M_MCAST) {
switch (ifp->if_type) {
case IFT_ETHER:
case IFT_FDDI:
case IFT_L2VLAN:
case IFT_IEEE8023ADLAG:
#if IFT_IEEE80211
case IFT_IEEE80211:
#endif
case IFT_BRIDGE:
ETHER_MAP_IPV6_MULTICAST(&SIN6(dst)->sin6_addr,
desten);
return(1);
case IFT_IEEE1394:
for (i = 0; i < ifp->if_addrlen; i++)
desten[i] = ~0;
return(1);
case IFT_ARCNET:
*desten = 0;
return(1);
default:
return(0);
}
}
if (rt == NULL) {
return(0);
}
RT_LOCK(rt);
if (rt->rt_gateway->sa_family != AF_LINK) {
printf("nd6_storelladdr: something odd happens\n");
RT_UNLOCK(rt);
return(0);
}
sdl = SDL(rt->rt_gateway);
if (sdl->sdl_alen == 0) {
printf("nd6_storelladdr: sdl_alen == 0\n");
RT_UNLOCK(rt);
return(0);
}
bcopy(LLADDR(sdl), desten, sdl->sdl_alen);
RT_UNLOCK(rt);
return(1);
}
errno_t
nd6_lookup_ipv6(ifnet_t ifp, const struct sockaddr_in6 *ip6_dest,
struct sockaddr_dl *ll_dest, size_t ll_dest_len, route_t hint,
mbuf_t packet)
{
route_t route = hint;
errno_t result = 0;
struct sockaddr_dl *sdl = NULL;
size_t copy_len;
if (ip6_dest->sin6_family != AF_INET6)
return (EAFNOSUPPORT);
if ((ifp->if_flags & (IFF_UP|IFF_RUNNING)) != (IFF_UP|IFF_RUNNING))
return (ENETDOWN);
if (hint != NULL) {
result = route_to_gwroute((const struct sockaddr *)ip6_dest,
hint, &route);
if (result != 0)
return (result);
if (route != NULL)
RT_LOCK_ASSERT_HELD(route);
}
if ((packet->m_flags & M_MCAST) != 0) {
if (route != NULL)
RT_UNLOCK(route);
result = dlil_resolve_multi(ifp,
(const struct sockaddr*)ip6_dest,
(struct sockaddr *)ll_dest, ll_dest_len);
if (route != NULL)
RT_LOCK(route);
goto release;
}
if (route == NULL) {
result = ENOBUFS;
goto release;
}
if (route->rt_gateway->sa_family != AF_LINK) {
printf("nd6_lookup_ipv6: gateway address not AF_LINK\n");
result = EADDRNOTAVAIL;
goto release;
}
sdl = SDL(route->rt_gateway);
if (sdl->sdl_alen == 0) {
printf("nd6_lookup_ipv6: sdl_alen == 0\n");
result = EHOSTUNREACH;
goto release;
}
copy_len = sdl->sdl_len <= ll_dest_len ? sdl->sdl_len : ll_dest_len;
bcopy(sdl, ll_dest, copy_len);
release:
if (route != NULL) {
if (route == hint) {
RT_REMREF_LOCKED(route);
RT_UNLOCK(route);
} else {
RT_UNLOCK(route);
rtfree(route);
}
}
return (result);
}
int
nd6_setifinfo(struct ifnet *ifp, u_int32_t before, u_int32_t after)
{
before &= ND6_IFF_PROXY_PREFIXES;
after &= ND6_IFF_PROXY_PREFIXES;
if (before == after)
return (0);
return (nd6_if_prproxy(ifp, ((int32_t)(after - before) > 0)));
}
SYSCTL_DECL(_net_inet6_icmp6);
static int
nd6_sysctl_drlist SYSCTL_HANDLER_ARGS
{
#pragma unused(oidp, arg1, arg2)
int error = 0;
char buf[1024];
struct nd_defrouter *dr;
int p64 = proc_is64bit(req->p);
if (req->newptr)
return (EPERM);
lck_mtx_lock(nd6_mutex);
if (p64) {
struct in6_defrouter_64 *d, *de;
for (dr = TAILQ_FIRST(&nd_defrouter);
dr;
dr = TAILQ_NEXT(dr, dr_entry)) {
d = (struct in6_defrouter_64 *)(void *)buf;
de = (struct in6_defrouter_64 *)
(void *)(buf + sizeof (buf));
if (d + 1 <= de) {
bzero(d, sizeof (*d));
d->rtaddr.sin6_family = AF_INET6;
d->rtaddr.sin6_len = sizeof (d->rtaddr);
if (in6_recoverscope(&d->rtaddr, &dr->rtaddr,
dr->ifp) != 0)
log(LOG_ERR,
"scope error in "
"default router list (%s)\n",
ip6_sprintf(&dr->rtaddr));
d->flags = dr->flags;
d->stateflags = dr->stateflags;
d->stateflags &= ~NDDRF_PROCESSED;
d->rtlifetime = dr->rtlifetime;
d->expire = dr->expire;
d->if_index = dr->ifp->if_index;
} else {
panic("buffer too short");
}
error = SYSCTL_OUT(req, buf, sizeof (*d));
if (error)
break;
}
} else {
struct in6_defrouter_32 *d_32, *de_32;
for (dr = TAILQ_FIRST(&nd_defrouter);
dr;
dr = TAILQ_NEXT(dr, dr_entry)) {
d_32 = (struct in6_defrouter_32 *)(void *)buf;
de_32 = (struct in6_defrouter_32 *)
(void *)(buf + sizeof (buf));
if (d_32 + 1 <= de_32) {
bzero(d_32, sizeof (*d_32));
d_32->rtaddr.sin6_family = AF_INET6;
d_32->rtaddr.sin6_len = sizeof (d_32->rtaddr);
if (in6_recoverscope(&d_32->rtaddr, &dr->rtaddr,
dr->ifp) != 0)
log(LOG_ERR,
"scope error in "
"default router list (%s)\n",
ip6_sprintf(&dr->rtaddr));
d_32->flags = dr->flags;
d_32->stateflags = dr->stateflags;
d_32->stateflags &= ~NDDRF_PROCESSED;
d_32->rtlifetime = dr->rtlifetime;
d_32->expire = dr->expire;
d_32->if_index = dr->ifp->if_index;
} else {
panic("buffer too short");
}
error = SYSCTL_OUT(req, buf, sizeof (*d_32));
if (error)
break;
}
}
lck_mtx_unlock(nd6_mutex);
return (error);
}
static int
nd6_sysctl_prlist SYSCTL_HANDLER_ARGS
{
#pragma unused(oidp, arg1, arg2)
int error = 0;
char buf[1024];
struct nd_prefix *pr;
int p64 = proc_is64bit(req->p);
if (req->newptr)
return (EPERM);
lck_mtx_lock(nd6_mutex);
if (p64) {
struct in6_prefix_64 *p, *pe;
for (pr = nd_prefix.lh_first; pr; pr = pr->ndpr_next) {
u_short advrtrs = 0;
size_t advance;
struct sockaddr_in6 *sin6, *s6;
struct nd_pfxrouter *pfr;
p = (struct in6_prefix_64 *)(void *)buf;
pe = (struct in6_prefix_64 *)
(void *)(buf + sizeof (buf));
if (p + 1 <= pe) {
bzero(p, sizeof (*p));
sin6 = (struct sockaddr_in6 *)(p + 1);
NDPR_LOCK(pr);
p->prefix = pr->ndpr_prefix;
if (in6_recoverscope(&p->prefix,
&p->prefix.sin6_addr, pr->ndpr_ifp) != 0)
log(LOG_ERR,
"scope error in prefix list (%s)\n",
ip6_sprintf(&p->prefix.sin6_addr));
p->raflags = pr->ndpr_raf;
p->prefixlen = pr->ndpr_plen;
p->vltime = pr->ndpr_vltime;
p->pltime = pr->ndpr_pltime;
p->if_index = pr->ndpr_ifp->if_index;
p->expire = pr->ndpr_expire;
p->refcnt = pr->ndpr_addrcnt;
p->flags = pr->ndpr_stateflags;
p->origin = PR_ORIG_RA;
advrtrs = 0;
for (pfr = pr->ndpr_advrtrs.lh_first;
pfr;
pfr = pfr->pfr_next) {
if ((void *)&sin6[advrtrs + 1] >
(void *)pe) {
advrtrs++;
continue;
}
s6 = &sin6[advrtrs];
bzero(s6, sizeof (*s6));
s6->sin6_family = AF_INET6;
s6->sin6_len = sizeof (*sin6);
if (in6_recoverscope(s6,
&pfr->router->rtaddr,
pfr->router->ifp) != 0)
log(LOG_ERR, "scope error in "
"prefix list (%s)\n",
ip6_sprintf(&pfr->router->
rtaddr));
advrtrs++;
}
p->advrtrs = advrtrs;
NDPR_UNLOCK(pr);
} else {
panic("buffer too short");
}
advance = sizeof (*p) + sizeof (*sin6) * advrtrs;
error = SYSCTL_OUT(req, buf, advance);
if (error)
break;
}
} else {
struct in6_prefix_32 *p_32, *pe_32;
for (pr = nd_prefix.lh_first; pr; pr = pr->ndpr_next) {
u_short advrtrs = 0;
size_t advance;
struct sockaddr_in6 *sin6, *s6;
struct nd_pfxrouter *pfr;
p_32 = (struct in6_prefix_32 *)(void *)buf;
pe_32 = (struct in6_prefix_32 *)
(void *)(buf + sizeof (buf));
if (p_32 + 1 <= pe_32) {
bzero(p_32, sizeof (*p_32));
sin6 = (struct sockaddr_in6 *)(p_32 + 1);
NDPR_LOCK(pr);
p_32->prefix = pr->ndpr_prefix;
if (in6_recoverscope(&p_32->prefix,
&p_32->prefix.sin6_addr, pr->ndpr_ifp) != 0)
log(LOG_ERR, "scope error in prefix "
"list (%s)\n", ip6_sprintf(&p_32->
prefix.sin6_addr));
p_32->raflags = pr->ndpr_raf;
p_32->prefixlen = pr->ndpr_plen;
p_32->vltime = pr->ndpr_vltime;
p_32->pltime = pr->ndpr_pltime;
p_32->if_index = pr->ndpr_ifp->if_index;
p_32->expire = pr->ndpr_expire;
p_32->refcnt = pr->ndpr_addrcnt;
p_32->flags = pr->ndpr_stateflags;
p_32->origin = PR_ORIG_RA;
advrtrs = 0;
for (pfr = pr->ndpr_advrtrs.lh_first;
pfr;
pfr = pfr->pfr_next) {
if ((void *)&sin6[advrtrs + 1] >
(void *)pe_32) {
advrtrs++;
continue;
}
s6 = &sin6[advrtrs];
bzero(s6, sizeof (*s6));
s6->sin6_family = AF_INET6;
s6->sin6_len = sizeof (*sin6);
if (in6_recoverscope(s6,
&pfr->router->rtaddr,
pfr->router->ifp) != 0)
log(LOG_ERR, "scope error in "
"prefix list (%s)\n",
ip6_sprintf(&pfr->router->
rtaddr));
advrtrs++;
}
p_32->advrtrs = advrtrs;
NDPR_UNLOCK(pr);
} else {
panic("buffer too short");
}
advance = sizeof (*p_32) + sizeof (*sin6) * advrtrs;
error = SYSCTL_OUT(req, buf, advance);
if (error)
break;
}
}
lck_mtx_unlock(nd6_mutex);
return (error);
}
SYSCTL_PROC(_net_inet6_icmp6, ICMPV6CTL_ND6_DRLIST, nd6_drlist,
CTLFLAG_RD | CTLFLAG_LOCKED, 0, 0, nd6_sysctl_drlist, "S,in6_defrouter","");
SYSCTL_PROC(_net_inet6_icmp6, ICMPV6CTL_ND6_PRLIST, nd6_prlist,
CTLFLAG_RD | CTLFLAG_LOCKED, 0, 0, nd6_sysctl_prlist, "S,in6_defrouter","");