#include <sys/types.h>
#include <sys/stream.h>
#include <sys/stropts.h>
#include <sys/strsun.h>
#include <sys/sysmacros.h>
#include <sys/errno.h>
#include <sys/dlpi.h>
#include <sys/socket.h>
#include <sys/ddi.h>
#include <sys/sunddi.h>
#include <sys/cmn_err.h>
#include <sys/debug.h>
#include <sys/vtrace.h>
#include <sys/kmem.h>
#include <sys/zone.h>
#include <sys/ethernet.h>
#include <sys/sdt.h>
#include <sys/mac.h>
#include <net/if.h>
#include <net/if_types.h>
#include <net/if_dl.h>
#include <net/route.h>
#include <netinet/in.h>
#include <netinet/ip6.h>
#include <netinet/icmp6.h>
#include <inet/common.h>
#include <inet/mi.h>
#include <inet/mib2.h>
#include <inet/nd.h>
#include <inet/ip.h>
#include <inet/ip_impl.h>
#include <inet/ipclassifier.h>
#include <inet/ip_if.h>
#include <inet/ip_ire.h>
#include <inet/ip_rts.h>
#include <inet/ip6.h>
#include <inet/ip_ndp.h>
#include <inet/sctp_ip.h>
#include <inet/ip_arp.h>
#include <inet/ip2mac_impl.h>
#define ANNOUNCE_INTERVAL(isv6) \
(isv6 ? ipst->ips_ip_ndp_unsolicit_interval : \
ipst->ips_ip_arp_publish_interval)
#define DEFENSE_INTERVAL(isv6) \
(isv6 ? ipst->ips_ndp_defend_interval : \
ipst->ips_arp_defend_interval)
#define ILL_PROBE_INTERVAL(ill) ((ill)->ill_note_link ? 150 : 1500)
#define IS_IPV4_LL_SPACE(ptr) (((uchar_t *)ptr)[0] == 169 && \
((uchar_t *)ptr)[1] == 254)
#define NCE_EXTERNAL_FLAGS_MASK \
(NCE_F_MYADDR | NCE_F_ISROUTER | NCE_F_NONUD | \
NCE_F_ANYCAST | NCE_F_UNSOL_ADV | NCE_F_BCAST | NCE_F_MCAST | \
NCE_F_AUTHORITY | NCE_F_PUBLISH | NCE_F_STATIC)
static void nce_cleanup_list(ncec_t *ncec);
static void nce_set_ll(ncec_t *ncec, uchar_t *ll_addr);
static ncec_t *ncec_lookup_illgrp(ill_t *, const in6_addr_t *,
ncec_t *);
static nce_t *nce_lookup_addr(ill_t *, const in6_addr_t *);
static int nce_set_multicast_v6(ill_t *ill, const in6_addr_t *addr,
uint16_t ncec_flags, nce_t **newnce);
static int nce_set_multicast_v4(ill_t *ill, const in_addr_t *dst,
uint16_t ncec_flags, nce_t **newnce);
static boolean_t ndp_xmit(ill_t *ill, uint32_t operation,
uint8_t *hwaddr, uint_t hwaddr_len, const in6_addr_t *sender,
const in6_addr_t *target, int flag);
static void ncec_refhold_locked(ncec_t *);
static boolean_t ill_defend_rate_limit(ill_t *, ncec_t *);
static void nce_queue_mp_common(ncec_t *, mblk_t *, boolean_t);
static int nce_add_common(ill_t *, uchar_t *, uint_t, const in6_addr_t *,
uint16_t, uint16_t, nce_t **);
static nce_t *nce_add_impl(ill_t *, ncec_t *, nce_t *, mblk_t *, list_t *);
static nce_t *nce_add(ill_t *, ncec_t *, list_t *);
static void nce_inactive(nce_t *);
extern nce_t *nce_lookup(ill_t *, const in6_addr_t *);
static nce_t *nce_ill_lookup_then_add(ill_t *, ncec_t *);
static int nce_add_v6(ill_t *, uchar_t *, uint_t, const in6_addr_t *,
uint16_t, uint16_t, nce_t **);
static int nce_add_v4(ill_t *, uchar_t *, uint_t, const in_addr_t *,
uint16_t, uint16_t, nce_t **);
static int nce_add_v6_postprocess(nce_t *);
static int nce_add_v4_postprocess(nce_t *);
static ill_t *nce_resolve_src(ncec_t *, in6_addr_t *);
static clock_t nce_fuzz_interval(clock_t, boolean_t);
static void nce_resolv_ipmp_ok(ncec_t *);
static void nce_walk_common(ill_t *, pfi_t, void *);
static void nce_start_timer(ncec_t *, uint_t);
static nce_t *nce_fastpath_create(ill_t *, ncec_t *);
static void nce_fastpath_trigger(nce_t *);
static nce_t *nce_fastpath(ncec_t *, boolean_t, nce_t *);
#ifdef DEBUG
static void ncec_trace_cleanup(const ncec_t *);
#endif
#define NCE_HASH_PTR_V4(ipst, addr) \
(&((ipst)->ips_ndp4->nce_hash_tbl[IRE_ADDR_HASH(addr, NCE_TABLE_SIZE)]))
#define NCE_HASH_PTR_V6(ipst, addr) \
(&((ipst)->ips_ndp6->nce_hash_tbl[NCE_ADDR_HASH_V6(addr, \
NCE_TABLE_SIZE)]))
extern kmem_cache_t *ncec_cache;
extern kmem_cache_t *nce_cache;
static void
nce_dad(ncec_t *ncec, ill_t *src_ill, boolean_t send_probe)
{
boolean_t dropped;
uint32_t probe_interval;
ASSERT(!(ncec->ncec_flags & NCE_F_MCAST));
ASSERT(!(ncec->ncec_flags & NCE_F_BCAST));
if (ncec->ncec_ipversion == IPV6_VERSION) {
dropped = ndp_xmit(src_ill, ND_NEIGHBOR_SOLICIT,
ncec->ncec_lladdr, ncec->ncec_lladdr_length,
&ipv6_all_zeros, &ncec->ncec_addr, NDP_PROBE);
probe_interval = ILL_PROBE_INTERVAL(src_ill);
} else {
if (send_probe)
dropped = arp_probe(ncec);
else
dropped = B_TRUE;
probe_interval = nce_fuzz_interval(ncec->ncec_xmit_interval,
!send_probe);
}
if (!dropped) {
mutex_enter(&ncec->ncec_lock);
ncec->ncec_pcnt--;
mutex_exit(&ncec->ncec_lock);
}
nce_restart_timer(ncec, probe_interval);
}
static int
nce_advert_flags(const ncec_t *ncec)
{
int flag = 0;
if (ncec->ncec_flags & NCE_F_ISROUTER)
flag |= NDP_ISROUTER;
if (!(ncec->ncec_flags & NCE_F_ANYCAST))
flag |= NDP_ORIDE;
return (flag);
}
int
nce_add_v6(ill_t *ill, uchar_t *hw_addr, uint_t hw_addr_len,
const in6_addr_t *addr, uint16_t flags, uint16_t state, nce_t **newnce)
{
int err;
nce_t *nce;
ASSERT(MUTEX_HELD(&ill->ill_ipst->ips_ndp6->ndp_g_lock));
ASSERT(ill != NULL && ill->ill_isv6);
err = nce_add_common(ill, hw_addr, hw_addr_len, addr, flags, state,
&nce);
if (err != 0)
return (err);
ASSERT(newnce != NULL);
*newnce = nce;
return (err);
}
int
nce_add_v6_postprocess(nce_t *nce)
{
ncec_t *ncec = nce->nce_common;
boolean_t dropped = B_FALSE;
uchar_t *hw_addr = ncec->ncec_lladdr;
uint_t hw_addr_len = ncec->ncec_lladdr_length;
ill_t *ill = ncec->ncec_ill;
int err = 0;
uint16_t flags = ncec->ncec_flags;
ip_stack_t *ipst = ill->ill_ipst;
boolean_t trigger_fastpath = B_TRUE;
if (NCE_PUBLISH(ncec) || !NCE_ISREACHABLE(ncec) ||
(hw_addr == NULL && ill->ill_net_type != IRE_IF_NORESOLVER))
trigger_fastpath = B_FALSE;
if (trigger_fastpath)
nce_fastpath_trigger(nce);
if (NCE_PUBLISH(ncec) && ncec->ncec_state == ND_PROBE) {
ill_t *hwaddr_ill;
if (IS_IPMP(ill)) {
hwaddr_ill = ipmp_illgrp_find_ill(ill->ill_grp,
hw_addr, hw_addr_len);
} else {
hwaddr_ill = ill;
}
nce_dad(ncec, hwaddr_ill, B_TRUE);
err = EINPROGRESS;
} else if (flags & NCE_F_UNSOL_ADV) {
mutex_enter(&ncec->ncec_lock);
ncec->ncec_unsolicit_count =
ipst->ips_ip_ndp_unsolicit_count - 1;
mutex_exit(&ncec->ncec_lock);
dropped = ndp_xmit(ill,
ND_NEIGHBOR_ADVERT,
hw_addr,
hw_addr_len,
&ncec->ncec_addr,
&ipv6_all_hosts_mcast,
nce_advert_flags(ncec));
mutex_enter(&ncec->ncec_lock);
if (dropped)
ncec->ncec_unsolicit_count++;
else
ncec->ncec_last_time_defended = ddi_get_lbolt();
if (ncec->ncec_unsolicit_count != 0) {
nce_start_timer(ncec,
ipst->ips_ip_ndp_unsolicit_interval);
}
mutex_exit(&ncec->ncec_lock);
}
return (err);
}
int
nce_lookup_then_add_v6(ill_t *ill, uchar_t *hw_addr, uint_t hw_addr_len,
const in6_addr_t *addr, uint16_t flags, uint16_t state, nce_t **newnce)
{
int err = 0;
ip_stack_t *ipst = ill->ill_ipst;
nce_t *nce, *upper_nce = NULL;
ill_t *in_ill = ill;
boolean_t need_ill_refrele = B_FALSE;
if (flags & NCE_F_MCAST) {
ASSERT(hw_addr == NULL);
ASSERT(!IS_IPMP(ill));
err = nce_set_multicast_v6(ill, addr, flags, newnce);
return (err);
}
ASSERT(ill->ill_isv6);
if (IS_UNDER_IPMP(ill) && !(flags & NCE_F_MYADDR)) {
ill = ipmp_ill_hold_ipmp_ill(ill);
if (ill == NULL)
return (ENXIO);
need_ill_refrele = B_TRUE;
}
mutex_enter(&ipst->ips_ndp6->ndp_g_lock);
nce = nce_lookup_addr(ill, addr);
if (nce == NULL) {
err = nce_add_v6(ill, hw_addr, hw_addr_len, addr, flags, state,
&nce);
} else {
err = EEXIST;
}
mutex_exit(&ipst->ips_ndp6->ndp_g_lock);
if (err == 0)
err = nce_add_v6_postprocess(nce);
if (in_ill != ill && nce != NULL) {
nce_t *under_nce = NULL;
rw_enter(&ipst->ips_ill_g_lock, RW_READER);
if (!IS_IN_SAME_ILLGRP(in_ill, ill)) {
DTRACE_PROBE2(ill__not__in__group, nce_t *, nce,
ill_t *, ill);
rw_exit(&ipst->ips_ill_g_lock);
err = ENXIO;
nce_refrele(nce);
nce = NULL;
goto bail;
}
under_nce = nce_fastpath_create(in_ill, nce->nce_common);
if (under_nce == NULL) {
rw_exit(&ipst->ips_ill_g_lock);
err = EINVAL;
nce_refrele(nce);
nce = NULL;
goto bail;
}
rw_exit(&ipst->ips_ill_g_lock);
upper_nce = nce;
nce = under_nce;
if (NCE_ISREACHABLE(nce->nce_common))
nce_fastpath_trigger(under_nce);
}
if (nce != NULL) {
if (newnce != NULL)
*newnce = nce;
else
nce_refrele(nce);
}
bail:
if (upper_nce != NULL)
nce_refrele(upper_nce);
if (need_ill_refrele)
ill_refrele(ill);
return (err);
}
static void
nce_remove(ndp_g_t *ndp, ncec_t *ncec, ncec_t **free_nce_list)
{
ncec_t *ncec1;
ncec_t **ptpn;
ASSERT(MUTEX_HELD(&ndp->ndp_g_lock));
ASSERT(ndp->ndp_g_walker == 0);
for (; ncec; ncec = ncec1) {
ncec1 = ncec->ncec_next;
mutex_enter(&ncec->ncec_lock);
if (NCE_ISCONDEMNED(ncec)) {
ptpn = ncec->ncec_ptpn;
ncec1 = ncec->ncec_next;
if (ncec1 != NULL)
ncec1->ncec_ptpn = ptpn;
*ptpn = ncec1;
ncec->ncec_ptpn = NULL;
ncec->ncec_next = NULL;
ncec->ncec_next = *free_nce_list;
*free_nce_list = ncec;
}
mutex_exit(&ncec->ncec_lock);
}
}
void
ncec_delete(ncec_t *ncec)
{
ncec_t **ptpn;
ncec_t *ncec1;
int ipversion = ncec->ncec_ipversion;
ndp_g_t *ndp;
ip_stack_t *ipst = ncec->ncec_ipst;
if (ipversion == IPV4_VERSION)
ndp = ipst->ips_ndp4;
else
ndp = ipst->ips_ndp6;
mutex_enter(&ncec->ncec_lock);
if (NCE_ISCONDEMNED(ncec)) {
mutex_exit(&ncec->ncec_lock);
return;
}
ASSERT(ncec->ncec_refcnt >= 2);
ncec->ncec_flags |= NCE_F_CONDEMNED;
mutex_exit(&ncec->ncec_lock);
atomic_inc_32(&ipst->ips_num_nce_condemned);
nce_fastpath_list_delete(ncec->ncec_ill, ncec, NULL);
ncec_cb_dispatch(ncec);
if (ncec->ncec_timeout_id != 0) {
(void) untimeout(ncec->ncec_timeout_id);
ncec->ncec_timeout_id = 0;
}
mutex_enter(&ndp->ndp_g_lock);
if (ncec->ncec_ptpn == NULL) {
mutex_exit(&ndp->ndp_g_lock);
return;
}
if (ndp->ndp_g_walker > 0) {
ndp->ndp_g_walker_cleanup = B_TRUE;
mutex_exit(&ndp->ndp_g_lock);
return;
}
ptpn = ncec->ncec_ptpn;
ncec1 = ncec->ncec_next;
if (ncec1 != NULL)
ncec1->ncec_ptpn = ptpn;
*ptpn = ncec1;
ncec->ncec_ptpn = NULL;
ncec->ncec_next = NULL;
mutex_exit(&ndp->ndp_g_lock);
ncec_refrele_notr(ncec);
}
void
ncec_inactive(ncec_t *ncec)
{
mblk_t **mpp;
ill_t *ill = ncec->ncec_ill;
ip_stack_t *ipst = ncec->ncec_ipst;
ASSERT(ncec->ncec_refcnt == 0);
ASSERT(MUTEX_HELD(&ncec->ncec_lock));
if (NCE_ISCONDEMNED(ncec))
atomic_add_32(&ipst->ips_num_nce_condemned, -1);
mpp = &ncec->ncec_qd_mp;
while (*mpp != NULL) {
mblk_t *mp;
mp = *mpp;
*mpp = mp->b_next;
inet_freemsg(mp);
}
ASSERT(list_is_empty(&ncec->ncec_cb));
list_destroy(&ncec->ncec_cb);
if (ncec->ncec_lladdr_length > 0)
kmem_free(ncec->ncec_lladdr, ncec->ncec_lladdr_length);
#ifdef DEBUG
ncec_trace_cleanup(ncec);
#endif
mutex_enter(&ill->ill_lock);
DTRACE_PROBE3(ill__decr__cnt, (ill_t *), ill,
(char *), "ncec", (void *), ncec);
ill->ill_ncec_cnt--;
ncec->ncec_ill = NULL;
if (ILL_DOWN_OK(ill)) {
ipif_ill_refrele_tail(ill);
} else {
mutex_exit(&ill->ill_lock);
}
mutex_destroy(&ncec->ncec_lock);
kmem_cache_free(ncec_cache, ncec);
}
void
ncec_delete_per_ill(ncec_t *ncec, void *arg)
{
if ((ncec != NULL) && ncec->ncec_ill == arg) {
ncec_delete(ncec);
}
}
static void
nce_cleanup_list(ncec_t *ncec)
{
ncec_t *ncec_next;
ASSERT(ncec != NULL);
while (ncec != NULL) {
ncec_next = ncec->ncec_next;
ncec->ncec_next = NULL;
nce_fastpath_list_delete(ncec->ncec_ill, ncec, NULL);
if (ncec->ncec_timeout_id != 0) {
(void) untimeout(ncec->ncec_timeout_id);
ncec->ncec_timeout_id = 0;
}
ncec_refrele_notr(ncec);
ncec = ncec_next;
}
}
boolean_t
nce_restart_dad(ncec_t *ncec)
{
boolean_t started;
ill_t *ill, *hwaddr_ill;
if (ncec == NULL)
return (B_FALSE);
ill = ncec->ncec_ill;
mutex_enter(&ncec->ncec_lock);
if (ncec->ncec_state == ND_PROBE) {
mutex_exit(&ncec->ncec_lock);
started = B_TRUE;
} else if (ncec->ncec_state == ND_REACHABLE) {
ASSERT(ncec->ncec_lladdr != NULL);
ncec->ncec_state = ND_PROBE;
ncec->ncec_pcnt = ND_MAX_UNICAST_SOLICIT;
mutex_exit(&ncec->ncec_lock);
if (IS_IPMP(ill)) {
hwaddr_ill = ipmp_illgrp_find_ill(ill->ill_grp,
ncec->ncec_lladdr, ncec->ncec_lladdr_length);
} else {
hwaddr_ill = ill;
}
nce_dad(ncec, hwaddr_ill, B_TRUE);
started = B_TRUE;
} else {
mutex_exit(&ncec->ncec_lock);
started = B_FALSE;
}
return (started);
}
ncec_t *
ncec_lookup_illgrp_v6(ill_t *ill, const in6_addr_t *addr)
{
ncec_t *ncec;
ip_stack_t *ipst = ill->ill_ipst;
rw_enter(&ipst->ips_ill_g_lock, RW_READER);
mutex_enter(&ipst->ips_ndp6->ndp_g_lock);
ncec = *((ncec_t **)NCE_HASH_PTR_V6(ipst, *addr));
ncec = ncec_lookup_illgrp(ill, addr, ncec);
mutex_exit(&ipst->ips_ndp6->ndp_g_lock);
rw_exit(&ipst->ips_ill_g_lock);
return (ncec);
}
ncec_t *
ncec_lookup_illgrp_v4(ill_t *ill, const in_addr_t *addr)
{
ncec_t *ncec = NULL;
in6_addr_t addr6;
ip_stack_t *ipst = ill->ill_ipst;
rw_enter(&ipst->ips_ill_g_lock, RW_READER);
mutex_enter(&ipst->ips_ndp4->ndp_g_lock);
ncec = *((ncec_t **)NCE_HASH_PTR_V4(ipst, *addr));
IN6_IPADDR_TO_V4MAPPED(*addr, &addr6);
ncec = ncec_lookup_illgrp(ill, &addr6, ncec);
mutex_exit(&ipst->ips_ndp4->ndp_g_lock);
rw_exit(&ipst->ips_ill_g_lock);
return (ncec);
}
ncec_t *
ncec_lookup_illgrp(ill_t *ill, const in6_addr_t *addr, ncec_t *ncec)
{
ndp_g_t *ndp;
ip_stack_t *ipst = ill->ill_ipst;
if (ill->ill_isv6)
ndp = ipst->ips_ndp6;
else
ndp = ipst->ips_ndp4;
ASSERT(ill != NULL);
ASSERT(MUTEX_HELD(&ndp->ndp_g_lock));
if (IN6_IS_ADDR_UNSPECIFIED(addr))
return (NULL);
for (; ncec != NULL; ncec = ncec->ncec_next) {
if (ncec->ncec_ill == ill ||
IS_IN_SAME_ILLGRP(ill, ncec->ncec_ill)) {
if (IN6_ARE_ADDR_EQUAL(&ncec->ncec_addr, addr)) {
mutex_enter(&ncec->ncec_lock);
if (!NCE_ISCONDEMNED(ncec)) {
ncec_refhold_locked(ncec);
mutex_exit(&ncec->ncec_lock);
break;
}
mutex_exit(&ncec->ncec_lock);
}
}
}
return (ncec);
}
nce_t *
nce_lookup_v4(ill_t *ill, const in_addr_t *addr)
{
nce_t *nce;
in6_addr_t addr6;
ip_stack_t *ipst = ill->ill_ipst;
mutex_enter(&ipst->ips_ndp4->ndp_g_lock);
IN6_IPADDR_TO_V4MAPPED(*addr, &addr6);
nce = nce_lookup_addr(ill, &addr6);
mutex_exit(&ipst->ips_ndp4->ndp_g_lock);
return (nce);
}
nce_t *
nce_lookup_v6(ill_t *ill, const in6_addr_t *addr6)
{
nce_t *nce;
ip_stack_t *ipst = ill->ill_ipst;
mutex_enter(&ipst->ips_ndp6->ndp_g_lock);
nce = nce_lookup_addr(ill, addr6);
mutex_exit(&ipst->ips_ndp6->ndp_g_lock);
return (nce);
}
static nce_t *
nce_lookup_addr(ill_t *ill, const in6_addr_t *addr)
{
nce_t *nce;
ASSERT(ill != NULL);
#ifdef DEBUG
if (ill->ill_isv6)
ASSERT(MUTEX_HELD(&ill->ill_ipst->ips_ndp6->ndp_g_lock));
else
ASSERT(MUTEX_HELD(&ill->ill_ipst->ips_ndp4->ndp_g_lock));
#endif
mutex_enter(&ill->ill_lock);
nce = nce_lookup(ill, addr);
mutex_exit(&ill->ill_lock);
return (nce);
}
static void
ncec_router_to_host(ncec_t *ncec)
{
ire_t *ire;
ip_stack_t *ipst = ncec->ncec_ipst;
mutex_enter(&ncec->ncec_lock);
ncec->ncec_flags &= ~NCE_F_ISROUTER;
mutex_exit(&ncec->ncec_lock);
ire = ire_ftable_lookup_v6(&ipv6_all_zeros, &ipv6_all_zeros,
&ncec->ncec_addr, IRE_DEFAULT, ncec->ncec_ill, ALL_ZONES, NULL,
MATCH_IRE_ILL | MATCH_IRE_TYPE | MATCH_IRE_GW, 0, ipst, NULL);
if (ire != NULL) {
ip_rts_rtmsg(RTM_DELETE, ire, 0, ipst);
ire_delete(ire);
ire_refrele(ire);
}
}
void
nce_process(ncec_t *ncec, uchar_t *hw_addr, uint32_t flag, boolean_t is_adv)
{
ill_t *ill = ncec->ncec_ill;
uint32_t hw_addr_len = ill->ill_phys_addr_length;
boolean_t ll_updated = B_FALSE;
boolean_t ll_changed;
nce_t *nce;
ASSERT(ncec->ncec_ipversion == IPV6_VERSION);
mutex_enter(&ncec->ncec_lock);
if (ncec->ncec_state == ND_INCOMPLETE) {
if (hw_addr == NULL) {
mutex_exit(&ncec->ncec_lock);
return;
}
nce_set_ll(ncec, hw_addr);
if (flag & ND_NA_FLAG_SOLICITED) {
nce_update(ncec, ND_REACHABLE, NULL);
} else {
nce_update(ncec, ND_STALE, NULL);
}
mutex_exit(&ncec->ncec_lock);
nce = nce_fastpath(ncec, B_TRUE, NULL);
nce_resolv_ok(ncec);
if (nce != NULL)
nce_refrele(nce);
return;
}
ll_changed = nce_cmp_ll_addr(ncec, hw_addr, hw_addr_len);
if (!is_adv) {
if (ll_changed)
nce_update(ncec, ND_STALE, hw_addr);
mutex_exit(&ncec->ncec_lock);
ncec_cb_dispatch(ncec);
return;
}
if (!(flag & ND_NA_FLAG_OVERRIDE) && ll_changed) {
if (ncec->ncec_state == ND_REACHABLE) {
nce_update(ncec, ND_STALE, NULL);
}
mutex_exit(&ncec->ncec_lock);
ncec_cb_dispatch(ncec);
return;
} else {
if (ll_changed) {
nce_update(ncec, ND_UNCHANGED, hw_addr);
ll_updated = B_TRUE;
}
if (flag & ND_NA_FLAG_SOLICITED) {
nce_update(ncec, ND_REACHABLE, NULL);
} else {
if (ll_updated) {
nce_update(ncec, ND_STALE, NULL);
}
}
mutex_exit(&ncec->ncec_lock);
if (!(flag & ND_NA_FLAG_ROUTER) && (ncec->ncec_flags &
NCE_F_ISROUTER)) {
ncec_router_to_host(ncec);
} else {
ncec_cb_dispatch(ncec);
}
}
}
void
ncec_walk_common(ndp_g_t *ndp, ill_t *ill, ncec_walk_cb_t cbf,
void *arg1, boolean_t trace)
{
ncec_t *ncec;
ncec_t *ncec1;
ncec_t **ncep;
ncec_t *free_nce_list = NULL;
mutex_enter(&ndp->ndp_g_lock);
ndp->ndp_g_walker++;
mutex_exit(&ndp->ndp_g_lock);
for (ncep = ndp->nce_hash_tbl;
ncep < A_END(ndp->nce_hash_tbl); ncep++) {
for (ncec = *ncep; ncec != NULL; ncec = ncec1) {
ncec1 = ncec->ncec_next;
if (ill == NULL || ncec->ncec_ill == ill) {
if (trace) {
ncec_refhold(ncec);
(*cbf)(ncec, arg1);
ncec_refrele(ncec);
} else {
ncec_refhold_notr(ncec);
(*cbf)(ncec, arg1);
ncec_refrele_notr(ncec);
}
}
}
}
mutex_enter(&ndp->ndp_g_lock);
ndp->ndp_g_walker--;
if (ndp->ndp_g_walker_cleanup && ndp->ndp_g_walker == 0) {
for (ncep = ndp->nce_hash_tbl;
ncep < A_END(ndp->nce_hash_tbl); ncep++) {
ncec = *ncep;
if (ncec != NULL) {
nce_remove(ndp, ncec, &free_nce_list);
}
}
ndp->ndp_g_walker_cleanup = B_FALSE;
}
mutex_exit(&ndp->ndp_g_lock);
if (free_nce_list != NULL) {
nce_cleanup_list(free_nce_list);
}
}
void
ncec_walk(ill_t *ill, ncec_walk_cb_t cbf, void *arg1, ip_stack_t *ipst)
{
ncec_walk_common(ipst->ips_ndp4, ill, cbf, arg1, B_TRUE);
ncec_walk_common(ipst->ips_ndp6, ill, cbf, arg1, B_TRUE);
}
uint_t ip_max_ill_mcast_nces = 16384;
uint_t ip_ill_mcast_reclaim = 256;
static boolean_t
nce_too_many_mcast(ill_t *ill, list_t *graveyard)
{
uint_t reclaim_count, max_count, reclaimed = 0;
boolean_t too_many;
nce_t *nce, *deadman;
ASSERT(graveyard != NULL);
ASSERT(list_is_empty(graveyard));
ASSERT(MUTEX_HELD(&ill->ill_lock));
max_count = ip_max_ill_mcast_nces;
reclaim_count = min(ip_ill_mcast_reclaim, max_count);
if (ill->ill_mcast_nces < max_count)
return (B_FALSE);
if (reclaim_count == 0)
return (B_TRUE);
nce = list_tail(&ill->ill_nce);
while (reclaimed < reclaim_count) {
while (nce != NULL &&
(nce->nce_common->ncec_flags & NCE_F_MCAST) == 0) {
nce = list_prev(&ill->ill_nce, nce);
}
if (nce == NULL)
break;
deadman = nce;
nce = list_prev(&ill->ill_nce, nce);
nce_refhold(deadman);
nce_delete(deadman);
list_insert_tail(graveyard, deadman);
reclaimed++;
}
if (reclaimed != reclaim_count) {
DTRACE_PROBE3(ill__mcast__nce__reclaim__mismatch, ill_t *, ill,
uint_t, reclaimed, uint_t, reclaim_count);
too_many = (reclaimed == 0);
} else {
too_many = B_FALSE;
}
return (too_many);
}
static void
ncec_mcast_reap_one(ncec_t *ncec, void *arg)
{
boolean_t reapit;
ill_t *ill = (ill_t *)arg;
if (ncec == NULL || ncec->ncec_ill != ill ||
(ncec->ncec_flags & NCE_F_MCAST) == 0)
return;
mutex_enter(&ncec->ncec_lock);
ASSERT(ncec->ncec_refcnt >= 2);
reapit = (ncec->ncec_refcnt == 2);
mutex_exit(&ncec->ncec_lock);
if (reapit) {
IP_STAT(ill->ill_ipst, ip_nce_mcast_reclaim_deleted);
ncec_delete(ncec);
}
}
static void
ncec_mcast_reap(void *arg)
{
ill_t *ill = (ill_t *)arg;
IP_STAT(ill->ill_ipst, ip_nce_mcast_reclaim_calls);
ncec_walk(ill, ncec_mcast_reap_one, ill, ill->ill_ipst);
mutex_enter(&ill->ill_lock);
ill->ill_mcast_ncec_cleanup = B_FALSE;
ill->ill_refcnt--;
if (ill->ill_refcnt == 0)
ipif_ill_refrele_tail(ill);
else
mutex_exit(&ill->ill_lock);
}
static void
nce_graveyard_free(list_t *graveyard)
{
nce_t *deadman, *current;
ill_t *ill;
boolean_t doit;
if (graveyard == NULL)
return;
current = list_head(graveyard);
if (current == NULL) {
list_destroy(graveyard);
return;
}
ill = current->nce_ill;
mutex_enter(&ill->ill_lock);
ill->ill_refcnt++;
mutex_exit(&ill->ill_lock);
while (current != NULL) {
ASSERT3P(ill, ==, current->nce_ill);
deadman = current;
current = list_next(graveyard, deadman);
list_remove(graveyard, deadman);
ASSERT3U((deadman->nce_common->ncec_flags & NCE_F_MCAST), !=,
0);
nce_refrele(deadman);
}
list_destroy(graveyard);
mutex_enter(&ill->ill_lock);
if (ill->ill_mcast_ncec_cleanup)
doit = B_FALSE;
else {
ill->ill_mcast_ncec_cleanup = B_TRUE;
doit = B_TRUE;
}
mutex_exit(&ill->ill_lock);
if (!doit || taskq_dispatch(system_taskq, ncec_mcast_reap,
ill, TQ_NOSLEEP) == TASKQID_INVALID) {
mutex_enter(&ill->ill_lock);
if (doit) {
IP_STAT(ill->ill_ipst, ip_nce_mcast_reclaim_tqfail);
ill->ill_mcast_ncec_cleanup = B_FALSE;
}
ill->ill_refcnt--;
if (ill->ill_refcnt == 0)
ipif_ill_refrele_tail(ill);
else
mutex_exit(&ill->ill_lock);
}
}
static int
nce_set_multicast_v6(ill_t *ill, const in6_addr_t *dst,
uint16_t flags, nce_t **newnce)
{
uchar_t *hw_addr;
int err = 0;
ip_stack_t *ipst = ill->ill_ipst;
nce_t *nce;
ASSERT(ill != NULL);
ASSERT(ill->ill_isv6);
ASSERT(!(IN6_IS_ADDR_UNSPECIFIED(dst)));
mutex_enter(&ipst->ips_ndp6->ndp_g_lock);
nce = nce_lookup_addr(ill, dst);
if (nce != NULL) {
mutex_exit(&ipst->ips_ndp6->ndp_g_lock);
goto done;
}
if (ill->ill_net_type == IRE_IF_RESOLVER) {
hw_addr = kmem_alloc(ill->ill_nd_lla_len, KM_NOSLEEP);
if (hw_addr == NULL) {
mutex_exit(&ipst->ips_ndp6->ndp_g_lock);
return (ENOMEM);
}
ip_mcast_mapping(ill, (uchar_t *)dst, hw_addr);
} else {
hw_addr = NULL;
}
ASSERT((flags & NCE_F_MCAST) != 0);
ASSERT((flags & NCE_F_NONUD) != 0);
err = nce_add_v6(ill, hw_addr, ill->ill_phys_addr_length, dst, flags,
ND_UNCHANGED, &nce);
mutex_exit(&ipst->ips_ndp6->ndp_g_lock);
if (err == 0)
err = (nce != NULL) ? nce_add_v6_postprocess(nce) : ENOMEM;
if (hw_addr != NULL)
kmem_free(hw_addr, ill->ill_nd_lla_len);
if (err != 0) {
ip1dbg(("nce_set_multicast_v6: create failed" "%d\n", err));
return (err);
}
done:
ASSERT(nce->nce_common->ncec_state == ND_REACHABLE);
if (newnce != NULL)
*newnce = nce;
else
nce_refrele(nce);
return (0);
}
int
ndp_query(ill_t *ill, struct lif_nd_req *lnr)
{
ncec_t *ncec;
in6_addr_t *addr;
sin6_t *sin6;
ASSERT(ill != NULL && ill->ill_isv6);
sin6 = (sin6_t *)&lnr->lnr_addr;
addr = &sin6->sin6_addr;
ncec = ncec_lookup_illgrp_v6(ill, addr);
if (ncec == NULL)
return (ESRCH);
if (!NCE_ISREACHABLE(ncec)) {
ncec_refrele(ncec);
return (ESRCH);
}
lnr->lnr_hdw_len = ill->ill_phys_addr_length;
bcopy(ncec->ncec_lladdr, (uchar_t *)&lnr->lnr_hdw_addr,
lnr->lnr_hdw_len);
if (ncec->ncec_flags & NCE_F_ISROUTER)
lnr->lnr_flags = NDF_ISROUTER_ON;
if (ncec->ncec_flags & NCE_F_ANYCAST)
lnr->lnr_flags |= NDF_ANYCAST_ON;
if (ncec->ncec_flags & NCE_F_STATIC)
lnr->lnr_flags |= NDF_STATIC;
ncec_refrele(ncec);
return (0);
}
mblk_t *
ndp_mcastreq(ill_t *ill, const in6_addr_t *v6group, uint32_t hw_addr_len,
uint32_t hw_addr_offset, mblk_t *mp)
{
uchar_t *hw_addr;
ipaddr_t v4group;
uchar_t *addr;
ASSERT(ill->ill_net_type == IRE_IF_RESOLVER);
if (IN6_IS_ADDR_V4MAPPED(v6group)) {
IN6_V4MAPPED_TO_IPADDR(v6group, v4group);
ASSERT(CLASSD(v4group));
ASSERT(!(ill->ill_isv6));
addr = (uchar_t *)&v4group;
} else {
ASSERT(IN6_IS_ADDR_MULTICAST(v6group));
ASSERT(ill->ill_isv6);
addr = (uchar_t *)v6group;
}
hw_addr = mi_offset_paramc(mp, hw_addr_offset, hw_addr_len);
if (hw_addr == NULL) {
ip0dbg(("ndp_mcastreq NULL hw_addr\n"));
freemsg(mp);
return (NULL);
}
ip_mcast_mapping(ill, addr, hw_addr);
return (mp);
}
void
ip_ndp_resolve(ncec_t *ncec)
{
in_addr_t sender4 = INADDR_ANY;
in6_addr_t sender6 = ipv6_all_zeros;
ill_t *src_ill;
uint32_t ms;
src_ill = nce_resolve_src(ncec, &sender6);
if (src_ill == NULL) {
ms = ncec->ncec_ill->ill_reachable_retrans_time;
nce_restart_timer(ncec, (clock_t)ms);
return;
}
if (ncec->ncec_ipversion == IPV4_VERSION)
IN6_V4MAPPED_TO_IPADDR(&sender6, sender4);
mutex_enter(&ncec->ncec_lock);
if (ncec->ncec_ipversion == IPV6_VERSION)
ms = ndp_solicit(ncec, sender6, src_ill);
else
ms = arp_request(ncec, sender4, src_ill);
mutex_exit(&ncec->ncec_lock);
if (ms == 0) {
if (ncec->ncec_state != ND_REACHABLE) {
if (ncec->ncec_ipversion == IPV6_VERSION)
ndp_resolv_failed(ncec);
else
arp_resolv_failed(ncec);
ASSERT((ncec->ncec_flags & NCE_F_STATIC) == 0);
nce_make_unreachable(ncec);
ncec_delete(ncec);
}
} else {
nce_restart_timer(ncec, (clock_t)ms);
}
ill_refrele(src_ill);
}
uint32_t
ndp_solicit(ncec_t *ncec, in6_addr_t src, ill_t *ill)
{
in6_addr_t dst;
boolean_t dropped = B_FALSE;
ASSERT(ncec->ncec_ipversion == IPV6_VERSION);
ASSERT(MUTEX_HELD(&ncec->ncec_lock));
if (ncec->ncec_rcnt == 0)
return (0);
dst = ncec->ncec_addr;
ncec->ncec_rcnt--;
mutex_exit(&ncec->ncec_lock);
dropped = ndp_xmit(ill, ND_NEIGHBOR_SOLICIT, ill->ill_phys_addr,
ill->ill_phys_addr_length, &src, &dst, 0);
mutex_enter(&ncec->ncec_lock);
if (dropped)
ncec->ncec_rcnt++;
return (ncec->ncec_ill->ill_reachable_retrans_time);
}
void
ip_addr_recover(ipsq_t *ipsq, queue_t *rq, mblk_t *mp, void *dummy_arg)
{
ill_t *ill = rq->q_ptr;
ipif_t *ipif;
in6_addr_t *addr6 = (in6_addr_t *)mp->b_rptr;
in_addr_t *addr4 = (in_addr_t *)mp->b_rptr;
boolean_t addr_equal;
for (ipif = ill->ill_ipif; ipif != NULL; ipif = ipif->ipif_next) {
if (ill->ill_isv6) {
addr_equal = IN6_ARE_ADDR_EQUAL(&ipif->ipif_v6lcl_addr,
addr6);
} else {
addr_equal = (ipif->ipif_lcl_addr == *addr4);
}
if ((ipif->ipif_flags & IPIF_POINTOPOINT) || !addr_equal)
continue;
mutex_enter(&ill->ill_lock);
if (!(ipif->ipif_flags & IPIF_DUPLICATE) ||
(ipif->ipif_state_flags & IPIF_CONDEMNED)) {
mutex_exit(&ill->ill_lock);
continue;
}
ipif->ipif_flags &= ~IPIF_DUPLICATE;
ill->ill_ipif_dup_count--;
mutex_exit(&ill->ill_lock);
ipif->ipif_was_dup = B_TRUE;
if (ill->ill_isv6) {
VERIFY(ipif_ndp_up(ipif, B_TRUE) != EINPROGRESS);
(void) ipif_up_done_v6(ipif);
} else {
VERIFY(ipif_arp_up(ipif, Res_act_initial, B_TRUE) !=
EINPROGRESS);
(void) ipif_up_done(ipif);
}
}
freeb(mp);
}
void
ipif_dup_recovery(void *arg)
{
ipif_t *ipif = arg;
ipif->ipif_recovery_id = 0;
if (!(ipif->ipif_flags & IPIF_DUPLICATE))
return;
if (ipif->ipif_state_flags & IPIF_CONDEMNED)
return;
if (!(ipif->ipif_ill->ill_phyint->phyint_flags & PHYI_RUNNING))
return;
ipif_do_recovery(ipif);
}
void
ipif_do_recovery(ipif_t *ipif)
{
ill_t *ill = ipif->ipif_ill;
mblk_t *mp;
ip_stack_t *ipst = ill->ill_ipst;
size_t mp_size;
if (ipif->ipif_isv6)
mp_size = sizeof (ipif->ipif_v6lcl_addr);
else
mp_size = sizeof (ipif->ipif_lcl_addr);
mp = allocb(mp_size, BPRI_MED);
if (mp == NULL) {
mutex_enter(&ill->ill_lock);
if (ipst->ips_ip_dup_recovery > 0 &&
ipif->ipif_recovery_id == 0 &&
!(ipif->ipif_state_flags & IPIF_CONDEMNED)) {
ipif->ipif_recovery_id = timeout(ipif_dup_recovery,
ipif, MSEC_TO_TICK(ipst->ips_ip_dup_recovery));
}
mutex_exit(&ill->ill_lock);
} else {
if (ipif->ipif_recovery_id != 0)
(void) untimeout(ipif->ipif_recovery_id);
ipif->ipif_recovery_id = 0;
if (ipif->ipif_isv6) {
bcopy(&ipif->ipif_v6lcl_addr, mp->b_rptr,
sizeof (ipif->ipif_v6lcl_addr));
} else {
bcopy(&ipif->ipif_lcl_addr, mp->b_rptr,
sizeof (ipif->ipif_lcl_addr));
}
ill_refhold(ill);
qwriter_ip(ill, ill->ill_rq, mp, ip_addr_recover, NEW_OP,
B_FALSE);
}
}
static void
ip_ndp_find_addresses(mblk_t *mp, ip_recv_attr_t *ira, ill_t *ill,
in6_addr_t *targp, uchar_t **haddr, uint_t *haddrlenp)
{
icmp6_t *icmp6 = (icmp6_t *)(mp->b_rptr + IPV6_HDR_LEN);
nd_neighbor_solicit_t *ns = (nd_neighbor_solicit_t *)icmp6;
uchar_t *addr;
int alen;
ASSERT(ira->ira_flags & IRAF_L2SRC_SET);
addr = ira->ira_l2src;
alen = ill->ill_phys_addr_length;
if (alen > 0) {
*haddr = addr;
*haddrlenp = alen;
} else {
*haddr = NULL;
*haddrlenp = 0;
}
*targp = ns->nd_ns_target;
}
static void
ip_ndp_excl(ipsq_t *ipsq, queue_t *rq, mblk_t *mp, void *dummy_arg)
{
ill_t *ill = rq->q_ptr;
ipif_t *ipif;
uchar_t *haddr;
uint_t haddrlen;
ip_stack_t *ipst = ill->ill_ipst;
in6_addr_t targ;
ip_recv_attr_t iras;
mblk_t *attrmp;
attrmp = mp;
mp = mp->b_cont;
attrmp->b_cont = NULL;
if (!ip_recv_attr_from_mblk(attrmp, &iras)) {
BUMP_MIB(ill->ill_ip_mib, ipIfStatsInDiscards);
ip_drop_input("ip_recv_attr_from_mblk", mp, ill);
freemsg(mp);
ira_cleanup(&iras, B_TRUE);
return;
}
ASSERT(ill == iras.ira_rill);
ip_ndp_find_addresses(mp, &iras, ill, &targ, &haddr, &haddrlen);
if (haddr != NULL && haddrlen == ill->ill_phys_addr_length) {
rw_enter(&ipst->ips_ill_g_lock, RW_READER);
if (bcmp(haddr, ill->ill_phys_addr, haddrlen) == 0 ||
IS_UNDER_IPMP(ill) &&
ipmp_illgrp_find_ill(ill->ill_grp, haddr,
haddrlen) != NULL) {
rw_exit(&ipst->ips_ill_g_lock);
goto ignore_conflict;
}
rw_exit(&ipst->ips_ill_g_lock);
}
ipif = ipif_lookup_addr_v6(&targ, ill, ALL_ZONES, ipst);
if (ipif == NULL)
goto ignore_conflict;
ill = ipif->ipif_ill;
if (ipif->ipif_flags & (IPIF_POINTOPOINT|IPIF_DUPLICATE)) {
ipif_refrele(ipif);
goto ignore_conflict;
}
if (!ipif->ipif_was_dup) {
char ibuf[LIFNAMSIZ];
char hbuf[MAC_STR_LEN];
char sbuf[INET6_ADDRSTRLEN];
ipif_get_name(ipif, ibuf, sizeof (ibuf));
cmn_err(CE_WARN, "%s has duplicate address %s (in use by %s);"
" disabled", ibuf,
inet_ntop(AF_INET6, &targ, sbuf, sizeof (sbuf)),
mac_colon_addr(haddr, haddrlen, hbuf, sizeof (hbuf)));
}
mutex_enter(&ill->ill_lock);
ASSERT(!(ipif->ipif_flags & IPIF_DUPLICATE));
ipif->ipif_flags |= IPIF_DUPLICATE;
ill->ill_ipif_dup_count++;
mutex_exit(&ill->ill_lock);
(void) ipif_down(ipif, NULL, NULL);
(void) ipif_down_tail(ipif);
mutex_enter(&ill->ill_lock);
if (!(ipif->ipif_flags & (IPIF_DHCPRUNNING|IPIF_TEMPORARY)) &&
ill->ill_net_type == IRE_IF_RESOLVER &&
!(ipif->ipif_state_flags & IPIF_CONDEMNED) &&
ipst->ips_ip_dup_recovery > 0) {
ASSERT(ipif->ipif_recovery_id == 0);
ipif->ipif_recovery_id = timeout(ipif_dup_recovery,
ipif, MSEC_TO_TICK(ipst->ips_ip_dup_recovery));
}
mutex_exit(&ill->ill_lock);
ipif_refrele(ipif);
ignore_conflict:
freemsg(mp);
ira_cleanup(&iras, B_TRUE);
}
static void
ndp_failure(mblk_t *mp, ip_recv_attr_t *ira)
{
const uchar_t *haddr;
ill_t *ill = ira->ira_rill;
ASSERT(ira->ira_flags & IRAF_L2SRC_SET);
haddr = ira->ira_l2src;
if (haddr != NULL &&
bcmp(haddr, ill->ill_phys_addr, ill->ill_phys_addr_length) == 0) {
return;
}
if ((mp = copymsg(mp)) != NULL) {
mblk_t *attrmp;
attrmp = ip_recv_attr_to_mblk(ira);
if (attrmp == NULL) {
BUMP_MIB(ill->ill_ip_mib, ipIfStatsInDiscards);
ip_drop_input("ipIfStatsInDiscards", mp, ill);
freemsg(mp);
} else {
ASSERT(attrmp->b_cont == NULL);
attrmp->b_cont = mp;
mp = attrmp;
ill_refhold(ill);
qwriter_ip(ill, ill->ill_rq, mp, ip_ndp_excl, NEW_OP,
B_FALSE);
}
}
}
boolean_t
ip_nce_conflict(mblk_t *mp, ip_recv_attr_t *ira, ncec_t *ncec)
{
ipif_t *ipif;
clock_t now;
uint_t maxdefense;
uint_t defs;
ill_t *ill = ira->ira_ill;
ip_stack_t *ipst = ill->ill_ipst;
uint32_t elapsed;
boolean_t isv6 = ill->ill_isv6;
ipaddr_t ncec_addr;
if (isv6) {
ipif = ipif_lookup_addr_v6(&ncec->ncec_addr, ill, ALL_ZONES,
ipst);
} else {
if (arp_no_defense) {
return (B_TRUE);
}
IN6_V4MAPPED_TO_IPADDR(&ncec->ncec_addr, ncec_addr);
ipif = ipif_lookup_addr(ncec_addr, ill, ALL_ZONES,
ipst);
}
if (ipif == NULL)
return (B_FALSE);
if (ipif->ipif_flags & (IPIF_DHCPRUNNING | IPIF_TEMPORARY))
maxdefense = ipst->ips_ip_max_temp_defend;
else
maxdefense = ipst->ips_ip_max_defend;
now = ddi_get_lbolt();
elapsed = (drv_hztousec(now - ncec->ncec_last_time_defended))/1000000;
mutex_enter(&ncec->ncec_lock);
if ((defs = ncec->ncec_defense_count) > 0 &&
elapsed > ipst->ips_ip_defend_interval) {
ncec->ncec_defense_count = defs = 0;
}
ncec->ncec_defense_count++;
ncec->ncec_last_time_defended = now;
mutex_exit(&ncec->ncec_lock);
ipif_refrele(ipif);
if (defs >= maxdefense) {
if (isv6)
ndp_failure(mp, ira);
else
arp_failure(mp, ira);
} else {
return (B_TRUE);
}
return (B_FALSE);
}
static void
ndp_input_solicit(mblk_t *mp, ip_recv_attr_t *ira)
{
ill_t *ill = ira->ira_ill, *under_ill;
nd_neighbor_solicit_t *ns;
uint32_t hlen = ill->ill_phys_addr_length;
uchar_t *haddr = NULL;
icmp6_t *icmp_nd;
ip6_t *ip6h;
ncec_t *our_ncec = NULL;
in6_addr_t target;
in6_addr_t src;
int len;
int flag = 0;
nd_opt_hdr_t *opt = NULL;
boolean_t bad_solicit = B_FALSE;
mib2_ipv6IfIcmpEntry_t *mib = ill->ill_icmp6_mib;
boolean_t need_ill_refrele = B_FALSE;
ip6h = (ip6_t *)mp->b_rptr;
icmp_nd = (icmp6_t *)(mp->b_rptr + IPV6_HDR_LEN);
len = mp->b_wptr - mp->b_rptr - IPV6_HDR_LEN;
src = ip6h->ip6_src;
ns = (nd_neighbor_solicit_t *)icmp_nd;
target = ns->nd_ns_target;
if (IN6_IS_ADDR_MULTICAST(&target) || IN6_IS_ADDR_V4MAPPED(&target) ||
IN6_IS_ADDR_LOOPBACK(&target)) {
if (ip_debug > 2) {
pr_addr_dbg("ndp_input_solicit: Martian Target %s\n",
AF_INET6, &target);
}
bad_solicit = B_TRUE;
goto done;
}
if (len > sizeof (nd_neighbor_solicit_t)) {
opt = (nd_opt_hdr_t *)&ns[1];
len -= sizeof (nd_neighbor_solicit_t);
if (!ndp_verify_optlen(opt, len)) {
ip1dbg(("ndp_input_solicit: Bad opt len\n"));
bad_solicit = B_TRUE;
goto done;
}
}
if (IN6_IS_ADDR_UNSPECIFIED(&src)) {
if (!IN6_IS_ADDR_MC_SOLICITEDNODE(&ip6h->ip6_dst)) {
if (ip_debug > 2) {
pr_addr_dbg("ndp_input_solicit: IPv6 "
"Destination is not solicited node "
"multicast %s\n", AF_INET6,
&ip6h->ip6_dst);
}
bad_solicit = B_TRUE;
goto done;
}
}
our_ncec = ncec_lookup_illgrp_v6(ill, &target);
if (our_ncec == NULL || !NCE_PUBLISH(our_ncec)) {
ip1dbg(("ndp_input_solicit: Wrong target in NS?!"
"ifname=%s ", ill->ill_name));
if (ip_debug > 2) {
pr_addr_dbg(" dst %s\n", AF_INET6, &target);
}
if (our_ncec == NULL)
bad_solicit = B_TRUE;
goto done;
}
if (opt != NULL) {
opt = ndp_get_option(opt, len, ND_OPT_SOURCE_LINKADDR);
if (opt != NULL) {
haddr = (uchar_t *)&opt[1];
if (hlen > opt->nd_opt_len * 8 - sizeof (*opt) ||
hlen == 0) {
ip1dbg(("ndp_input_advert: bad SLLA\n"));
bad_solicit = B_TRUE;
goto done;
}
}
}
if (!IN6_IS_ADDR_MULTICAST(&ip6h->ip6_dst))
flag |= NDP_UNICAST;
if (!IN6_IS_ADDR_UNSPECIFIED(&src)) {
int err;
nce_t *nnce;
ASSERT(ill->ill_isv6);
if (haddr == NULL && IN6_IS_ADDR_MULTICAST(&ip6h->ip6_dst)) {
ip1dbg(("ndp_input_solicit: source link-layer address "
"option missing with a specified source.\n"));
bad_solicit = B_TRUE;
goto done;
}
if (our_ncec->ncec_state == ND_PROBE)
goto done;
if (haddr == NULL)
goto no_source;
under_ill = ill;
if (IS_UNDER_IPMP(under_ill)) {
ill = ipmp_ill_hold_ipmp_ill(under_ill);
if (ill == NULL)
ill = under_ill;
else
need_ill_refrele = B_TRUE;
}
err = nce_lookup_then_add_v6(ill,
haddr, hlen,
&src,
0,
ND_STALE,
&nnce);
if (need_ill_refrele) {
ill_refrele(ill);
ill = under_ill;
need_ill_refrele = B_FALSE;
}
switch (err) {
case 0:
nce_refrele(nnce);
break;
case EEXIST:
nce_process(nnce->nce_common, haddr, 0, B_FALSE);
nce_refrele(nnce);
break;
default:
ip1dbg(("ndp_input_solicit: Can't create NCE %d\n",
err));
goto done;
}
no_source:
flag |= NDP_SOLICITED;
} else {
if (haddr != NULL) {
ip1dbg(("ndp_input_solicit: source link-layer address "
"option present with an unspecified source.\n"));
bad_solicit = B_TRUE;
goto done;
}
if (our_ncec->ncec_state == ND_PROBE) {
if (!(ira->ira_flags & IRAF_L2SRC_LOOPBACK)) {
ndp_failure(mp, ira);
}
goto done;
}
src = ipv6_all_hosts_mcast;
}
flag |= nce_advert_flags(our_ncec);
(void) ndp_xmit(ill,
ND_NEIGHBOR_ADVERT,
our_ncec->ncec_lladdr,
our_ncec->ncec_lladdr_length,
&target,
&src,
flag);
done:
if (bad_solicit)
BUMP_MIB(mib, ipv6IfIcmpInBadNeighborSolicitations);
if (our_ncec != NULL)
ncec_refrele(our_ncec);
}
void
ndp_input_advert(mblk_t *mp, ip_recv_attr_t *ira)
{
ill_t *ill = ira->ira_ill;
nd_neighbor_advert_t *na;
uint32_t hlen = ill->ill_phys_addr_length;
uchar_t *haddr = NULL;
icmp6_t *icmp_nd;
ip6_t *ip6h;
ncec_t *dst_ncec = NULL;
in6_addr_t target;
nd_opt_hdr_t *opt = NULL;
int len;
ip_stack_t *ipst = ill->ill_ipst;
mib2_ipv6IfIcmpEntry_t *mib = ill->ill_icmp6_mib;
ip6h = (ip6_t *)mp->b_rptr;
icmp_nd = (icmp6_t *)(mp->b_rptr + IPV6_HDR_LEN);
len = mp->b_wptr - mp->b_rptr - IPV6_HDR_LEN;
na = (nd_neighbor_advert_t *)icmp_nd;
if (IN6_IS_ADDR_MULTICAST(&ip6h->ip6_dst) &&
(na->nd_na_flags_reserved & ND_NA_FLAG_SOLICITED)) {
ip1dbg(("ndp_input_advert: Target is multicast but the "
"solicited flag is not zero\n"));
BUMP_MIB(mib, ipv6IfIcmpInBadNeighborAdvertisements);
return;
}
target = na->nd_na_target;
if (IN6_IS_ADDR_MULTICAST(&target) || IN6_IS_ADDR_V4MAPPED(&target) ||
IN6_IS_ADDR_LOOPBACK(&target)) {
if (ip_debug > 2) {
pr_addr_dbg("ndp_input_solicit: Martian Target %s\n",
AF_INET6, &target);
}
BUMP_MIB(mib, ipv6IfIcmpInBadNeighborAdvertisements);
return;
}
if (len > sizeof (nd_neighbor_advert_t)) {
opt = (nd_opt_hdr_t *)&na[1];
if (!ndp_verify_optlen(opt,
len - sizeof (nd_neighbor_advert_t))) {
ip1dbg(("ndp_input_advert: cannot verify SLLA\n"));
BUMP_MIB(mib, ipv6IfIcmpInBadNeighborAdvertisements);
return;
}
len -= sizeof (nd_neighbor_advert_t);
opt = ndp_get_option(opt, len, ND_OPT_TARGET_LINKADDR);
if (opt != NULL) {
haddr = (uchar_t *)&opt[1];
if (hlen > opt->nd_opt_len * 8 - sizeof (*opt) ||
hlen == 0) {
ip1dbg(("ndp_input_advert: bad SLLA\n"));
BUMP_MIB(mib,
ipv6IfIcmpInBadNeighborAdvertisements);
return;
}
}
}
if ((dst_ncec = ncec_lookup_illgrp_v6(ill, &target)) == NULL)
return;
if (NCE_PUBLISH(dst_ncec)) {
if (haddr != NULL) {
if (ira->ira_flags & IRAF_L2SRC_LOOPBACK)
goto out;
if (!nce_cmp_ll_addr(dst_ncec, haddr, hlen))
goto out;
if (IS_UNDER_IPMP(ill)) {
rw_enter(&ipst->ips_ill_g_lock, RW_READER);
if (IS_UNDER_IPMP(ill) && ipmp_illgrp_find_ill(
ill->ill_grp, haddr, hlen) != NULL) {
rw_exit(&ipst->ips_ill_g_lock);
goto out;
}
rw_exit(&ipst->ips_ill_g_lock);
}
}
if (dst_ncec->ncec_state == ND_PROBE) {
ndp_failure(mp, ira);
} else {
if (ip_nce_conflict(mp, ira, dst_ncec)) {
char hbuf[MAC_STR_LEN];
char sbuf[INET6_ADDRSTRLEN];
cmn_err(CE_WARN,
"node '%s' is using %s on %s",
inet_ntop(AF_INET6, &target, sbuf,
sizeof (sbuf)),
haddr == NULL ? "<none>" :
mac_colon_addr(haddr, hlen, hbuf,
sizeof (hbuf)), ill->ill_name);
(void) ndp_announce(dst_ncec);
}
}
} else {
if (na->nd_na_flags_reserved & ND_NA_FLAG_ROUTER)
dst_ncec->ncec_flags |= NCE_F_ISROUTER;
nce_process(dst_ncec, haddr, na->nd_na_flags_reserved, B_TRUE);
}
out:
ncec_refrele(dst_ncec);
}
void
ndp_input(mblk_t *mp, ip_recv_attr_t *ira)
{
ill_t *ill = ira->ira_rill;
icmp6_t *icmp_nd;
ip6_t *ip6h;
int len;
mib2_ipv6IfIcmpEntry_t *mib = ill->ill_icmp6_mib;
ill_t *orig_ill = NULL;
if (IS_UNDER_IPMP(ill)) {
orig_ill = ill;
ill = ipmp_ill_hold_ipmp_ill(orig_ill);
if (ill == NULL) {
ill = orig_ill;
BUMP_MIB(ill->ill_ip_mib, ipIfStatsInDiscards);
ip_drop_input("ipIfStatsInDiscards - IPMP ill",
mp, ill);
freemsg(mp);
return;
}
ASSERT(ill != orig_ill);
orig_ill = ira->ira_ill;
ira->ira_ill = ill;
mib = ill->ill_icmp6_mib;
}
if (!pullupmsg(mp, -1)) {
ip1dbg(("ndp_input: pullupmsg failed\n"));
BUMP_MIB(ill->ill_ip_mib, ipIfStatsInDiscards);
ip_drop_input("ipIfStatsInDiscards - pullupmsg", mp, ill);
goto done;
}
ip6h = (ip6_t *)mp->b_rptr;
if (ip6h->ip6_hops != IPV6_MAX_HOPS) {
ip1dbg(("ndp_input: hoplimit != IPV6_MAX_HOPS\n"));
ip_drop_input("ipv6IfIcmpBadHoplimit", mp, ill);
BUMP_MIB(mib, ipv6IfIcmpBadHoplimit);
goto done;
}
if (ip6h->ip6_nxt != IPPROTO_ICMPV6) {
ip1dbg(("ndp_input: Wrong next header 0x%x\n",
ip6h->ip6_nxt));
ip_drop_input("Wrong next header", mp, ill);
BUMP_MIB(mib, ipv6IfIcmpInErrors);
goto done;
}
icmp_nd = (icmp6_t *)(mp->b_rptr + IPV6_HDR_LEN);
ASSERT(icmp_nd->icmp6_type == ND_NEIGHBOR_SOLICIT ||
icmp_nd->icmp6_type == ND_NEIGHBOR_ADVERT);
if (icmp_nd->icmp6_code != 0) {
ip1dbg(("ndp_input: icmp6 code != 0 \n"));
ip_drop_input("code non-zero", mp, ill);
BUMP_MIB(mib, ipv6IfIcmpInErrors);
goto done;
}
len = mp->b_wptr - mp->b_rptr - IPV6_HDR_LEN;
if (len < sizeof (struct icmp6_hdr) + sizeof (struct in6_addr)) {
ip1dbg(("ndp_input: packet too short\n"));
ip_drop_input("packet too short", mp, ill);
BUMP_MIB(mib, ipv6IfIcmpInErrors);
goto done;
}
if (icmp_nd->icmp6_type == ND_NEIGHBOR_SOLICIT) {
ndp_input_solicit(mp, ira);
} else {
ndp_input_advert(mp, ira);
}
done:
freemsg(mp);
if (orig_ill != NULL) {
ill_refrele(ill);
ira->ira_ill = orig_ill;
}
}
static boolean_t
ndp_xmit(ill_t *ill, uint32_t operation, uint8_t *hw_addr, uint_t hw_addr_len,
const in6_addr_t *sender, const in6_addr_t *target, int flag)
{
uint32_t len;
icmp6_t *icmp6;
mblk_t *mp;
ip6_t *ip6h;
nd_opt_hdr_t *opt;
uint_t plen;
zoneid_t zoneid = GLOBAL_ZONEID;
ill_t *hwaddr_ill = ill;
ip_xmit_attr_t ixas;
ip_stack_t *ipst = ill->ill_ipst;
boolean_t need_refrele = B_FALSE;
boolean_t probe = B_FALSE;
if (IS_UNDER_IPMP(ill)) {
probe = ipif_lookup_testaddr_v6(ill, sender, NULL);
if (!probe) {
ill = ipmp_ill_hold_ipmp_ill(ill);
if (ill != NULL)
need_refrele = B_TRUE;
else
ill = hwaddr_ill;
}
}
if (!(IN6_IS_ADDR_UNSPECIFIED(sender))) {
zoneid = ipif_lookup_addr_zoneid_v6(sender, ill, ipst);
if (zoneid == ALL_ZONES)
zoneid = GLOBAL_ZONEID;
}
plen = (sizeof (nd_opt_hdr_t) + hw_addr_len + 7) / 8;
len = IPV6_HDR_LEN + sizeof (nd_neighbor_advert_t) + plen * 8;
mp = allocb(len, BPRI_LO);
if (mp == NULL) {
if (need_refrele)
ill_refrele(ill);
return (B_TRUE);
}
bzero((char *)mp->b_rptr, len);
mp->b_wptr = mp->b_rptr + len;
bzero(&ixas, sizeof (ixas));
ixas.ixa_flags = IXAF_SET_ULP_CKSUM | IXAF_NO_HW_CKSUM;
ixas.ixa_ifindex = ill->ill_phyint->phyint_ifindex;
ixas.ixa_ipst = ipst;
ixas.ixa_cred = kcred;
ixas.ixa_cpid = NOPID;
ixas.ixa_tsl = NULL;
ixas.ixa_zoneid = zoneid;
ip6h = (ip6_t *)mp->b_rptr;
ip6h->ip6_vcf = IPV6_DEFAULT_VERS_AND_FLOW;
ip6h->ip6_plen = htons(len - IPV6_HDR_LEN);
ip6h->ip6_nxt = IPPROTO_ICMPV6;
ip6h->ip6_hops = IPV6_MAX_HOPS;
ixas.ixa_multicast_ttl = ip6h->ip6_hops;
ip6h->ip6_dst = *target;
icmp6 = (icmp6_t *)&ip6h[1];
if (hw_addr_len != 0) {
opt = (nd_opt_hdr_t *)((uint8_t *)ip6h + IPV6_HDR_LEN +
sizeof (nd_neighbor_advert_t));
} else {
opt = NULL;
}
if (operation == ND_NEIGHBOR_SOLICIT) {
nd_neighbor_solicit_t *ns = (nd_neighbor_solicit_t *)icmp6;
if (opt != NULL && !(flag & NDP_PROBE)) {
opt->nd_opt_type = ND_OPT_SOURCE_LINKADDR;
}
ip6h->ip6_src = *sender;
ns->nd_ns_target = *target;
if (!(flag & NDP_UNICAST)) {
ip6h->ip6_dst = ipv6_solicited_node_mcast;
ip6h->ip6_dst.s6_addr32[3] |=
ns->nd_ns_target.s6_addr32[3];
}
} else {
nd_neighbor_advert_t *na = (nd_neighbor_advert_t *)icmp6;
ASSERT(!(flag & NDP_PROBE));
if (opt != NULL)
opt->nd_opt_type = ND_OPT_TARGET_LINKADDR;
ip6h->ip6_src = *sender;
na->nd_na_target = *sender;
if (flag & NDP_ISROUTER)
na->nd_na_flags_reserved |= ND_NA_FLAG_ROUTER;
if (flag & NDP_SOLICITED)
na->nd_na_flags_reserved |= ND_NA_FLAG_SOLICITED;
if (flag & NDP_ORIDE)
na->nd_na_flags_reserved |= ND_NA_FLAG_OVERRIDE;
}
if (!(flag & NDP_PROBE)) {
if (hw_addr != NULL && opt != NULL) {
opt->nd_opt_len = (uint8_t)plen;
bcopy(hw_addr, &opt[1], hw_addr_len);
}
}
if (opt != NULL && opt->nd_opt_type == 0) {
len -= plen * 8;
mp->b_wptr = mp->b_rptr + len;
ip6h->ip6_plen = htons(len - IPV6_HDR_LEN);
}
icmp6->icmp6_type = (uint8_t)operation;
icmp6->icmp6_code = 0;
icmp6->icmp6_cksum = ip6h->ip6_plen;
(void) ip_output_simple(mp, &ixas);
ixa_cleanup(&ixas);
if (need_refrele)
ill_refrele(ill);
return (B_FALSE);
}
void
nce_make_unreachable(ncec_t *ncec)
{
mutex_enter(&ncec->ncec_lock);
ncec->ncec_state = ND_UNREACHABLE;
mutex_exit(&ncec->ncec_lock);
}
void
nce_timer(void *arg)
{
ncec_t *ncec = arg;
ill_t *ill = ncec->ncec_ill, *src_ill;
char addrbuf[INET6_ADDRSTRLEN];
boolean_t dropped = B_FALSE;
ip_stack_t *ipst = ncec->ncec_ipst;
boolean_t isv6 = (ncec->ncec_ipversion == IPV6_VERSION);
in_addr_t sender4 = INADDR_ANY;
in6_addr_t sender6 = ipv6_all_zeros;
ASSERT(ncec != NULL);
mutex_enter(&ncec->ncec_lock);
ncec_refhold_locked(ncec);
ncec->ncec_timeout_id = 0;
mutex_exit(&ncec->ncec_lock);
src_ill = nce_resolve_src(ncec, &sender6);
if (src_ill == NULL) {
if (!isv6) {
IN6_V4MAPPED_TO_IPADDR(&ncec->ncec_addr, sender4);
ip1dbg(("no src ill for %s\n", inet_ntop(AF_INET,
&sender4, addrbuf, sizeof (addrbuf))));
} else {
ip1dbg(("no src ill for %s\n", inet_ntop(AF_INET6,
&ncec->ncec_addr, addrbuf, sizeof (addrbuf))));
}
nce_restart_timer(ncec, ill->ill_reachable_retrans_time);
ncec_refrele(ncec);
return;
}
if (!isv6)
IN6_V4MAPPED_TO_IPADDR(&sender6, sender4);
mutex_enter(&ncec->ncec_lock);
switch (ncec->ncec_state) {
case ND_DELAY:
ASSERT(ncec->ncec_lladdr != NULL);
ncec->ncec_state = ND_PROBE;
ncec->ncec_pcnt = ND_MAX_UNICAST_SOLICIT;
if (isv6) {
mutex_exit(&ncec->ncec_lock);
dropped = ndp_xmit(src_ill, ND_NEIGHBOR_SOLICIT,
src_ill->ill_phys_addr,
src_ill->ill_phys_addr_length,
&sender6, &ncec->ncec_addr,
NDP_UNICAST);
} else {
dropped = (arp_request(ncec, sender4, src_ill) == 0);
mutex_exit(&ncec->ncec_lock);
}
if (!dropped) {
mutex_enter(&ncec->ncec_lock);
ncec->ncec_pcnt--;
mutex_exit(&ncec->ncec_lock);
}
if (ip_debug > 3) {
pr_addr_dbg("nce_timer: state for %s changed "
"to PROBE\n", AF_INET6, &ncec->ncec_addr);
}
nce_restart_timer(ncec, ill->ill_reachable_retrans_time);
break;
case ND_PROBE:
ASSERT(ncec->ncec_pcnt >= -1);
if (ncec->ncec_pcnt > 0) {
ip2dbg(("nce_timer: pcount=%x dst %s\n",
ncec->ncec_pcnt,
inet_ntop((isv6? AF_INET6 : AF_INET),
&ncec->ncec_addr, addrbuf, sizeof (addrbuf))));
if (NCE_PUBLISH(ncec)) {
mutex_exit(&ncec->ncec_lock);
nce_dad(ncec, src_ill, B_TRUE);
} else {
ASSERT(src_ill != NULL);
if (isv6) {
mutex_exit(&ncec->ncec_lock);
dropped = ndp_xmit(src_ill,
ND_NEIGHBOR_SOLICIT,
src_ill->ill_phys_addr,
src_ill->ill_phys_addr_length,
&sender6, &ncec->ncec_addr,
NDP_UNICAST);
} else {
dropped = (arp_request(ncec, sender4,
src_ill) == 0);
mutex_exit(&ncec->ncec_lock);
}
if (!dropped) {
mutex_enter(&ncec->ncec_lock);
ncec->ncec_pcnt--;
mutex_exit(&ncec->ncec_lock);
}
nce_restart_timer(ncec,
ill->ill_reachable_retrans_time);
}
} else if (ncec->ncec_pcnt < 0) {
ncec->ncec_state = ND_UNREACHABLE;
mutex_exit(&ncec->ncec_lock);
if (ip_debug > 2) {
pr_addr_dbg("nce_timer: Delete NCE for"
" dst %s\n", (isv6? AF_INET6: AF_INET),
&ncec->ncec_addr);
}
if ((ncec->ncec_flags & NCE_F_STATIC) == 0)
ncec_delete(ncec);
} else if (!NCE_PUBLISH(ncec)) {
ASSERT((ncec->ncec_flags & NCE_F_NONUD) == 0);
ip2dbg(("nce_timer: pcount=%x dst %s\n",
ncec->ncec_pcnt, inet_ntop(AF_INET6,
&ncec->ncec_addr, addrbuf, sizeof (addrbuf))));
ncec->ncec_pcnt--;
mutex_exit(&ncec->ncec_lock);
nce_restart_timer(ncec,
ill->ill_reachable_retrans_time);
} else if (ill->ill_phyint->phyint_flags & PHYI_RUNNING) {
ipif_t *ipif;
ipaddr_t ncec_addr;
ncec->ncec_state = ND_REACHABLE;
ncec->ncec_flags &= ~NCE_F_UNVERIFIED;
mutex_exit(&ncec->ncec_lock);
if (isv6) {
ipif = ipif_lookup_addr_exact_v6(
&ncec->ncec_addr, ill, ipst);
} else {
IN6_V4MAPPED_TO_IPADDR(&ncec->ncec_addr,
ncec_addr);
ipif = ipif_lookup_addr_exact(ncec_addr, ill,
ipst);
}
if (ipif != NULL) {
if (ipif->ipif_was_dup) {
char ibuf[LIFNAMSIZ];
char sbuf[INET6_ADDRSTRLEN];
ipif->ipif_was_dup = B_FALSE;
(void) inet_ntop(AF_INET6,
&ipif->ipif_v6lcl_addr,
sbuf, sizeof (sbuf));
ipif_get_name(ipif, ibuf,
sizeof (ibuf));
cmn_err(CE_NOTE, "recovered address "
"%s on %s", sbuf, ibuf);
}
if ((ipif->ipif_flags & IPIF_UP) &&
!ipif->ipif_addr_ready)
ipif_up_notify(ipif);
ipif->ipif_addr_ready = 1;
ipif_refrele(ipif);
}
if (!isv6 && arp_no_defense)
break;
if (ncec->ncec_unsolicit_count > 0) {
ncec->ncec_unsolicit_count--;
if (isv6) {
dropped = ndp_announce(ncec);
} else {
dropped = arp_announce(ncec);
}
if (dropped)
ncec->ncec_unsolicit_count++;
else
ncec->ncec_last_time_defended =
ddi_get_lbolt();
}
if (ncec->ncec_unsolicit_count > 0) {
nce_restart_timer(ncec,
ANNOUNCE_INTERVAL(isv6));
} else if (DEFENSE_INTERVAL(isv6) != 0) {
nce_restart_timer(ncec, DEFENSE_INTERVAL(isv6));
}
} else {
ncec->ncec_state = ND_REACHABLE;
mutex_exit(&ncec->ncec_lock);
}
break;
case ND_INCOMPLETE: {
mblk_t *mp, *nextmp;
mblk_t **prevmpp;
prevmpp = &ncec->ncec_qd_mp;
for (mp = ncec->ncec_qd_mp; mp != NULL; mp = nextmp) {
nextmp = mp->b_next;
if (IS_UNDER_IPMP(ill) && ncec->ncec_nprobes > 0) {
inet_freemsg(mp);
ncec->ncec_nprobes--;
*prevmpp = nextmp;
} else {
prevmpp = &mp->b_next;
}
}
mutex_exit(&ncec->ncec_lock);
ip_ndp_resolve(ncec);
break;
}
case ND_REACHABLE:
if (((ncec->ncec_flags & NCE_F_UNSOL_ADV) &&
ncec->ncec_unsolicit_count != 0) ||
(NCE_PUBLISH(ncec) && DEFENSE_INTERVAL(isv6) != 0)) {
if (ncec->ncec_unsolicit_count > 0) {
ncec->ncec_unsolicit_count--;
mutex_exit(&ncec->ncec_lock);
} else {
boolean_t rate_limit;
mutex_exit(&ncec->ncec_lock);
rate_limit = ill_defend_rate_limit(ill, ncec);
if (rate_limit) {
nce_restart_timer(ncec,
DEFENSE_INTERVAL(isv6));
break;
}
}
if (isv6) {
dropped = ndp_announce(ncec);
} else {
dropped = arp_announce(ncec);
}
mutex_enter(&ncec->ncec_lock);
if (dropped) {
ncec->ncec_unsolicit_count++;
} else {
ncec->ncec_last_time_defended =
ddi_get_lbolt();
}
mutex_exit(&ncec->ncec_lock);
if (ncec->ncec_unsolicit_count != 0) {
nce_restart_timer(ncec,
ANNOUNCE_INTERVAL(isv6));
} else {
nce_restart_timer(ncec, DEFENSE_INTERVAL(isv6));
}
} else {
mutex_exit(&ncec->ncec_lock);
}
break;
default:
mutex_exit(&ncec->ncec_lock);
break;
}
ncec_refrele(ncec);
ill_refrele(src_ill);
}
static void
nce_set_ll(ncec_t *ncec, uchar_t *ll_addr)
{
ill_t *ill = ncec->ncec_ill;
ASSERT(ll_addr != NULL);
if (ill->ill_phys_addr_length > 0) {
bcopy(ll_addr, ncec->ncec_lladdr, ill->ill_nd_lla_len);
}
}
boolean_t
nce_cmp_ll_addr(const ncec_t *ncec, const uchar_t *ll_addr,
uint32_t ll_addr_len)
{
ASSERT(ncec->ncec_lladdr != NULL);
if (ll_addr == NULL)
return (B_FALSE);
if (bcmp(ll_addr, ncec->ncec_lladdr, ll_addr_len) != 0)
return (B_TRUE);
return (B_FALSE);
}
void
nce_update(ncec_t *ncec, uint16_t new_state, uchar_t *new_ll_addr)
{
ill_t *ill = ncec->ncec_ill;
boolean_t need_stop_timer = B_FALSE;
boolean_t need_fastpath_update = B_FALSE;
nce_t *nce = NULL;
timeout_id_t tid;
ASSERT(MUTEX_HELD(&ncec->ncec_lock));
if (new_state != ND_UNCHANGED) {
if ((ncec->ncec_flags & NCE_F_NONUD) &&
(ncec->ncec_state != ND_INCOMPLETE))
return;
ASSERT((int16_t)new_state >= ND_STATE_VALID_MIN);
ASSERT((int16_t)new_state <= ND_STATE_VALID_MAX);
need_stop_timer = B_TRUE;
if (new_state == ND_REACHABLE)
ncec->ncec_last = TICK_TO_MSEC(ddi_get_lbolt64());
else {
ncec->ncec_last = 0;
}
ncec->ncec_state = new_state;
ncec->ncec_pcnt = ND_MAX_UNICAST_SOLICIT;
ASSERT(ncec->ncec_lladdr != NULL || new_state == ND_INITIAL ||
new_state == ND_INCOMPLETE);
}
tid = 0;
if (need_stop_timer || (ncec->ncec_flags & NCE_F_STATIC)) {
tid = ncec->ncec_timeout_id;
ncec->ncec_timeout_id = 0;
}
if (new_ll_addr != NULL) {
bcopy(new_ll_addr, ncec->ncec_lladdr,
ill->ill_phys_addr_length);
need_fastpath_update = B_TRUE;
}
mutex_exit(&ncec->ncec_lock);
if (need_stop_timer || (ncec->ncec_flags & NCE_F_STATIC)) {
if (tid != 0)
(void) untimeout(tid);
}
if (need_fastpath_update) {
nce_fastpath_list_delete(ncec->ncec_ill, ncec, NULL);
nce = nce_fastpath(ncec, B_TRUE, NULL);
if (nce != NULL)
nce_refrele(nce);
}
mutex_enter(&ncec->ncec_lock);
}
static void
nce_queue_mp_common(ncec_t *ncec, mblk_t *mp, boolean_t head_insert)
{
uint_t count = 0;
mblk_t **mpp, *tmp;
ASSERT(MUTEX_HELD(&ncec->ncec_lock));
for (mpp = &ncec->ncec_qd_mp; *mpp != NULL; mpp = &(*mpp)->b_next) {
if (++count > ncec->ncec_ill->ill_max_buf) {
tmp = ncec->ncec_qd_mp->b_next;
ncec->ncec_qd_mp->b_next = NULL;
BUMP_MIB(ncec->ncec_ill->ill_ip_mib,
ipIfStatsOutDiscards);
ip_drop_output("ipIfStatsOutDiscards", ncec->ncec_qd_mp,
ncec->ncec_ill);
freemsg(ncec->ncec_qd_mp);
ncec->ncec_qd_mp = tmp;
}
}
if (head_insert) {
ncec->ncec_nprobes++;
mp->b_next = ncec->ncec_qd_mp;
ncec->ncec_qd_mp = mp;
} else {
*mpp = mp;
}
}
void
nce_queue_mp(ncec_t *ncec, mblk_t *mp, boolean_t head_insert)
{
ASSERT(MUTEX_HELD(&ncec->ncec_lock));
nce_queue_mp_common(ncec, mp, head_insert);
}
void
ndp_resolv_failed(ncec_t *ncec)
{
mblk_t *mp, *nxt_mp;
char buf[INET6_ADDRSTRLEN];
ill_t *ill = ncec->ncec_ill;
ip_recv_attr_t iras;
bzero(&iras, sizeof (iras));
iras.ira_flags = 0;
iras.ira_ill = iras.ira_rill = ill;
iras.ira_ruifindex = ill->ill_phyint->phyint_ifindex;
iras.ira_rifindex = iras.ira_ruifindex;
ip1dbg(("ndp_resolv_failed: dst %s\n",
inet_ntop(AF_INET6, (char *)&ncec->ncec_addr, buf, sizeof (buf))));
mutex_enter(&ncec->ncec_lock);
mp = ncec->ncec_qd_mp;
ncec->ncec_qd_mp = NULL;
ncec->ncec_nprobes = 0;
mutex_exit(&ncec->ncec_lock);
while (mp != NULL) {
nxt_mp = mp->b_next;
mp->b_next = NULL;
BUMP_MIB(ill->ill_ip_mib, ipIfStatsOutDiscards);
ip_drop_output("ipIfStatsOutDiscards - address unreachable",
mp, ill);
icmp_unreachable_v6(mp,
ICMP6_DST_UNREACH_ADDR, B_FALSE, &iras);
ASSERT(!(iras.ira_flags & IRAF_IPSEC_SECURE));
mp = nxt_mp;
}
ncec_cb_dispatch(ncec);
}
void
nce_resolv_ok(ncec_t *ncec)
{
mblk_t *mp;
uint_t pkt_len;
iaflags_t ixaflags = IXAF_NO_TRACE;
nce_t *nce;
ill_t *ill = ncec->ncec_ill;
boolean_t isv6 = (ncec->ncec_ipversion == IPV6_VERSION);
ip_stack_t *ipst = ill->ill_ipst;
if (IS_IPMP(ncec->ncec_ill)) {
nce_resolv_ipmp_ok(ncec);
return;
}
mutex_enter(&ncec->ncec_lock);
ASSERT(ncec->ncec_nprobes == 0);
mp = ncec->ncec_qd_mp;
ncec->ncec_qd_mp = NULL;
mutex_exit(&ncec->ncec_lock);
while (mp != NULL) {
mblk_t *nxt_mp;
if (ill->ill_isv6) {
ip6_t *ip6h = (ip6_t *)mp->b_rptr;
pkt_len = ntohs(ip6h->ip6_plen) + IPV6_HDR_LEN;
} else {
ipha_t *ipha = (ipha_t *)mp->b_rptr;
ixaflags |= IXAF_IS_IPV4;
pkt_len = ntohs(ipha->ipha_length);
}
nxt_mp = mp->b_next;
mp->b_next = NULL;
mutex_enter(&ill->ill_lock);
nce = nce_lookup(ill, &ncec->ncec_addr);
mutex_exit(&ill->ill_lock);
if (nce == NULL) {
if (isv6) {
BUMP_MIB(&ipst->ips_ip6_mib,
ipIfStatsOutDiscards);
} else {
BUMP_MIB(&ipst->ips_ip_mib,
ipIfStatsOutDiscards);
}
ip_drop_output("ipIfStatsOutDiscards - no nce",
mp, NULL);
freemsg(mp);
} else {
(void) ip_xmit(mp, nce, ixaflags, pkt_len, 0,
ALL_ZONES, 0, NULL);
nce_refrele(nce);
}
mp = nxt_mp;
}
ncec_cb_dispatch(ncec);
}
int
ndp_sioc_update(ill_t *ill, lif_nd_req_t *lnr)
{
sin6_t *sin6;
in6_addr_t *addr;
ncec_t *ncec;
nce_t *nce;
int err = 0;
uint16_t new_flags = 0;
uint16_t old_flags = 0;
int inflags = lnr->lnr_flags;
ip_stack_t *ipst = ill->ill_ipst;
boolean_t do_postprocess = B_FALSE;
ASSERT(ill->ill_isv6);
if ((lnr->lnr_state_create != ND_REACHABLE) &&
(lnr->lnr_state_create != ND_STALE))
return (EINVAL);
sin6 = (sin6_t *)&lnr->lnr_addr;
addr = &sin6->sin6_addr;
mutex_enter(&ipst->ips_ndp6->ndp_g_lock);
ASSERT(!IS_UNDER_IPMP(ill));
nce = nce_lookup_addr(ill, addr);
if (nce != NULL)
new_flags = nce->nce_common->ncec_flags;
switch (inflags & (NDF_ISROUTER_ON|NDF_ISROUTER_OFF)) {
case NDF_ISROUTER_ON:
new_flags |= NCE_F_ISROUTER;
break;
case NDF_ISROUTER_OFF:
new_flags &= ~NCE_F_ISROUTER;
break;
case (NDF_ISROUTER_OFF|NDF_ISROUTER_ON):
mutex_exit(&ipst->ips_ndp6->ndp_g_lock);
if (nce != NULL)
nce_refrele(nce);
return (EINVAL);
}
if (inflags & NDF_STATIC)
new_flags |= NCE_F_STATIC;
switch (inflags & (NDF_ANYCAST_ON|NDF_ANYCAST_OFF)) {
case NDF_ANYCAST_ON:
new_flags |= NCE_F_ANYCAST;
break;
case NDF_ANYCAST_OFF:
new_flags &= ~NCE_F_ANYCAST;
break;
case (NDF_ANYCAST_OFF|NDF_ANYCAST_ON):
mutex_exit(&ipst->ips_ndp6->ndp_g_lock);
if (nce != NULL)
nce_refrele(nce);
return (EINVAL);
}
if (nce == NULL) {
err = nce_add_v6(ill,
(uchar_t *)lnr->lnr_hdw_addr,
ill->ill_phys_addr_length,
addr,
new_flags,
lnr->lnr_state_create,
&nce);
if (err != 0) {
mutex_exit(&ipst->ips_ndp6->ndp_g_lock);
ip1dbg(("ndp_sioc_update: Can't create NCE %d\n", err));
return (err);
} else {
do_postprocess = B_TRUE;
}
}
ncec = nce->nce_common;
old_flags = ncec->ncec_flags;
if (old_flags & NCE_F_ISROUTER && !(new_flags & NCE_F_ISROUTER)) {
ncec_router_to_host(ncec);
mutex_exit(&ipst->ips_ndp6->ndp_g_lock);
if (do_postprocess)
err = nce_add_v6_postprocess(nce);
nce_refrele(nce);
return (0);
}
mutex_exit(&ipst->ips_ndp6->ndp_g_lock);
if (do_postprocess)
err = nce_add_v6_postprocess(nce);
ASSERT(err == 0);
mutex_enter(&ncec->ncec_lock);
ncec->ncec_flags = new_flags;
mutex_exit(&ncec->ncec_lock);
nce_process(ncec, (uchar_t *)lnr->lnr_hdw_addr, 0, B_FALSE);
nce_refrele(nce);
return (0);
}
static nce_t *
nce_fastpath_create(ill_t *ill, ncec_t *ncec)
{
nce_t *nce;
nce = nce_ill_lookup_then_add(ill, ncec);
if (nce == NULL || IS_LOOPBACK(nce->nce_ill) || IS_VNI(nce->nce_ill))
return (nce);
mutex_enter(&ncec->ncec_lock);
if (ncec->ncec_lladdr != NULL) {
bcopy(ncec->ncec_lladdr, nce->nce_dlur_mp->b_rptr +
NCE_LL_ADDR_OFFSET(ill), ill->ill_phys_addr_length);
} else {
nce->nce_dlur_mp = ill_dlur_gen(NULL, 0, ill->ill_sap,
ill->ill_sap_length);
}
mutex_exit(&ncec->ncec_lock);
return (nce);
}
static nce_t *
nce_fastpath(ncec_t *ncec, boolean_t trigger_fp_req, nce_t *ncec_nce)
{
nce_t *nce;
ill_t *ill = ncec->ncec_ill;
ASSERT(ill != NULL);
if (IS_IPMP(ill) && trigger_fp_req) {
trigger_fp_req = B_FALSE;
ipmp_ncec_refresh_nce(ncec);
}
if (ncec_nce == NULL)
nce = nce_fastpath_create(ill, ncec);
else
nce = ncec_nce;
if (nce == NULL || IS_LOOPBACK(nce->nce_ill) || IS_VNI(nce->nce_ill))
return (nce);
if (trigger_fp_req)
nce_fastpath_trigger(nce);
return (nce);
}
static void
nce_fastpath_trigger(nce_t *nce)
{
int res;
ill_t *ill = nce->nce_ill;
ncec_t *ncec = nce->nce_common;
res = ill_fastpath_probe(ill, nce->nce_dlur_mp);
if (res != 0 && res != EAGAIN && res != ENOTSUP)
nce_fastpath_list_delete(ill, ncec, NULL);
}
static nce_t *
nce_ill_lookup_then_add_locked(ill_t *ill, ncec_t *ncec, list_t *graveyard)
{
nce_t *nce = NULL;
ASSERT(MUTEX_HELD(&ill->ill_lock));
if (ill->ill_state_flags & ILL_CONDEMNED)
return (NULL);
mutex_enter(&ncec->ncec_lock);
if (!NCE_ISCONDEMNED(ncec)) {
nce = nce_lookup(ill, &ncec->ncec_addr);
if (nce != NULL)
goto done;
nce = nce_add(ill, ncec, graveyard);
}
done:
mutex_exit(&ncec->ncec_lock);
return (nce);
}
static nce_t *
nce_ill_lookup_then_add(ill_t *ill, ncec_t *ncec)
{
nce_t *nce;
list_t graveyard;
list_create(&graveyard, sizeof (nce_t), offsetof(nce_t, nce_node));
mutex_enter(&ill->ill_lock);
nce = nce_ill_lookup_then_add_locked(ill, ncec, &graveyard);
mutex_exit(&ill->ill_lock);
nce_graveyard_free(&graveyard);
return (nce);
}
void
nce_fastpath_list_delete(ill_t *ill, ncec_t *ncec, list_t *dead)
{
nce_t *nce;
ASSERT(ill != NULL);
if (IS_IPMP(ill))
ipmp_ncec_delete_nce(ncec);
mutex_enter(&ill->ill_lock);
for (nce = list_head(&ill->ill_nce); nce != NULL;
nce = list_next(&ill->ill_nce, nce)) {
if (nce->nce_common == ncec) {
nce_refhold(nce);
nce_delete(nce);
break;
}
}
mutex_exit(&ill->ill_lock);
if (nce != NULL) {
if (dead == NULL)
nce_refrele(nce);
else
list_insert_tail(dead, nce);
}
}
static nce_t *
nce_delete_then_add(nce_t *nce)
{
ill_t *ill = nce->nce_ill;
nce_t *newnce = NULL;
list_t graveyard;
list_create(&graveyard, sizeof (nce_t), offsetof(nce_t, nce_node));
ip0dbg(("nce_delete_then_add nce %p ill %s\n",
(void *)nce, ill->ill_name));
mutex_enter(&ill->ill_lock);
mutex_enter(&nce->nce_common->ncec_lock);
nce_delete(nce);
if (!NCE_ISCONDEMNED(nce->nce_common))
newnce = nce_add(ill, nce->nce_common, &graveyard);
mutex_exit(&nce->nce_common->ncec_lock);
mutex_exit(&ill->ill_lock);
nce_graveyard_free(&graveyard);
nce_refrele(nce);
return (newnce);
}
typedef struct nce_fp_match_s {
nce_t *nce_fp_match_res;
mblk_t *nce_fp_match_ack_mp;
} nce_fp_match_t;
static int
nce_fastpath_match_dlur(ill_t *ill, nce_t *nce, void *arg)
{
nce_fp_match_t *nce_fp_marg = arg;
ncec_t *ncec = nce->nce_common;
mblk_t *mp = nce_fp_marg->nce_fp_match_ack_mp;
uchar_t *mp_rptr, *ud_mp_rptr;
mblk_t *ud_mp = nce->nce_dlur_mp;
ptrdiff_t cmplen;
if (ud_mp == NULL)
return (0);
mp_rptr = mp->b_rptr;
cmplen = mp->b_wptr - mp_rptr;
ASSERT(cmplen >= 0);
ud_mp_rptr = ud_mp->b_rptr;
mutex_enter(&ncec->ncec_lock);
if (ud_mp->b_wptr - ud_mp_rptr != cmplen ||
bcmp((char *)mp_rptr, (char *)ud_mp_rptr, cmplen) == 0) {
nce_fp_marg->nce_fp_match_res = nce;
mutex_exit(&ncec->ncec_lock);
nce_refhold(nce);
return (1);
}
mutex_exit(&ncec->ncec_lock);
return (0);
}
void
nce_fastpath_update(ill_t *ill, mblk_t *mp)
{
nce_fp_match_t nce_fp_marg;
nce_t *nce;
mblk_t *nce_fp_mp, *fp_mp;
nce_fp_marg.nce_fp_match_res = NULL;
nce_fp_marg.nce_fp_match_ack_mp = mp;
nce_walk(ill, nce_fastpath_match_dlur, &nce_fp_marg);
if ((nce = nce_fp_marg.nce_fp_match_res) == NULL)
return;
mutex_enter(&nce->nce_lock);
nce_fp_mp = nce->nce_fp_mp;
if (nce_fp_mp != NULL) {
fp_mp = mp->b_cont;
if (nce_fp_mp->b_rptr + MBLKL(fp_mp) >
nce_fp_mp->b_datap->db_lim) {
mutex_exit(&nce->nce_lock);
nce = nce_delete_then_add(nce);
if (nce == NULL) {
return;
}
mutex_enter(&nce->nce_lock);
nce_fp_mp = nce->nce_fp_mp;
}
}
if (nce_fp_mp == NULL) {
fp_mp = dupb(mp->b_cont);
nce->nce_fp_mp = fp_mp;
} else {
fp_mp = mp->b_cont;
bcopy(fp_mp->b_rptr, nce_fp_mp->b_rptr, MBLKL(fp_mp));
nce->nce_fp_mp->b_wptr = nce->nce_fp_mp->b_rptr
+ MBLKL(fp_mp);
}
mutex_exit(&nce->nce_lock);
nce_refrele(nce);
}
nd_opt_hdr_t *
ndp_get_option(nd_opt_hdr_t *opt, int optlen, int opt_type)
{
while (optlen > 0) {
if (opt->nd_opt_type == opt_type)
return (opt);
optlen -= 8 * opt->nd_opt_len;
opt = (struct nd_opt_hdr *)((char *)opt + 8 * opt->nd_opt_len);
}
return (NULL);
}
boolean_t
ndp_verify_optlen(nd_opt_hdr_t *opt, int optlen)
{
ASSERT(opt != NULL);
while (optlen > 0) {
if (opt->nd_opt_len == 0)
return (B_FALSE);
optlen -= 8 * opt->nd_opt_len;
if (optlen < 0)
return (B_FALSE);
opt = (struct nd_opt_hdr *)((char *)opt + 8 * opt->nd_opt_len);
}
return (B_TRUE);
}
static void
ncec_cache_reclaim(ncec_t *ncec, void *arg)
{
ip_stack_t *ipst = ncec->ncec_ipst;
uint_t fraction = *(uint_t *)arg;
uint_t rand;
if ((ncec->ncec_flags &
(NCE_F_MYADDR | NCE_F_STATIC | NCE_F_BCAST)) != 0) {
return;
}
rand = (uint_t)ddi_get_lbolt() +
NCE_ADDR_HASH_V6(ncec->ncec_addr, NCE_TABLE_SIZE);
if ((rand/fraction)*fraction == rand) {
IP_STAT(ipst, ip_nce_reclaim_deleted);
ncec_delete(ncec);
}
}
static void
ip_nce_reclaim_stack(ip_stack_t *ipst)
{
uint_t fraction = ipst->ips_ip_nce_reclaim_fraction;
IP_STAT(ipst, ip_nce_reclaim_calls);
ncec_walk(NULL, ncec_cache_reclaim, &fraction, ipst);
ipcl_walk(conn_ixa_cleanup, (void *)B_FALSE, ipst);
}
void
ip_nce_reclaim(void *args)
{
netstack_handle_t nh;
netstack_t *ns;
ip_stack_t *ipst;
netstack_next_init(&nh);
while ((ns = netstack_next(&nh)) != NULL) {
if ((ipst = ns->netstack_ip) == NULL) {
netstack_rele(ns);
continue;
}
ip_nce_reclaim_stack(ipst);
netstack_rele(ns);
}
netstack_next_fini(&nh);
}
#ifdef DEBUG
void
ncec_trace_ref(ncec_t *ncec)
{
ASSERT(MUTEX_HELD(&ncec->ncec_lock));
if (ncec->ncec_trace_disable)
return;
if (!th_trace_ref(ncec, ncec->ncec_ipst)) {
ncec->ncec_trace_disable = B_TRUE;
ncec_trace_cleanup(ncec);
}
}
void
ncec_untrace_ref(ncec_t *ncec)
{
ASSERT(MUTEX_HELD(&ncec->ncec_lock));
if (!ncec->ncec_trace_disable)
th_trace_unref(ncec);
}
static void
ncec_trace_cleanup(const ncec_t *ncec)
{
th_trace_cleanup(ncec, ncec->ncec_trace_disable);
}
#endif
void
arp_resolv_failed(ncec_t *ncec)
{
mblk_t *mp, *nxt_mp;
char buf[INET6_ADDRSTRLEN];
struct in_addr ipv4addr;
ill_t *ill = ncec->ncec_ill;
ip_stack_t *ipst = ncec->ncec_ipst;
ip_recv_attr_t iras;
bzero(&iras, sizeof (iras));
iras.ira_flags = IRAF_IS_IPV4;
iras.ira_ill = iras.ira_rill = ill;
iras.ira_ruifindex = ill->ill_phyint->phyint_ifindex;
iras.ira_rifindex = iras.ira_ruifindex;
IN6_V4MAPPED_TO_INADDR(&ncec->ncec_addr, &ipv4addr);
ip3dbg(("arp_resolv_failed: dst %s\n",
inet_ntop(AF_INET, &ipv4addr, buf, sizeof (buf))));
mutex_enter(&ncec->ncec_lock);
mp = ncec->ncec_qd_mp;
ncec->ncec_qd_mp = NULL;
ncec->ncec_nprobes = 0;
mutex_exit(&ncec->ncec_lock);
while (mp != NULL) {
nxt_mp = mp->b_next;
mp->b_next = NULL;
BUMP_MIB(ill->ill_ip_mib, ipIfStatsOutDiscards);
ip_drop_output("ipIfStatsOutDiscards - address unreachable",
mp, ill);
if (ipst->ips_ip_arp_icmp_error) {
ip3dbg(("arp_resolv_failed: "
"Calling icmp_unreachable\n"));
icmp_unreachable(mp, ICMP_HOST_UNREACHABLE, &iras);
} else {
freemsg(mp);
}
ASSERT(!(iras.ira_flags & IRAF_IPSEC_SECURE));
mp = nxt_mp;
}
ncec_cb_dispatch(ncec);
}
int
nce_lookup_then_add_v4(ill_t *ill, uchar_t *hw_addr, uint_t hw_addr_len,
const in_addr_t *addr, uint16_t flags, uint16_t state, nce_t **newnce)
{
int err;
in6_addr_t addr6;
ip_stack_t *ipst = ill->ill_ipst;
nce_t *nce, *upper_nce = NULL;
ill_t *in_ill = ill, *under = NULL;
boolean_t need_ill_refrele = B_FALSE;
if (flags & NCE_F_MCAST) {
ASSERT(hw_addr == NULL);
ASSERT(!IS_IPMP(ill));
err = nce_set_multicast_v4(ill, addr, flags, newnce);
return (err);
}
if (IS_UNDER_IPMP(ill) && !(flags & NCE_F_MYADDR)) {
ill = ipmp_ill_hold_ipmp_ill(ill);
if (ill == NULL)
return (ENXIO);
need_ill_refrele = B_TRUE;
}
if ((flags & NCE_F_BCAST) != 0) {
if (IS_IPMP(ill)) {
under = ipmp_ill_hold_xmit_ill(ill, B_FALSE);
if (under == NULL) {
if (need_ill_refrele)
ill_refrele(ill);
return (ENETDOWN);
}
hw_addr = under->ill_bcast_mp->b_rptr +
NCE_LL_ADDR_OFFSET(under);
hw_addr_len = under->ill_phys_addr_length;
} else {
hw_addr = ill->ill_bcast_mp->b_rptr +
NCE_LL_ADDR_OFFSET(ill),
hw_addr_len = ill->ill_phys_addr_length;
}
}
mutex_enter(&ipst->ips_ndp4->ndp_g_lock);
IN6_IPADDR_TO_V4MAPPED(*addr, &addr6);
nce = nce_lookup_addr(ill, &addr6);
if (nce == NULL) {
err = nce_add_v4(ill, hw_addr, hw_addr_len, addr, flags,
state, &nce);
} else {
err = EEXIST;
}
mutex_exit(&ipst->ips_ndp4->ndp_g_lock);
if (err == 0)
err = nce_add_v4_postprocess(nce);
if (in_ill != ill && nce != NULL) {
nce_t *under_nce = NULL;
rw_enter(&ipst->ips_ill_g_lock, RW_READER);
if (!IS_IN_SAME_ILLGRP(in_ill, ill)) {
DTRACE_PROBE2(ill__not__in__group, nce_t *, nce,
ill_t *, ill);
rw_exit(&ipst->ips_ill_g_lock);
err = ENXIO;
nce_refrele(nce);
nce = NULL;
goto bail;
}
under_nce = nce_fastpath_create(in_ill, nce->nce_common);
if (under_nce == NULL) {
rw_exit(&ipst->ips_ill_g_lock);
err = EINVAL;
nce_refrele(nce);
nce = NULL;
goto bail;
}
rw_exit(&ipst->ips_ill_g_lock);
upper_nce = nce;
nce = under_nce;
if (NCE_ISREACHABLE(nce->nce_common))
nce_fastpath_trigger(under_nce);
}
if (nce != NULL) {
if (newnce != NULL)
*newnce = nce;
else
nce_refrele(nce);
}
bail:
if (under != NULL)
ill_refrele(under);
if (upper_nce != NULL)
nce_refrele(upper_nce);
if (need_ill_refrele)
ill_refrele(ill);
return (err);
}
int
nce_add_v4(ill_t *ill, uchar_t *hw_addr, uint_t hw_addr_len,
const in_addr_t *addr, uint16_t flags, uint16_t state, nce_t **newnce)
{
int err;
boolean_t is_multicast = (flags & NCE_F_MCAST);
struct in6_addr addr6;
nce_t *nce;
ASSERT(MUTEX_HELD(&ill->ill_ipst->ips_ndp4->ndp_g_lock));
ASSERT(!ill->ill_isv6);
ASSERT(!IN_MULTICAST(htonl(*addr)) || is_multicast);
IN6_IPADDR_TO_V4MAPPED(*addr, &addr6);
err = nce_add_common(ill, hw_addr, hw_addr_len, &addr6, flags, state,
&nce);
ASSERT(newnce != NULL);
*newnce = nce;
return (err);
}
int
nce_add_v4_postprocess(nce_t *nce)
{
ncec_t *ncec = nce->nce_common;
uint16_t flags = ncec->ncec_flags;
boolean_t ndp_need_dad = B_FALSE;
boolean_t dropped;
clock_t delay;
ip_stack_t *ipst = ncec->ncec_ill->ill_ipst;
uchar_t *hw_addr = ncec->ncec_lladdr;
boolean_t trigger_fastpath = B_TRUE;
if (NCE_PUBLISH(ncec) || !NCE_ISREACHABLE(ncec) || (hw_addr == NULL &&
ncec->ncec_ill->ill_net_type != IRE_IF_NORESOLVER))
trigger_fastpath = B_FALSE;
if (trigger_fastpath)
nce_fastpath_trigger(nce);
if (NCE_PUBLISH(ncec) && ncec->ncec_state == ND_PROBE) {
ndp_need_dad = B_TRUE;
} else if (flags & NCE_F_UNSOL_ADV) {
mutex_enter(&ncec->ncec_lock);
ncec->ncec_unsolicit_count =
ipst->ips_ip_arp_publish_count - 1;
mutex_exit(&ncec->ncec_lock);
dropped = arp_announce(ncec);
mutex_enter(&ncec->ncec_lock);
if (dropped)
ncec->ncec_unsolicit_count++;
else
ncec->ncec_last_time_defended = ddi_get_lbolt();
if (ncec->ncec_unsolicit_count != 0) {
nce_start_timer(ncec,
ipst->ips_ip_arp_publish_interval);
}
mutex_exit(&ncec->ncec_lock);
}
if (ndp_need_dad) {
mutex_enter(&ncec->ncec_lock);
if (ncec->ncec_pcnt == 0) {
ncec->ncec_unsolicit_count = 0;
mutex_exit(&ncec->ncec_lock);
nce_restart_timer(ncec, 0);
} else {
mutex_exit(&ncec->ncec_lock);
delay = ((ncec->ncec_flags & NCE_F_FAST) ?
ipst->ips_arp_probe_delay :
ipst->ips_arp_fastprobe_delay);
nce_dad(ncec, NULL, (delay == 0 ? B_TRUE : B_FALSE));
}
}
return (0);
}
void
nce_update_hw_changed(ncec_t *ncec, void *arg)
{
nce_hw_map_t *hwm = arg;
ipaddr_t ncec_addr;
if (ncec->ncec_state != ND_REACHABLE)
return;
IN6_V4MAPPED_TO_IPADDR(&ncec->ncec_addr, ncec_addr);
if (ncec_addr != hwm->hwm_addr)
return;
mutex_enter(&ncec->ncec_lock);
if (hwm->hwm_flags != 0)
ncec->ncec_flags = hwm->hwm_flags;
nce_update(ncec, ND_STALE, hwm->hwm_hwaddr);
mutex_exit(&ncec->ncec_lock);
}
void
ncec_refhold(ncec_t *ncec)
{
mutex_enter(&(ncec)->ncec_lock);
(ncec)->ncec_refcnt++;
ASSERT((ncec)->ncec_refcnt != 0);
#ifdef DEBUG
ncec_trace_ref(ncec);
#endif
mutex_exit(&(ncec)->ncec_lock);
}
void
ncec_refhold_notr(ncec_t *ncec)
{
mutex_enter(&(ncec)->ncec_lock);
(ncec)->ncec_refcnt++;
ASSERT((ncec)->ncec_refcnt != 0);
mutex_exit(&(ncec)->ncec_lock);
}
static void
ncec_refhold_locked(ncec_t *ncec)
{
ASSERT(MUTEX_HELD(&(ncec)->ncec_lock));
(ncec)->ncec_refcnt++;
#ifdef DEBUG
ncec_trace_ref(ncec);
#endif
}
void
ncec_refrele(ncec_t *ncec)
{
mutex_enter(&(ncec)->ncec_lock);
#ifdef DEBUG
ncec_untrace_ref(ncec);
#endif
ASSERT((ncec)->ncec_refcnt != 0);
if (--(ncec)->ncec_refcnt == 0) {
ncec_inactive(ncec);
} else {
mutex_exit(&(ncec)->ncec_lock);
}
}
void
ncec_refrele_notr(ncec_t *ncec)
{
mutex_enter(&(ncec)->ncec_lock);
ASSERT((ncec)->ncec_refcnt != 0);
if (--(ncec)->ncec_refcnt == 0) {
ncec_inactive(ncec);
} else {
mutex_exit(&(ncec)->ncec_lock);
}
}
void
nce_restart_timer(ncec_t *ncec, uint_t ms)
{
timeout_id_t tid;
ASSERT(!MUTEX_HELD(&(ncec)->ncec_lock));
mutex_enter(&ncec->ncec_lock);
tid = ncec->ncec_timeout_id;
ncec->ncec_timeout_id = 0;
if (tid != 0) {
mutex_exit(&ncec->ncec_lock);
(void) untimeout(tid);
mutex_enter(&ncec->ncec_lock);
}
nce_start_timer(ncec, ms);
mutex_exit(&ncec->ncec_lock);
}
static void
nce_start_timer(ncec_t *ncec, uint_t ms)
{
ASSERT(MUTEX_HELD(&ncec->ncec_lock));
if (!NCE_ISCONDEMNED(ncec) && ncec->ncec_timeout_id == 0) {
ncec->ncec_timeout_id = timeout(nce_timer, ncec,
MSEC_TO_TICK(ms) == 0 ? 1 : MSEC_TO_TICK(ms));
}
}
int
nce_set_multicast_v4(ill_t *ill, const in_addr_t *dst,
uint16_t flags, nce_t **newnce)
{
uchar_t *hw_addr;
int err = 0;
ip_stack_t *ipst = ill->ill_ipst;
in6_addr_t dst6;
nce_t *nce;
ASSERT(!ill->ill_isv6);
IN6_IPADDR_TO_V4MAPPED(*dst, &dst6);
mutex_enter(&ipst->ips_ndp4->ndp_g_lock);
if ((nce = nce_lookup_addr(ill, &dst6)) != NULL) {
mutex_exit(&ipst->ips_ndp4->ndp_g_lock);
goto done;
}
if (ill->ill_net_type == IRE_IF_RESOLVER) {
hw_addr = kmem_alloc(ill->ill_phys_addr_length, KM_NOSLEEP);
if (hw_addr == NULL) {
mutex_exit(&ipst->ips_ndp4->ndp_g_lock);
return (ENOMEM);
}
ip_mcast_mapping(ill, (uchar_t *)dst, hw_addr);
} else {
hw_addr = NULL;
}
ASSERT(flags & NCE_F_MCAST);
ASSERT(flags & NCE_F_NONUD);
err = nce_add_v4(ill, hw_addr, ill->ill_phys_addr_length, dst, flags,
ND_UNCHANGED, &nce);
mutex_exit(&ipst->ips_ndp4->ndp_g_lock);
if (err == 0)
err = (nce != NULL) ? nce_add_v4_postprocess(nce) : ENOMEM;
if (hw_addr != NULL)
kmem_free(hw_addr, ill->ill_phys_addr_length);
if (err != 0) {
ip1dbg(("nce_set_multicast_v4: create failed" "%d\n", err));
return (err);
}
done:
if (newnce != NULL)
*newnce = nce;
else
nce_refrele(nce);
return (0);
}
#define NCE_RESCHED_LIST_LEN 8
typedef struct {
ill_t *ncert_ill;
uint_t ncert_num;
ncec_t *ncert_nces[NCE_RESCHED_LIST_LEN];
} nce_resched_t;
static int
ncec_reschedule(ill_t *ill, nce_t *nce, void *arg)
{
nce_resched_t *ncert = arg;
ncec_t **ncecs;
ncec_t **ncec_max;
ncec_t *ncec_temp;
ncec_t *ncec = nce->nce_common;
ASSERT(ncec->ncec_ill == ncert->ncert_ill);
if (!NCE_MYADDR(ncec) || ncec->ncec_state != ND_REACHABLE)
return (0);
if (ncert->ncert_num < NCE_RESCHED_LIST_LEN) {
ncec_refhold(ncec);
ncert->ncert_nces[ncert->ncert_num++] = ncec;
} else {
ncecs = ncert->ncert_nces;
ncec_max = ncecs + NCE_RESCHED_LIST_LEN;
ncec_refhold(ncec);
for (; ncecs < ncec_max; ncecs++) {
ASSERT(ncec != NULL);
if ((*ncecs)->ncec_last_time_defended >
ncec->ncec_last_time_defended) {
ncec_temp = *ncecs;
*ncecs = ncec;
ncec = ncec_temp;
}
}
ncec_refrele(ncec);
}
return (0);
}
static void
nce_ill_reschedule(ill_t *ill, nce_resched_t *ncert)
{
ncec_t *ncec;
ip_stack_t *ipst = ill->ill_ipst;
uint_t i, defend_rate;
i = ill->ill_defend_count;
ill->ill_defend_count = 0;
if (ill->ill_isv6)
defend_rate = ipst->ips_ndp_defend_rate;
else
defend_rate = ipst->ips_arp_defend_rate;
if (i < defend_rate) {
DTRACE_PROBE1(reschedule_none, ill_t *, ill);
return;
}
ncert->ncert_ill = ill;
while (ill->ill_defend_count < defend_rate) {
nce_walk_common(ill, ncec_reschedule, ncert);
for (i = 0; i < ncert->ncert_num; i++) {
ncec = ncert->ncert_nces[i];
mutex_enter(&ncec->ncec_lock);
ncec->ncec_flags |= NCE_F_DELAYED;
mutex_exit(&ncec->ncec_lock);
if (++ill->ill_defend_count >= defend_rate)
break;
}
if (ncert->ncert_num < NCE_RESCHED_LIST_LEN)
break;
}
}
static boolean_t
ill_defend_rate_limit(ill_t *ill, ncec_t *ncec)
{
clock_t now = ddi_get_lbolt();
ip_stack_t *ipst = ill->ill_ipst;
clock_t start = ill->ill_defend_start;
uint32_t elapsed, defend_period, defend_rate;
nce_resched_t ncert;
boolean_t ret;
int i;
if (ill->ill_isv6) {
defend_period = ipst->ips_ndp_defend_period;
defend_rate = ipst->ips_ndp_defend_rate;
} else {
defend_period = ipst->ips_arp_defend_period;
defend_rate = ipst->ips_arp_defend_rate;
}
if (defend_rate == 0)
return (B_TRUE);
bzero(&ncert, sizeof (ncert));
mutex_enter(&ill->ill_lock);
if (start > 0) {
elapsed = now - start;
if (elapsed > SEC_TO_TICK(defend_period)) {
ill->ill_defend_start = now;
nce_ill_reschedule(ill, &ncert);
}
} else {
ill->ill_defend_start = now;
}
ASSERT(ill->ill_defend_count <= defend_rate);
mutex_enter(&ncec->ncec_lock);
if (ncec->ncec_flags & NCE_F_DELAYED) {
ncec->ncec_flags &= ~NCE_F_DELAYED;
mutex_exit(&ncec->ncec_lock);
ret = B_FALSE;
goto done;
}
mutex_exit(&ncec->ncec_lock);
if (ill->ill_defend_count < defend_rate)
ill->ill_defend_count++;
if (ill->ill_defend_count == defend_rate) {
ret = B_TRUE;
} else {
ret = B_FALSE;
}
done:
mutex_exit(&ill->ill_lock);
for (i = 0; i < ncert.ncert_num; i++) {
clock_t xmit_interval;
ncec_t *tmp;
tmp = ncert.ncert_nces[i];
xmit_interval = nce_fuzz_interval(tmp->ncec_xmit_interval,
B_FALSE);
nce_restart_timer(tmp, xmit_interval);
ncec_refrele(tmp);
}
return (ret);
}
boolean_t
ndp_announce(ncec_t *ncec)
{
return (ndp_xmit(ncec->ncec_ill, ND_NEIGHBOR_ADVERT, ncec->ncec_lladdr,
ncec->ncec_lladdr_length, &ncec->ncec_addr, &ipv6_all_hosts_mcast,
nce_advert_flags(ncec)));
}
ill_t *
nce_resolve_src(ncec_t *ncec, in6_addr_t *src)
{
mblk_t *mp;
in6_addr_t src6;
ipaddr_t src4;
ill_t *ill = ncec->ncec_ill;
ill_t *src_ill = NULL;
ipif_t *ipif = NULL;
boolean_t is_myaddr = NCE_MYADDR(ncec);
boolean_t isv6 = (ncec->ncec_ipversion == IPV6_VERSION);
ASSERT(src != NULL);
ASSERT(IN6_IS_ADDR_UNSPECIFIED(src));
src4 = 0;
src6 = *src;
if (is_myaddr) {
src6 = ncec->ncec_addr;
if (!isv6)
IN6_V4MAPPED_TO_IPADDR(&ncec->ncec_addr, src4);
} else {
mutex_enter(&ncec->ncec_lock);
mp = ncec->ncec_qd_mp;
if (mp != NULL) {
if (isv6) {
ip6_t *ip6h = (ip6_t *)mp->b_rptr;
src6 = ip6h->ip6_src;
} else {
ipha_t *ipha = (ipha_t *)mp->b_rptr;
src4 = ipha->ipha_src;
IN6_IPADDR_TO_V4MAPPED(src4, &src6);
}
}
mutex_exit(&ncec->ncec_lock);
}
if (!IN6_IS_ADDR_UNSPECIFIED(&src6) || is_myaddr) {
if (isv6) {
ipif = ipif_lookup_addr_nondup_v6(&src6, ill, ALL_ZONES,
ill->ill_ipst);
} else {
ipif = ipif_lookup_addr_nondup(src4, ill, ALL_ZONES,
ill->ill_ipst);
}
if (ipif == NULL) {
src6 = ipv6_all_zeros;
src4 = INADDR_ANY;
} else if (!ipif->ipif_addr_ready && !is_myaddr) {
DTRACE_PROBE2(nce__resolve__ipif__not__ready,
ncec_t *, ncec, ipif_t *, ipif);
ipif_refrele(ipif);
return (NULL);
}
}
if (IN6_IS_ADDR_UNSPECIFIED(&src6) && !is_myaddr) {
if (isv6) {
ipif = ipif_select_source_v6(ill, &ncec->ncec_addr,
B_TRUE, IPV6_PREFER_SRC_DEFAULT, ALL_ZONES,
B_FALSE, NULL);
} else {
ipaddr_t nce_addr;
IN6_V4MAPPED_TO_IPADDR(&ncec->ncec_addr, nce_addr);
ipif = ipif_select_source_v4(ill, nce_addr, ALL_ZONES,
B_FALSE, NULL);
}
if (ipif == NULL && IS_IPMP(ill)) {
ill_t *send_ill = ipmp_ill_hold_xmit_ill(ill, B_TRUE);
if (send_ill != NULL) {
if (isv6) {
ipif = ipif_select_source_v6(send_ill,
&ncec->ncec_addr, B_TRUE,
IPV6_PREFER_SRC_DEFAULT, ALL_ZONES,
B_FALSE, NULL);
} else {
IN6_V4MAPPED_TO_IPADDR(&ncec->ncec_addr,
src4);
ipif = ipif_select_source_v4(send_ill,
src4, ALL_ZONES, B_TRUE, NULL);
}
ill_refrele(send_ill);
}
}
if (ipif == NULL) {
char buf[INET6_ADDRSTRLEN];
ip1dbg(("nce_resolve_src: No source ipif for dst %s\n",
inet_ntop((isv6 ? AF_INET6 : AF_INET),
(char *)&ncec->ncec_addr, buf, sizeof (buf))));
DTRACE_PROBE1(nce__resolve__no__ipif, ncec_t *, ncec);
return (NULL);
}
src6 = ipif->ipif_v6lcl_addr;
}
*src = src6;
if (ipif != NULL) {
src_ill = ipif->ipif_ill;
if (IS_IPMP(src_ill))
src_ill = ipmp_ipif_hold_bound_ill(ipif);
else
ill_refhold(src_ill);
ipif_refrele(ipif);
DTRACE_PROBE2(nce__resolve__src__ill, ncec_t *, ncec,
ill_t *, src_ill);
}
return (src_ill);
}
void
ip_nce_lookup_and_update(ipaddr_t *addr, ipif_t *ipif, ip_stack_t *ipst,
uchar_t *hwaddr, int hwaddr_len, int flags)
{
ill_t *ill;
ncec_t *ncec;
nce_t *nce;
uint16_t new_state;
ill = (ipif ? ipif->ipif_ill : NULL);
if (ill != NULL) {
nce = nce_lookup_v4(ill, addr);
if (nce != NULL) {
ncec = nce->nce_common;
mutex_enter(&ncec->ncec_lock);
if (NCE_ISREACHABLE(ncec))
new_state = ND_UNCHANGED;
else
new_state = ND_STALE;
ncec->ncec_flags = flags;
nce_update(ncec, new_state, hwaddr);
mutex_exit(&ncec->ncec_lock);
nce_refrele(nce);
return;
}
} else {
nce_hw_map_t hwm;
hwm.hwm_addr = *addr;
hwm.hwm_hwlen = hwaddr_len;
hwm.hwm_hwaddr = hwaddr;
hwm.hwm_flags = flags;
ncec_walk_common(ipst->ips_ndp4, NULL,
nce_update_hw_changed, &hwm, B_TRUE);
}
}
static int
nce_add_common(ill_t *ill, uchar_t *hw_addr, uint_t hw_addr_len,
const in6_addr_t *addr, uint16_t flags, uint16_t nce_state, nce_t **retnce)
{
static ncec_t nce_nil;
uchar_t *template = NULL;
int err;
ncec_t *ncec;
ncec_t **ncep;
ip_stack_t *ipst = ill->ill_ipst;
uint16_t state;
boolean_t fastprobe = B_FALSE;
struct ndp_g_s *ndp;
nce_t *nce = NULL;
list_t graveyard;
mblk_t *dlur_mp = NULL;
if (ill->ill_isv6)
ndp = ill->ill_ipst->ips_ndp6;
else
ndp = ill->ill_ipst->ips_ndp4;
*retnce = NULL;
state = 0;
ASSERT(MUTEX_HELD(&ndp->ndp_g_lock));
if (IN6_IS_ADDR_UNSPECIFIED(addr)) {
ip0dbg(("nce_add_common: no addr\n"));
return (EINVAL);
}
if ((flags & ~NCE_EXTERNAL_FLAGS_MASK)) {
ip0dbg(("nce_add_common: flags = %x\n", (int)flags));
return (EINVAL);
}
if (ill->ill_isv6) {
ncep = ((ncec_t **)NCE_HASH_PTR_V6(ipst, *addr));
} else {
ipaddr_t v4addr;
IN6_V4MAPPED_TO_IPADDR(addr, v4addr);
ncep = ((ncec_t **)NCE_HASH_PTR_V4(ipst, v4addr));
}
ncec = *ncep;
for (; ncec != NULL; ncec = ncec->ncec_next) {
if (ncec->ncec_ill == ill) {
if (IN6_ARE_ADDR_EQUAL(&ncec->ncec_addr, addr)) {
if (NCE_MYADDR(ncec))
return (ENXIO);
*retnce = nce_ill_lookup_then_add(ill, ncec);
if (*retnce != NULL)
break;
}
}
}
if (*retnce != NULL)
return (0);
ncec = kmem_cache_alloc(ncec_cache, KM_NOSLEEP);
if (ncec == NULL)
return (ENOMEM);
*ncec = nce_nil;
ncec->ncec_ill = ill;
ncec->ncec_ipversion = (ill->ill_isv6 ? IPV6_VERSION : IPV4_VERSION);
ncec->ncec_flags = flags;
ncec->ncec_ipst = ipst;
if (!ill->ill_isv6) {
ipaddr_t addr4;
ASSERT(IN6_IS_ADDR_V4MAPPED(addr));
IN6_V4MAPPED_TO_IPADDR(addr, addr4);
if (ill->ill_note_link && !IS_IPV4_LL_SPACE(&addr4)) {
fastprobe = B_TRUE;
} else if (IS_IPMP(ill) && NCE_PUBLISH(ncec) &&
!IS_IPV4_LL_SPACE(&addr4)) {
ill_t *hwaddr_ill;
hwaddr_ill = ipmp_illgrp_find_ill(ill->ill_grp, hw_addr,
hw_addr_len);
if (hwaddr_ill != NULL && hwaddr_ill->ill_note_link)
fastprobe = B_TRUE;
}
if (fastprobe) {
ncec->ncec_xmit_interval =
ipst->ips_arp_fastprobe_interval;
ncec->ncec_pcnt =
ipst->ips_arp_fastprobe_count;
ncec->ncec_flags |= NCE_F_FAST;
} else {
ncec->ncec_xmit_interval =
ipst->ips_arp_probe_interval;
ncec->ncec_pcnt =
ipst->ips_arp_probe_count;
}
if (NCE_PUBLISH(ncec)) {
ncec->ncec_unsolicit_count =
ipst->ips_ip_arp_publish_count;
}
} else {
ncec->ncec_pcnt = ND_MAX_UNICAST_SOLICIT;
if (NCE_PUBLISH(ncec)) {
ncec->ncec_unsolicit_count =
ipst->ips_ip_ndp_unsolicit_count;
}
}
ncec->ncec_rcnt = ill->ill_xmit_count;
ncec->ncec_addr = *addr;
ncec->ncec_qd_mp = NULL;
ncec->ncec_refcnt = 1;
mutex_init(&ncec->ncec_lock, NULL, MUTEX_DEFAULT, NULL);
ncec->ncec_trace_disable = B_FALSE;
if (hw_addr_len > 0) {
template = kmem_alloc(hw_addr_len, KM_NOSLEEP);
if (template == NULL) {
err = ENOMEM;
goto err_ret;
}
ncec->ncec_lladdr = template;
ncec->ncec_lladdr_length = hw_addr_len;
bzero(ncec->ncec_lladdr, hw_addr_len);
}
if ((flags & NCE_F_BCAST) != 0) {
state = ND_REACHABLE;
ASSERT(hw_addr_len > 0);
} else if (ill->ill_net_type == IRE_IF_RESOLVER) {
state = ND_INITIAL;
} else if (ill->ill_net_type == IRE_IF_NORESOLVER) {
state = ND_REACHABLE;
if (ill->ill_phys_addr_length == IP_ADDR_LEN &&
ill->ill_mactype != DL_IPV4 &&
ill->ill_mactype != DL_6TO4) {
bcopy((uchar_t *)addr,
ncec->ncec_lladdr, ill->ill_phys_addr_length);
}
if (ill->ill_phys_addr_length == IPV6_ADDR_LEN &&
ill->ill_mactype != DL_IPV6) {
bcopy((uchar_t *)addr,
ncec->ncec_lladdr, ill->ill_phys_addr_length);
}
if (!ill->ill_isv6)
ncec->ncec_flags |= NCE_F_NONUD;
} else if (ill->ill_net_type == IRE_LOOPBACK) {
state = ND_REACHABLE;
}
if (hw_addr != NULL || ill->ill_net_type == IRE_IF_NORESOLVER) {
if ((flags & (NCE_F_MCAST|NCE_F_BCAST)) ||
ill->ill_net_type == IRE_IF_NORESOLVER)
state = ND_REACHABLE;
else if (!NCE_PUBLISH(ncec))
state = ND_STALE;
else
state = ND_PROBE;
if (hw_addr != NULL)
nce_set_ll(ncec, hw_addr);
}
if (nce_state != ND_UNCHANGED)
state = nce_state;
if (state == ND_PROBE)
ncec->ncec_flags |= NCE_F_UNVERIFIED;
ncec->ncec_state = state;
if (state == ND_REACHABLE) {
ncec->ncec_last = ncec->ncec_init_time =
TICK_TO_MSEC(ddi_get_lbolt64());
} else {
ncec->ncec_last = 0;
if (state == ND_INITIAL)
ncec->ncec_init_time = TICK_TO_MSEC(ddi_get_lbolt64());
}
list_create(&ncec->ncec_cb, sizeof (ncec_cb_t),
offsetof(ncec_cb_t, ncec_cb_node));
nce = kmem_cache_alloc(nce_cache, KM_NOSLEEP);
if (nce == NULL) {
err = ENOMEM;
goto err_ret;
}
if (ncec->ncec_lladdr != NULL ||
ill->ill_net_type == IRE_IF_NORESOLVER) {
dlur_mp = ill_dlur_gen(ncec->ncec_lladdr,
ill->ill_phys_addr_length, ill->ill_sap,
ill->ill_sap_length);
if (dlur_mp == NULL) {
err = ENOMEM;
goto err_ret;
}
}
mutex_enter(&ill->ill_lock);
if (ill->ill_state_flags & ILL_CONDEMNED) {
mutex_exit(&ill->ill_lock);
err = EINVAL;
goto err_ret;
}
if (!NCE_MYADDR(ncec) &&
(ill->ill_state_flags & ILL_DOWN_IN_PROGRESS)) {
mutex_exit(&ill->ill_lock);
DTRACE_PROBE1(nce__add__on__down__ill, ncec_t *, ncec);
err = EINVAL;
goto err_ret;
}
mutex_enter(&ncec->ncec_lock);
if ((ncec->ncec_next = *ncep) != NULL)
ncec->ncec_next->ncec_ptpn = &ncec->ncec_next;
*ncep = ncec;
ncec->ncec_ptpn = ncep;
DTRACE_PROBE3(ill__incr__cnt, (ill_t *), ill,
(char *), "ncec", (void *), ncec);
ill->ill_ncec_cnt++;
list_create(&graveyard, sizeof (nce_t), offsetof(nce_t, nce_node));
*retnce = nce_add_impl(ill, ncec, nce, dlur_mp, &graveyard);
mutex_exit(&ncec->ncec_lock);
mutex_exit(&ill->ill_lock);
nce_graveyard_free(&graveyard);
return (0);
err_ret:
if (ncec != NULL)
kmem_cache_free(ncec_cache, ncec);
if (nce != NULL)
kmem_cache_free(nce_cache, nce);
freemsg(dlur_mp);
if (template != NULL)
kmem_free(template, ill->ill_phys_addr_length);
return (err);
}
void
nce_refhold(nce_t *nce)
{
mutex_enter(&nce->nce_lock);
nce->nce_refcnt++;
ASSERT((nce)->nce_refcnt != 0);
mutex_exit(&nce->nce_lock);
}
void
nce_refrele(nce_t *nce)
{
ASSERT((nce)->nce_refcnt != 0);
mutex_enter(&nce->nce_lock);
if (--nce->nce_refcnt == 0)
nce_inactive(nce);
else
mutex_exit(&nce->nce_lock);
}
static void
nce_inactive(nce_t *nce)
{
ill_t *ill = nce->nce_ill;
ASSERT(nce->nce_refcnt == 0);
ncec_refrele_notr(nce->nce_common);
nce->nce_common = NULL;
freemsg(nce->nce_fp_mp);
freemsg(nce->nce_dlur_mp);
mutex_enter(&ill->ill_lock);
DTRACE_PROBE3(ill__decr__cnt, (ill_t *), ill,
(char *), "nce", (void *), nce);
ill->ill_nce_cnt--;
nce->nce_ill = NULL;
if (ILL_DOWN_OK(ill)) {
ipif_ill_refrele_tail(ill);
} else {
mutex_exit(&ill->ill_lock);
}
mutex_destroy(&nce->nce_lock);
kmem_cache_free(nce_cache, nce);
}
static nce_t *
nce_add_impl(ill_t *ill, ncec_t *ncec, nce_t *nce, mblk_t *dlur_mp,
list_t *graveyard)
{
ASSERT(MUTEX_HELD(&ill->ill_lock));
if ((ncec->ncec_flags & NCE_F_MCAST) != 0) {
if (nce_too_many_mcast(ill, graveyard)) {
kmem_cache_free(nce_cache, nce);
return (NULL);
}
ill->ill_mcast_nces++;
}
bzero(nce, sizeof (*nce));
mutex_init(&nce->nce_lock, NULL, MUTEX_DEFAULT, NULL);
nce->nce_common = ncec;
nce->nce_addr = ncec->ncec_addr;
nce->nce_ill = ill;
DTRACE_PROBE3(ill__incr__cnt, (ill_t *), ill,
(char *), "nce", (void *), nce);
ill->ill_nce_cnt++;
nce->nce_refcnt = 1;
ncec->ncec_refcnt++;
nce->nce_dlur_mp = dlur_mp;
nce->nce_refcnt++;
list_insert_head(&ill->ill_nce, nce);
return (nce);
}
static nce_t *
nce_add(ill_t *ill, ncec_t *ncec, list_t *graveyard)
{
nce_t *nce;
mblk_t *dlur_mp = NULL;
ASSERT(MUTEX_HELD(&ill->ill_lock));
ASSERT(MUTEX_HELD(&ncec->ncec_lock));
nce = kmem_cache_alloc(nce_cache, KM_NOSLEEP);
if (nce == NULL)
return (NULL);
if (ncec->ncec_lladdr != NULL ||
ill->ill_net_type == IRE_IF_NORESOLVER) {
dlur_mp = ill_dlur_gen(ncec->ncec_lladdr,
ill->ill_phys_addr_length, ill->ill_sap,
ill->ill_sap_length);
if (dlur_mp == NULL) {
kmem_cache_free(nce_cache, nce);
return (NULL);
}
}
return (nce_add_impl(ill, ncec, nce, dlur_mp, graveyard));
}
void
nce_delete(nce_t *nce)
{
ill_t *ill = nce->nce_ill;
ASSERT(MUTEX_HELD(&ill->ill_lock));
mutex_enter(&nce->nce_lock);
if (nce->nce_is_condemned) {
mutex_exit(&nce->nce_lock);
return;
}
nce->nce_is_condemned = B_TRUE;
mutex_exit(&nce->nce_lock);
if ((nce->nce_common->ncec_flags & NCE_F_MCAST) == NCE_F_MCAST)
ill->ill_mcast_nces--;
list_remove(&ill->ill_nce, nce);
nce_refrele(nce);
}
nce_t *
nce_lookup(ill_t *ill, const in6_addr_t *addr)
{
nce_t *nce = NULL;
ASSERT(ill != NULL);
ASSERT(MUTEX_HELD(&ill->ill_lock));
for (nce = list_head(&ill->ill_nce); nce != NULL;
nce = list_next(&ill->ill_nce, nce)) {
if (IN6_ARE_ADDR_EQUAL(&nce->nce_addr, addr))
break;
}
if (nce != NULL) {
ASSERT(!nce->nce_is_condemned);
nce_refhold(nce);
}
return (nce);
}
static void
nce_walk_common(ill_t *ill, pfi_t func, void *arg)
{
nce_t *nce = NULL, *nce_next;
ASSERT(MUTEX_HELD(&ill->ill_lock));
for (nce = list_head(&ill->ill_nce); nce != NULL; ) {
nce_next = list_next(&ill->ill_nce, nce);
if (func(ill, nce, arg) != 0)
break;
nce = nce_next;
}
}
void
nce_walk(ill_t *ill, pfi_t func, void *arg)
{
mutex_enter(&ill->ill_lock);
nce_walk_common(ill, func, arg);
mutex_exit(&ill->ill_lock);
}
void
nce_flush(ill_t *ill, boolean_t flushall)
{
nce_t *nce, *nce_next;
list_t dead;
list_create(&dead, sizeof (nce_t), offsetof(nce_t, nce_node));
mutex_enter(&ill->ill_lock);
for (nce = list_head(&ill->ill_nce); nce != NULL; ) {
nce_next = list_next(&ill->ill_nce, nce);
if (!flushall && NCE_PUBLISH(nce->nce_common)) {
nce = nce_next;
continue;
}
nce_refhold(nce);
nce_delete(nce);
list_insert_tail(&dead, nce);
nce = nce_next;
}
mutex_exit(&ill->ill_lock);
while ((nce = list_head(&dead)) != NULL) {
list_remove(&dead, nce);
nce_refrele(nce);
}
ASSERT(list_is_empty(&dead));
list_destroy(&dead);
}
static clock_t
nce_fuzz_interval(clock_t intv, boolean_t initial_time)
{
clock_t rnd, frac;
(void) random_get_pseudo_bytes((uint8_t *)&rnd, sizeof (rnd));
rnd &= (1ul << (NBBY * sizeof (rnd) - 1)) - 1;
if (initial_time) {
if (intv <= 0)
intv = 1;
else
intv = (rnd % intv) + 1;
} else {
if ((frac = intv / 5) <= 1)
frac = 2;
if ((intv = intv - frac + rnd % (2 * frac + 1)) <= 0)
intv = 1;
}
return (intv);
}
void
nce_resolv_ipmp_ok(ncec_t *ncec)
{
mblk_t *mp;
uint_t pkt_len;
iaflags_t ixaflags = IXAF_NO_TRACE;
nce_t *under_nce;
ill_t *ill = ncec->ncec_ill;
boolean_t isv6 = (ncec->ncec_ipversion == IPV6_VERSION);
ipif_t *src_ipif = NULL;
ip_stack_t *ipst = ill->ill_ipst;
ill_t *send_ill;
uint_t nprobes;
ASSERT(IS_IPMP(ill));
mutex_enter(&ncec->ncec_lock);
nprobes = ncec->ncec_nprobes;
mp = ncec->ncec_qd_mp;
ncec->ncec_qd_mp = NULL;
ncec->ncec_nprobes = 0;
mutex_exit(&ncec->ncec_lock);
while (mp != NULL) {
mblk_t *nxt_mp;
nxt_mp = mp->b_next;
mp->b_next = NULL;
if (isv6) {
ip6_t *ip6h = (ip6_t *)mp->b_rptr;
pkt_len = ntohs(ip6h->ip6_plen) + IPV6_HDR_LEN;
src_ipif = ipif_lookup_addr_nondup_v6(&ip6h->ip6_src,
ill, ALL_ZONES, ipst);
} else {
ipha_t *ipha = (ipha_t *)mp->b_rptr;
ixaflags |= IXAF_IS_IPV4;
pkt_len = ntohs(ipha->ipha_length);
src_ipif = ipif_lookup_addr_nondup(ipha->ipha_src,
ill, ALL_ZONES, ipst);
}
if (src_ipif == NULL || IS_IPMP(src_ipif->ipif_ill)) {
if (src_ipif == NULL && nprobes > 0)
goto drop_pkt;
send_ill = ipmp_ill_hold_xmit_ill(ncec->ncec_ill,
B_TRUE);
} else {
send_ill = src_ipif->ipif_ill;
ill_refhold(send_ill);
}
DTRACE_PROBE4(nce__resolve__ipmp, (mblk_t *), mp,
(ncec_t *), ncec, (ipif_t *),
src_ipif, (ill_t *), send_ill);
if (send_ill == NULL) {
if (src_ipif != NULL)
ipif_refrele(src_ipif);
goto drop_pkt;
}
rw_enter(&ipst->ips_ill_g_lock, RW_READER);
if (IS_IN_SAME_ILLGRP(send_ill, ncec->ncec_ill))
under_nce = nce_fastpath_create(send_ill, ncec);
else
under_nce = NULL;
rw_exit(&ipst->ips_ill_g_lock);
if (under_nce != NULL && NCE_ISREACHABLE(ncec))
nce_fastpath_trigger(under_nce);
ill_refrele(send_ill);
if (src_ipif != NULL)
ipif_refrele(src_ipif);
if (under_nce != NULL) {
(void) ip_xmit(mp, under_nce, ixaflags, pkt_len, 0,
ALL_ZONES, 0, NULL);
nce_refrele(under_nce);
if (nprobes > 0)
nprobes--;
mp = nxt_mp;
continue;
}
drop_pkt:
if (isv6) {
BUMP_MIB(&ipst->ips_ip6_mib, ipIfStatsOutDiscards);
} else {
BUMP_MIB(&ipst->ips_ip_mib, ipIfStatsOutDiscards);
}
ip_drop_output("ipIfStatsOutDiscards - no under_ill", mp, NULL);
freemsg(mp);
if (nprobes > 0)
nprobes--;
mp = nxt_mp;
}
ncec_cb_dispatch(ncec);
}