#include <inet/ip.h>
#include <inet/ip6.h>
#include <inet/ip_if.h>
#include <inet/ip_ire.h>
#include <inet/ip_multi.h>
#include <inet/ip_ndp.h>
#include <inet/ip_rts.h>
#include <inet/mi.h>
#include <net/if_types.h>
#include <sys/dlpi.h>
#include <sys/kmem.h>
#include <sys/modhash.h>
#include <sys/sdt.h>
#include <sys/strsun.h>
#include <sys/sunddi.h>
#include <sys/types.h>
#define IPMP_GRP_TO_IPST(grp) PHYINT_TO_IPST((grp)->gr_phyint)
#define IPMP_ILLGRP_TO_IPST(illg) ((illg)->ig_ipmp_ill->ill_ipst)
#define IPMP_GRP_HASH_SIZE 64
#define IPMP_ILL_REFRESH_TIMEOUT 120
static const kstat_named_t ipmp_kstats[IPMP_KSTAT_MAX] = {
{ "obytes", KSTAT_DATA_UINT32 },
{ "obytes64", KSTAT_DATA_UINT64 },
{ "rbytes", KSTAT_DATA_UINT32 },
{ "rbytes64", KSTAT_DATA_UINT64 },
{ "opackets", KSTAT_DATA_UINT32 },
{ "opackets64", KSTAT_DATA_UINT64 },
{ "oerrors", KSTAT_DATA_UINT32 },
{ "ipackets", KSTAT_DATA_UINT32 },
{ "ipackets64", KSTAT_DATA_UINT64 },
{ "ierrors", KSTAT_DATA_UINT32 },
{ "multircv", KSTAT_DATA_UINT32 },
{ "multixmt", KSTAT_DATA_UINT32 },
{ "brdcstrcv", KSTAT_DATA_UINT32 },
{ "brdcstxmt", KSTAT_DATA_UINT32 },
{ "link_up", KSTAT_DATA_UINT32 }
};
static void ipmp_grp_insert(ipmp_grp_t *, mod_hash_hndl_t);
static int ipmp_grp_create_kstats(ipmp_grp_t *);
static int ipmp_grp_update_kstats(kstat_t *, int);
static void ipmp_grp_destroy_kstats(ipmp_grp_t *);
static ill_t *ipmp_illgrp_min_ill(ipmp_illgrp_t *);
static ill_t *ipmp_illgrp_max_ill(ipmp_illgrp_t *);
static void ipmp_illgrp_set_cast(ipmp_illgrp_t *, ill_t *);
static void ipmp_illgrp_set_mtu(ipmp_illgrp_t *, uint_t, uint_t);
static boolean_t ipmp_ill_activate(ill_t *);
static void ipmp_ill_deactivate(ill_t *);
static void ipmp_ill_ire_mark_testhidden(ire_t *, char *);
static void ipmp_ill_ire_clear_testhidden(ire_t *, char *);
static void ipmp_ill_refresh_active_timer_start(ill_t *);
static void ipmp_ill_rtsaddrmsg(ill_t *, int);
static void ipmp_ill_bind_ipif(ill_t *, ipif_t *, enum ip_resolver_action);
static ipif_t *ipmp_ill_unbind_ipif(ill_t *, ipif_t *, boolean_t);
static void ipmp_phyint_get_kstats(phyint_t *, uint64_t *);
static boolean_t ipmp_ipif_is_up_dataaddr(const ipif_t *);
static void ipmp_ncec_delete_nonlocal(ncec_t *, void *);
void
ipmp_init(ip_stack_t *ipst)
{
ipst->ips_ipmp_grp_hash = mod_hash_create_extended("ipmp_grp_hash",
IPMP_GRP_HASH_SIZE, mod_hash_null_keydtor, mod_hash_null_valdtor,
mod_hash_bystr, NULL, mod_hash_strkey_cmp, KM_SLEEP);
rw_init(&ipst->ips_ipmp_lock, NULL, RW_DEFAULT, 0);
}
void
ipmp_destroy(ip_stack_t *ipst)
{
mod_hash_destroy_hash(ipst->ips_ipmp_grp_hash);
rw_destroy(&ipst->ips_ipmp_lock);
}
ipmp_grp_t *
ipmp_grp_create(const char *grname, phyint_t *phyi)
{
ipmp_grp_t *grp;
ip_stack_t *ipst = PHYINT_TO_IPST(phyi);
mod_hash_hndl_t mh;
ASSERT(RW_WRITE_HELD(&ipst->ips_ipmp_lock));
if ((grp = kmem_zalloc(sizeof (ipmp_grp_t), KM_NOSLEEP)) == NULL)
return (NULL);
(void) strlcpy(grp->gr_name, grname, sizeof (grp->gr_name));
(void) strlcpy(grp->gr_ifname, grname, sizeof (grp->gr_ifname));
grp->gr_phyint = phyi;
if (ipmp_grp_create_kstats(grp) != 0) {
kmem_free(grp, sizeof (ipmp_grp_t));
return (NULL);
}
if (mod_hash_reserve_nosleep(ipst->ips_ipmp_grp_hash, &mh) != 0) {
ipmp_grp_destroy_kstats(grp);
kmem_free(grp, sizeof (ipmp_grp_t));
return (NULL);
}
ipmp_grp_insert(grp, mh);
return (grp);
}
static int
ipmp_grp_create_kstats(ipmp_grp_t *grp)
{
kstat_t *ksp;
netstackid_t id = IPMP_GRP_TO_IPST(grp)->ips_netstack->netstack_stackid;
ksp = kstat_create_netstack("ipmp", 0, grp->gr_ifname, "net",
KSTAT_TYPE_NAMED, IPMP_KSTAT_MAX, 0, id);
if (ksp == NULL)
return (ENOMEM);
ksp->ks_update = ipmp_grp_update_kstats;
ksp->ks_private = grp;
bcopy(ipmp_kstats, ksp->ks_data, sizeof (ipmp_kstats));
kstat_install(ksp);
grp->gr_ksp = ksp;
return (0);
}
static int
ipmp_grp_update_kstats(kstat_t *ksp, int rw)
{
uint_t i;
kstat_named_t *kn = KSTAT_NAMED_PTR(ksp);
ipmp_grp_t *grp = ksp->ks_private;
ip_stack_t *ipst = IPMP_GRP_TO_IPST(grp);
ipsq_t *ipsq, *grp_ipsq = grp->gr_phyint->phyint_ipsq;
phyint_t *phyi;
uint64_t phyi_kstats[IPMP_KSTAT_MAX];
if (rw == KSTAT_WRITE)
return (EACCES);
for (i = 0; i < IPMP_KSTAT_MAX; i++) {
if (kn[i].data_type == KSTAT_DATA_UINT32) {
kn[i].value.ui32 = grp->gr_kstats0[i];
} else {
ASSERT(kn[i].data_type == KSTAT_DATA_UINT64);
kn[i].value.ui64 = grp->gr_kstats0[i];
}
}
rw_enter(&ipst->ips_ill_g_lock, RW_READER);
ipsq = grp_ipsq->ipsq_next;
for (; ipsq != grp_ipsq; ipsq = ipsq->ipsq_next) {
phyi = ipsq->ipsq_phyint;
if (phyi == NULL)
continue;
ipmp_phyint_get_kstats(phyi, phyi_kstats);
for (i = 0; i < IPMP_KSTAT_MAX; i++) {
phyi_kstats[i] -= phyi->phyint_kstats0[i];
if (kn[i].data_type == KSTAT_DATA_UINT32)
kn[i].value.ui32 += phyi_kstats[i];
else
kn[i].value.ui64 += phyi_kstats[i];
}
}
kn[IPMP_KSTAT_LINK_UP].value.ui32 =
(grp->gr_phyint->phyint_flags & PHYI_RUNNING) != 0;
rw_exit(&ipst->ips_ill_g_lock);
return (0);
}
static void
ipmp_grp_destroy_kstats(ipmp_grp_t *grp)
{
netstackid_t id = IPMP_GRP_TO_IPST(grp)->ips_netstack->netstack_stackid;
kstat_delete_netstack(grp->gr_ksp, id);
bzero(grp->gr_kstats0, sizeof (grp->gr_kstats0));
grp->gr_ksp = NULL;
}
ipmp_grp_t *
ipmp_grp_lookup(const char *grname, ip_stack_t *ipst)
{
ipmp_grp_t *grp;
ASSERT(RW_LOCK_HELD(&ipst->ips_ipmp_lock));
if (mod_hash_find(ipst->ips_ipmp_grp_hash, (mod_hash_key_t)grname,
(mod_hash_val_t *)&grp) == 0)
return (grp);
return (NULL);
}
void
ipmp_grp_info(const ipmp_grp_t *grp, lifgroupinfo_t *lifgr)
{
ill_t *ill;
ip_stack_t *ipst = IPMP_GRP_TO_IPST(grp);
ASSERT(RW_LOCK_HELD(&ipst->ips_ipmp_lock));
lifgr->gi_v4 = (grp->gr_v4 != NULL);
lifgr->gi_v6 = (grp->gr_v6 != NULL);
lifgr->gi_nv4 = grp->gr_nv4 + grp->gr_pendv4;
lifgr->gi_nv6 = grp->gr_nv6 + grp->gr_pendv6;
lifgr->gi_mactype = grp->gr_nif > 0 ? grp->gr_mactype : SUNW_DL_IPMP;
(void) strlcpy(lifgr->gi_grifname, grp->gr_ifname, LIFNAMSIZ);
lifgr->gi_m4ifname[0] = '\0';
lifgr->gi_m6ifname[0] = '\0';
lifgr->gi_bcifname[0] = '\0';
if (grp->gr_v4 != NULL && (ill = grp->gr_v4->ig_cast_ill) != NULL) {
(void) strlcpy(lifgr->gi_m4ifname, ill->ill_name, LIFNAMSIZ);
(void) strlcpy(lifgr->gi_bcifname, ill->ill_name, LIFNAMSIZ);
}
if (grp->gr_v6 != NULL && (ill = grp->gr_v6->ig_cast_ill) != NULL)
(void) strlcpy(lifgr->gi_m6ifname, ill->ill_name, LIFNAMSIZ);
}
static void
ipmp_grp_insert(ipmp_grp_t *grp, mod_hash_hndl_t mh)
{
int err;
ip_stack_t *ipst = IPMP_GRP_TO_IPST(grp);
ASSERT(RW_WRITE_HELD(&ipst->ips_ipmp_lock));
err = mod_hash_insert_reserve(ipst->ips_ipmp_grp_hash,
(mod_hash_key_t)grp->gr_name, (mod_hash_val_t)grp, mh);
if (err != 0) {
panic("cannot insert IPMP group \"%s\" (err %d)",
grp->gr_name, err);
}
}
static void
ipmp_grp_remove(ipmp_grp_t *grp)
{
int err;
mod_hash_val_t val;
mod_hash_key_t key = (mod_hash_key_t)grp->gr_name;
ip_stack_t *ipst = IPMP_GRP_TO_IPST(grp);
ASSERT(RW_WRITE_HELD(&ipst->ips_ipmp_lock));
err = mod_hash_remove(ipst->ips_ipmp_grp_hash, key, &val);
if (err != 0 || val != grp) {
panic("cannot remove IPMP group \"%s\" (err %d)",
grp->gr_name, err);
}
}
int
ipmp_grp_rename(ipmp_grp_t *grp, const char *grname)
{
mod_hash_hndl_t mh;
ip_stack_t *ipst = IPMP_GRP_TO_IPST(grp);
ASSERT(RW_WRITE_HELD(&ipst->ips_ipmp_lock));
if (grname[0] == '\0')
return (EINVAL);
if (mod_hash_find(ipst->ips_ipmp_grp_hash, (mod_hash_key_t)grname,
(mod_hash_val_t *)&grp) != MH_ERR_NOTFOUND)
return (EEXIST);
if (mod_hash_reserve_nosleep(ipst->ips_ipmp_grp_hash, &mh) != 0)
return (ENOMEM);
ipmp_grp_remove(grp);
(void) strlcpy(grp->gr_name, grname, sizeof (grp->gr_name));
ipmp_grp_insert(grp, mh);
return (0);
}
void
ipmp_grp_destroy(ipmp_grp_t *grp)
{
ip_stack_t *ipst = IPMP_GRP_TO_IPST(grp);
ASSERT(RW_WRITE_HELD(&ipst->ips_ipmp_lock));
if (grp->gr_nif != 0)
panic("cannot destroy IPMP group \"%s\": in use", grp->gr_name);
ipmp_grp_remove(grp);
ipmp_grp_destroy_kstats(grp);
ASSERT(grp->gr_v4 == NULL);
ASSERT(grp->gr_v6 == NULL);
ASSERT(grp->gr_nv4 == 0);
ASSERT(grp->gr_nv6 == 0);
ASSERT(grp->gr_nactif == 0);
ASSERT(grp->gr_linkdownmp == NULL);
grp->gr_phyint = NULL;
kmem_free(grp, sizeof (ipmp_grp_t));
}
static int
ipmp_grp_vet_ill(ipmp_grp_t *grp, ill_t *ill)
{
ip_stack_t *ipst = IPMP_GRP_TO_IPST(grp);
ASSERT(IAM_WRITER_ILL(ill));
ASSERT(RW_LOCK_HELD(&ipst->ips_ipmp_lock));
if (ill->ill_ipif_up_count + ill->ill_ipif_dup_count > 0)
return (EADDRINUSE);
if (ill_appaddr_cnt(ill) != 0)
return (EADDRNOTAVAIL);
if (ill_ptpaddr_cnt(ill) != 0)
return (EINVAL);
if (!(ill->ill_flags & ILLF_MULTICAST))
return (ENOTSUP);
if (ill->ill_flags & (ILLF_NONUD | ILLF_NOARP))
return (ENOTSUP);
if (IS_USESRC_ILL(ill) || IS_USESRC_CLI_ILL(ill))
return (ENOTSUP);
if (grp->gr_nif > 0 && grp->gr_mactype != ill->ill_mactype)
return (EINVAL);
return (0);
}
int
ipmp_grp_vet_phyint(ipmp_grp_t *grp, phyint_t *phyi)
{
int err = 0;
ip_stack_t *ipst = IPMP_GRP_TO_IPST(grp);
ASSERT(IAM_WRITER_IPSQ(phyi->phyint_ipsq));
ASSERT(RW_LOCK_HELD(&ipst->ips_ipmp_lock));
if (phyi->phyint_illv4 != NULL && grp->gr_v4 == NULL ||
phyi->phyint_illv6 != NULL && grp->gr_v6 == NULL)
return (EAFNOSUPPORT);
if (phyi->phyint_illv4 != NULL)
err = ipmp_grp_vet_ill(grp, phyi->phyint_illv4);
if (err == 0 && phyi->phyint_illv6 != NULL)
err = ipmp_grp_vet_ill(grp, phyi->phyint_illv6);
return (err);
}
ipmp_illgrp_t *
ipmp_illgrp_create(ill_t *ill)
{
uint_t mtu = ill->ill_isv6 ? IPV6_MIN_MTU : IP_MIN_MTU;
ipmp_illgrp_t *illg;
ASSERT(IAM_WRITER_ILL(ill));
ASSERT(IS_IPMP(ill));
ASSERT(ill->ill_grp == NULL);
if ((illg = kmem_zalloc(sizeof (ipmp_illgrp_t), KM_NOSLEEP)) == NULL)
return (NULL);
list_create(&illg->ig_if, sizeof (ill_t), offsetof(ill_t, ill_grpnode));
list_create(&illg->ig_actif, sizeof (ill_t),
offsetof(ill_t, ill_actnode));
list_create(&illg->ig_arpent, sizeof (ipmp_arpent_t),
offsetof(ipmp_arpent_t, ia_node));
illg->ig_ipmp_ill = ill;
ill->ill_grp = illg;
ipmp_illgrp_set_mtu(illg, mtu, mtu);
return (illg);
}
void
ipmp_illgrp_destroy(ipmp_illgrp_t *illg)
{
ASSERT(IAM_WRITER_ILL(illg->ig_ipmp_ill));
ASSERT(IS_IPMP(illg->ig_ipmp_ill));
ASSERT(illg->ig_next_ill == NULL);
ASSERT(illg->ig_cast_ill == NULL);
ASSERT(list_is_empty(&illg->ig_arpent));
ASSERT(list_is_empty(&illg->ig_if));
ASSERT(list_is_empty(&illg->ig_actif));
ASSERT(illg->ig_nactif == 0);
illg->ig_ipmp_ill->ill_grp = NULL;
illg->ig_ipmp_ill = NULL;
list_destroy(&illg->ig_if);
list_destroy(&illg->ig_actif);
list_destroy(&illg->ig_arpent);
kmem_free(illg, sizeof (ipmp_illgrp_t));
}
ill_t *
ipmp_illgrp_add_ipif(ipmp_illgrp_t *illg, ipif_t *ipif)
{
ill_t *minill;
ipmp_arpent_t *entp;
ASSERT(IAM_WRITER_IPIF(ipif));
ASSERT(ipmp_ipif_is_dataaddr(ipif));
if (!ipif->ipif_isv6) {
entp = ipmp_illgrp_lookup_arpent(illg, &ipif->ipif_lcl_addr);
if (entp != NULL)
ipmp_illgrp_destroy_arpent(illg, entp);
}
if ((minill = ipmp_illgrp_min_ill(illg)) != NULL)
ipmp_ill_bind_ipif(minill, ipif, Res_act_none);
return (ipif->ipif_bound ? ipif->ipif_bound_ill : NULL);
}
void
ipmp_illgrp_del_ipif(ipmp_illgrp_t *illg, ipif_t *ipif)
{
ill_t *maxill, *boundill = ipif->ipif_bound_ill;
ASSERT(IAM_WRITER_IPIF(ipif));
if (boundill != NULL) {
(void) ipmp_ill_unbind_ipif(boundill, ipif, B_FALSE);
maxill = ipmp_illgrp_max_ill(illg);
if (maxill->ill_bound_cnt > boundill->ill_bound_cnt + 1) {
ipif = ipmp_ill_unbind_ipif(maxill, NULL, B_TRUE);
ipmp_ill_bind_ipif(boundill, ipif, Res_act_rebind);
}
}
}
static ill_t *
ipmp_illgrp_max_ill(ipmp_illgrp_t *illg)
{
ill_t *ill, *bestill = NULL;
ASSERT(IAM_WRITER_ILL(illg->ig_ipmp_ill));
ill = list_head(&illg->ig_actif);
for (; ill != NULL; ill = list_next(&illg->ig_actif, ill)) {
if (bestill == NULL ||
ill->ill_bound_cnt > bestill->ill_bound_cnt) {
bestill = ill;
}
}
return (bestill);
}
static ill_t *
ipmp_illgrp_min_ill(ipmp_illgrp_t *illg)
{
ill_t *ill, *bestill = NULL;
ASSERT(IAM_WRITER_ILL(illg->ig_ipmp_ill));
ill = list_head(&illg->ig_actif);
for (; ill != NULL; ill = list_next(&illg->ig_actif, ill)) {
if (bestill == NULL ||
ill->ill_bound_cnt < bestill->ill_bound_cnt) {
if (ill->ill_bound_cnt == 0)
return (ill);
bestill = ill;
}
}
return (bestill);
}
ill_t *
ipmp_illgrp_ipmp_ill(ipmp_illgrp_t *illg)
{
return (illg->ig_ipmp_ill);
}
ill_t *
ipmp_illgrp_next_ill(ipmp_illgrp_t *illg)
{
ill_t *ill;
ip_stack_t *ipst = IPMP_ILLGRP_TO_IPST(illg);
ASSERT(IAM_WRITER_ILL(illg->ig_ipmp_ill));
rw_enter(&ipst->ips_ipmp_lock, RW_WRITER);
if ((ill = illg->ig_next_ill) != NULL) {
illg->ig_next_ill = list_next(&illg->ig_actif, ill);
if (illg->ig_next_ill == NULL)
illg->ig_next_ill = list_head(&illg->ig_actif);
}
rw_exit(&ipst->ips_ipmp_lock);
return (ill);
}
ill_t *
ipmp_illgrp_hold_next_ill(ipmp_illgrp_t *illg)
{
ill_t *ill;
uint_t i;
ip_stack_t *ipst = IPMP_ILLGRP_TO_IPST(illg);
rw_enter(&ipst->ips_ipmp_lock, RW_WRITER);
for (i = 0; i < illg->ig_nactif; i++) {
ill = illg->ig_next_ill;
illg->ig_next_ill = list_next(&illg->ig_actif, ill);
if (illg->ig_next_ill == NULL)
illg->ig_next_ill = list_head(&illg->ig_actif);
if (ill_check_and_refhold(ill)) {
rw_exit(&ipst->ips_ipmp_lock);
return (ill);
}
}
rw_exit(&ipst->ips_ipmp_lock);
return (NULL);
}
ill_t *
ipmp_illgrp_hold_cast_ill(ipmp_illgrp_t *illg)
{
ill_t *castill;
ip_stack_t *ipst = IPMP_ILLGRP_TO_IPST(illg);
rw_enter(&ipst->ips_ipmp_lock, RW_READER);
castill = illg->ig_cast_ill;
if (castill != NULL && ill_check_and_refhold(castill)) {
rw_exit(&ipst->ips_ipmp_lock);
return (castill);
}
rw_exit(&ipst->ips_ipmp_lock);
return (NULL);
}
static void
ipmp_illgrp_set_cast(ipmp_illgrp_t *illg, ill_t *castill)
{
ill_t *ocastill = illg->ig_cast_ill;
ill_t *ipmp_ill = illg->ig_ipmp_ill;
ip_stack_t *ipst = IPMP_ILLGRP_TO_IPST(illg);
ASSERT(IAM_WRITER_ILL(ipmp_ill));
if (ocastill != NULL) {
DTRACE_PROBE2(ipmp__illgrp__cast__disable, ipmp_illgrp_t *,
illg, ill_t *, ocastill);
ASSERT(ocastill->ill_nom_cast);
ocastill->ill_nom_cast = B_FALSE;
if (ipmp_ill->ill_dl_up)
ill_leave_multicast(ipmp_ill);
ncec_walk(ocastill, ipmp_ncec_delete_nonlocal, ocastill,
ocastill->ill_ipst);
}
rw_enter(&ipst->ips_ipmp_lock, RW_WRITER);
illg->ig_cast_ill = castill;
rw_exit(&ipst->ips_ipmp_lock);
if (castill != NULL) {
DTRACE_PROBE2(ipmp__illgrp__cast__enable, ipmp_illgrp_t *,
illg, ill_t *, castill);
ASSERT(!castill->ill_nom_cast);
castill->ill_nom_cast = B_TRUE;
if (ipmp_ill->ill_dl_up)
ill_recover_multicast(ipmp_ill);
}
}
ipmp_arpent_t *
ipmp_illgrp_create_arpent(ipmp_illgrp_t *illg, boolean_t proxyarp,
ipaddr_t ipaddr, uchar_t *lladdr, size_t lladdr_len, uint16_t flags)
{
ipmp_arpent_t *entp, *oentp;
ASSERT(IAM_WRITER_ILL(illg->ig_ipmp_ill));
if ((entp = kmem_alloc(sizeof (ipmp_arpent_t) + lladdr_len,
KM_NOSLEEP)) == NULL)
return (NULL);
if ((oentp = ipmp_illgrp_lookup_arpent(illg, &entp->ia_ipaddr)) != NULL)
ipmp_illgrp_destroy_arpent(illg, oentp);
entp->ia_ipaddr = ipaddr;
entp->ia_flags = flags;
entp->ia_lladdr_len = lladdr_len;
entp->ia_lladdr = (uchar_t *)&entp[1];
bcopy(lladdr, entp->ia_lladdr, lladdr_len);
entp->ia_proxyarp = proxyarp;
entp->ia_notified = B_TRUE;
list_insert_head(&illg->ig_arpent, entp);
return (entp);
}
void
ipmp_illgrp_destroy_arpent(ipmp_illgrp_t *illg, ipmp_arpent_t *entp)
{
ASSERT(IAM_WRITER_ILL(illg->ig_ipmp_ill));
list_remove(&illg->ig_arpent, entp);
kmem_free(entp, sizeof (ipmp_arpent_t) + entp->ia_lladdr_len);
}
void
ipmp_illgrp_mark_arpent(ipmp_illgrp_t *illg, ipmp_arpent_t *entp)
{
entp->ia_notified = B_TRUE;
}
ipmp_arpent_t *
ipmp_illgrp_lookup_arpent(ipmp_illgrp_t *illg, ipaddr_t *addrp)
{
ipmp_arpent_t *entp = list_head(&illg->ig_arpent);
ASSERT(IAM_WRITER_ILL(illg->ig_ipmp_ill));
if (addrp == NULL)
return (entp);
for (; entp != NULL; entp = list_next(&illg->ig_arpent, entp))
if (entp->ia_ipaddr == *addrp)
break;
return (entp);
}
void
ipmp_illgrp_refresh_arpent(ipmp_illgrp_t *illg)
{
ill_t *ill, *ipmp_ill = illg->ig_ipmp_ill;
uint_t paddrlen = ipmp_ill->ill_phys_addr_length;
ipmp_arpent_t *entp;
ncec_t *ncec;
nce_t *nce;
ASSERT(IAM_WRITER_ILL(ipmp_ill));
ASSERT(!ipmp_ill->ill_isv6);
ill = list_head(&illg->ig_actif);
entp = list_head(&illg->ig_arpent);
for (; entp != NULL; entp = list_next(&illg->ig_arpent, entp)) {
if (ill == NULL || ipmp_ill->ill_ipif_up_count == 0) {
entp->ia_notified = B_FALSE;
continue;
}
ASSERT(paddrlen == ill->ill_phys_addr_length);
if (entp->ia_proxyarp) {
if (bcmp(ill->ill_phys_addr, entp->ia_lladdr,
paddrlen) == 0 && entp->ia_notified)
continue;
bcopy(ill->ill_phys_addr, entp->ia_lladdr, paddrlen);
}
(void) nce_lookup_then_add_v4(ipmp_ill, entp->ia_lladdr,
paddrlen, &entp->ia_ipaddr, entp->ia_flags, ND_UNCHANGED,
&nce);
if (nce == NULL || !entp->ia_proxyarp) {
if (nce != NULL)
nce_refrele(nce);
continue;
}
ncec = nce->nce_common;
mutex_enter(&ncec->ncec_lock);
nce_update(ncec, ND_UNCHANGED, ill->ill_phys_addr);
mutex_exit(&ncec->ncec_lock);
nce_refrele(nce);
ipmp_illgrp_mark_arpent(illg, entp);
if ((ill = list_next(&illg->ig_actif, ill)) == NULL)
ill = list_head(&illg->ig_actif);
}
}
ill_t *
ipmp_illgrp_find_ill(ipmp_illgrp_t *illg, uchar_t *physaddr, uint_t paddrlen)
{
ill_t *ill;
ill_t *ipmp_ill = illg->ig_ipmp_ill;
ip_stack_t *ipst = IPMP_ILLGRP_TO_IPST(illg);
ASSERT(IAM_WRITER_ILL(ipmp_ill) || RW_LOCK_HELD(&ipst->ips_ill_g_lock));
ill = list_head(&illg->ig_if);
for (; ill != NULL; ill = list_next(&illg->ig_if, ill)) {
if (ill->ill_phys_addr_length == paddrlen &&
bcmp(ill->ill_phys_addr, physaddr, paddrlen) == 0)
return (ill);
}
return (NULL);
}
static void
ipmp_illgrp_set_mtu(ipmp_illgrp_t *illg, uint_t mtu, uint_t mc_mtu)
{
ill_t *ill = illg->ig_ipmp_ill;
mblk_t *mp;
ASSERT(illg->ig_mtu == 0 || IAM_WRITER_ILL(ill));
if ((mp = ip_dlnotify_alloc2(DL_NOTE_SDU_SIZE2, mtu, mc_mtu)) != NULL) {
illg->ig_mtu = mtu;
illg->ig_mc_mtu = mc_mtu;
put(ill->ill_rq, mp);
}
}
void
ipmp_illgrp_refresh_mtu(ipmp_illgrp_t *illg)
{
ill_t *ill;
ill_t *ipmp_ill = illg->ig_ipmp_ill;
uint_t mtu = 0;
uint_t mc_mtu = 0;
ASSERT(IAM_WRITER_ILL(ipmp_ill));
ill = list_head(&illg->ig_if);
for (; ill != NULL; ill = list_next(&illg->ig_if, ill)) {
mutex_enter(&ill->ill_lock);
if (mtu == 0 || ill->ill_mtu < mtu)
mtu = ill->ill_mtu;
if (mc_mtu == 0 || ill->ill_mc_mtu < mc_mtu)
mc_mtu = ill->ill_mc_mtu;
mutex_exit(&ill->ill_lock);
}
mtu = MAX(mtu, ipmp_ill->ill_isv6 ? IPV6_MIN_MTU : IP_MIN_MTU);
mc_mtu = MAX(mc_mtu, ipmp_ill->ill_isv6 ? IPV6_MIN_MTU : IP_MIN_MTU);
if (illg->ig_mtu != mtu || illg->ig_mc_mtu != mc_mtu)
ipmp_illgrp_set_mtu(illg, mtu, mc_mtu);
}
void
ipmp_illgrp_link_grp(ipmp_illgrp_t *illg, ipmp_grp_t *grp)
{
ip_stack_t *ipst = IPMP_ILLGRP_TO_IPST(illg);
ASSERT(RW_WRITE_HELD(&ipst->ips_ipmp_lock));
if (illg->ig_ipmp_ill->ill_isv6) {
ASSERT(grp->gr_v6 == NULL || grp->gr_v6 == illg);
grp->gr_v6 = illg;
} else {
ASSERT(grp->gr_v4 == NULL || grp->gr_v4 == illg);
grp->gr_v4 = illg;
}
}
int
ipmp_illgrp_unlink_grp(ipmp_illgrp_t *illg)
{
ipmp_grp_t *grp = illg->ig_ipmp_ill->ill_phyint->phyint_grp;
ip_stack_t *ipst = IPMP_ILLGRP_TO_IPST(illg);
ASSERT(RW_WRITE_HELD(&ipst->ips_ipmp_lock));
if (illg->ig_ipmp_ill->ill_isv6) {
if (grp->gr_nv6 + grp->gr_pendv6 != 0)
return (EBUSY);
grp->gr_v6 = NULL;
} else {
if (grp->gr_nv4 + grp->gr_pendv4 != 0)
return (EBUSY);
grp->gr_v4 = NULL;
}
return (0);
}
void
ipmp_ill_join_illgrp(ill_t *ill, ipmp_illgrp_t *illg)
{
ill_t *ipmp_ill;
ipif_t *ipif;
ip_stack_t *ipst = ill->ill_ipst;
ASSERT(!IS_IPMP(ill) && ill->ill_phyint->phyint_grp != NULL);
ASSERT(IAM_WRITER_ILL(ill));
ASSERT(ill->ill_grp == NULL);
ipmp_ill = illg->ig_ipmp_ill;
rw_enter(&ipst->ips_ipmp_lock, RW_WRITER);
if (ill->ill_isv6)
ill->ill_phyint->phyint_grp->gr_nv6++;
else
ill->ill_phyint->phyint_grp->gr_nv4++;
rw_exit(&ipst->ips_ipmp_lock);
mutex_enter(&ill->ill_lock);
if (ipmp_ill->ill_flags & ILLF_ROUTER)
ill->ill_flags |= ILLF_ROUTER;
else
ill->ill_flags &= ~ILLF_ROUTER;
mutex_exit(&ill->ill_lock);
mutex_enter(&ill->ill_mcast_serializer);
ill->ill_grp_pending = 1;
mutex_exit(&ill->ill_mcast_serializer);
update_conn_ill(ill, ill->ill_ipst);
if (ill->ill_isv6) {
reset_mrt_ill(ill);
} else {
ipif = ill->ill_ipif;
for (; ipif != NULL; ipif = ipif->ipif_next) {
reset_mrt_vif_ipif(ipif);
}
}
ip_purge_allmulti(ill);
if (list_is_empty(&illg->ig_if)) {
ASSERT(ipmp_ill->ill_phys_addr_length == 0);
ipmp_ill->ill_phys_addr_length = ill->ill_phys_addr_length;
ipmp_ill->ill_nd_lla_len = ill->ill_phys_addr_length;
ipmp_ill->ill_type = ill->ill_type;
if (ill->ill_flags & ILLF_COS_ENABLED) {
mutex_enter(&ipmp_ill->ill_lock);
ipmp_ill->ill_flags |= ILLF_COS_ENABLED;
mutex_exit(&ipmp_ill->ill_lock);
}
ipmp_illgrp_set_mtu(illg, ill->ill_mtu, ill->ill_mc_mtu);
} else {
ASSERT(ipmp_ill->ill_phys_addr_length ==
ill->ill_phys_addr_length);
ASSERT(ipmp_ill->ill_type == ill->ill_type);
if (!(ill->ill_flags & ILLF_COS_ENABLED)) {
mutex_enter(&ipmp_ill->ill_lock);
ipmp_ill->ill_flags &= ~ILLF_COS_ENABLED;
mutex_exit(&ipmp_ill->ill_lock);
}
if (illg->ig_mtu > ill->ill_mtu ||
illg->ig_mc_mtu > ill->ill_mc_mtu) {
ipmp_illgrp_set_mtu(illg, ill->ill_mtu,
ill->ill_mc_mtu);
}
}
rw_enter(&ipst->ips_ill_g_lock, RW_WRITER);
list_insert_tail(&illg->ig_if, ill);
ill->ill_grp = illg;
rw_exit(&ipst->ips_ill_g_lock);
mutex_enter(&ill->ill_mcast_serializer);
ill->ill_grp_pending = 0;
mutex_exit(&ill->ill_mcast_serializer);
ire_walk_ill(MATCH_IRE_ILL, 0, ipmp_ill_ire_mark_testhidden, ill, ill);
ipmp_ill_refresh_active(ill);
}
void
ipmp_ill_leave_illgrp(ill_t *ill)
{
ill_t *ipmp_ill;
ipif_t *ipif;
ipmp_arpent_t *entp;
ipmp_illgrp_t *illg = ill->ill_grp;
ip_stack_t *ipst = IPMP_ILLGRP_TO_IPST(illg);
ASSERT(IS_UNDER_IPMP(ill));
ASSERT(IAM_WRITER_ILL(ill));
ASSERT(illg != NULL);
ipmp_ill = illg->ig_ipmp_ill;
(void) untimeout(ill->ill_refresh_tid);
ire_walk_ill(MATCH_IRE_ILL, 0, ipmp_ill_ire_clear_testhidden, ill, ill);
for (ipif = ill->ill_ipif; ipif != NULL; ipif = ipif->ipif_next)
if (ipif->ipif_flags & IPIF_UP)
ipif_multicast_down(ipif);
rw_enter(&ipst->ips_ipmp_lock, RW_WRITER);
if (ill->ill_isv6)
ill->ill_phyint->phyint_grp->gr_nv6--;
else
ill->ill_phyint->phyint_grp->gr_nv4--;
rw_exit(&ipst->ips_ipmp_lock);
if (list_link_active(&ill->ill_actnode))
ipmp_ill_deactivate(ill);
rw_enter(&ipst->ips_ill_g_lock, RW_WRITER);
list_remove(&illg->ig_if, ill);
ill->ill_grp = NULL;
rw_exit(&ipst->ips_ill_g_lock);
for (ipif = ill->ill_ipif; ipif != NULL; ipif = ipif->ipif_next)
if (ipif->ipif_flags & IPIF_UP)
ipif_multicast_up(ipif);
ipmp_illgrp_refresh_mtu(illg);
if (list_is_empty(&illg->ig_if)) {
while ((entp = ipmp_illgrp_lookup_arpent(illg, NULL)) != NULL)
ipmp_illgrp_destroy_arpent(illg, entp);
ipmp_ill->ill_phys_addr_length = 0;
ipmp_ill->ill_nd_lla_len = 0;
ipmp_ill->ill_type = IFT_OTHER;
mutex_enter(&ipmp_ill->ill_lock);
ipmp_ill->ill_flags &= ~ILLF_COS_ENABLED;
mutex_exit(&ipmp_ill->ill_lock);
} else {
if (!(ill->ill_flags & ILLF_COS_ENABLED)) {
ASSERT(!(ipmp_ill->ill_flags & ILLF_COS_ENABLED));
ill = list_head(&illg->ig_if);
do {
if (!(ill->ill_flags & ILLF_COS_ENABLED))
break;
} while ((ill = list_next(&illg->ig_if, ill)) != NULL);
if (ill == NULL) {
mutex_enter(&ipmp_ill->ill_lock);
ipmp_ill->ill_flags |= ILLF_COS_ENABLED;
mutex_exit(&ipmp_ill->ill_lock);
}
}
}
}
static boolean_t
ipmp_ill_try_refresh_active(ill_t *ill)
{
boolean_t refreshed = B_TRUE;
ASSERT(IAM_WRITER_ILL(ill));
ASSERT(IS_UNDER_IPMP(ill));
if (ipmp_ill_is_active(ill)) {
if (!list_link_active(&ill->ill_actnode))
refreshed = ipmp_ill_activate(ill);
} else {
if (list_link_active(&ill->ill_actnode))
ipmp_ill_deactivate(ill);
}
return (refreshed);
}
void
ipmp_ill_refresh_active(ill_t *ill)
{
if (!ipmp_ill_try_refresh_active(ill))
ipmp_ill_refresh_active_timer_start(ill);
}
static void
ipmp_ill_refresh_active_timer(void *ill_arg)
{
ill_t *ill = ill_arg;
boolean_t refreshed = B_FALSE;
mutex_enter(&ill->ill_lock);
ill->ill_refresh_tid = 0;
if (ill->ill_state_flags & ILL_CONDEMNED) {
mutex_exit(&ill->ill_lock);
return;
}
mutex_exit(&ill->ill_lock);
if (ipsq_try_enter(NULL, ill, NULL, NULL, NULL, NEW_OP, B_FALSE)) {
refreshed = ipmp_ill_try_refresh_active(ill);
ipsq_exit(ill->ill_phyint->phyint_ipsq);
}
if (!refreshed)
ipmp_ill_refresh_active_timer_start(ill);
}
static void
ipmp_ill_refresh_active_timer_start(ill_t *ill)
{
mutex_enter(&ill->ill_lock);
if (ill->ill_refresh_tid != 0 ||
(ill->ill_state_flags & ILL_CONDEMNED)) {
mutex_exit(&ill->ill_lock);
return;
}
ill->ill_refresh_tid = timeout(ipmp_ill_refresh_active_timer, ill,
SEC_TO_TICK(IPMP_ILL_REFRESH_TIMEOUT));
mutex_exit(&ill->ill_lock);
}
static boolean_t
ipmp_ill_activate(ill_t *ill)
{
ipif_t *ipif;
mblk_t *linkupmp = NULL, *linkdownmp = NULL;
ipmp_grp_t *grp = ill->ill_phyint->phyint_grp;
ipmp_illgrp_t *illg = ill->ill_grp;
ill_t *maxill;
ip_stack_t *ipst = IPMP_ILLGRP_TO_IPST(illg);
ASSERT(IAM_WRITER_ILL(ill));
ASSERT(IS_UNDER_IPMP(ill));
if (grp->gr_nactif == 0) {
linkupmp = ip_dlnotify_alloc(DL_NOTE_LINK_UP, 0);
linkdownmp = ip_dlnotify_alloc(DL_NOTE_LINK_DOWN, 0);
if (linkupmp == NULL || linkdownmp == NULL)
goto fail;
}
if (list_is_empty(&illg->ig_actif)) {
ipmp_illgrp_set_cast(illg, ill);
ipif = illg->ig_ipmp_ill->ill_ipif;
for (; ipif != NULL; ipif = ipif->ipif_next)
if (ipmp_ipif_is_up_dataaddr(ipif))
ipmp_ill_bind_ipif(ill, ipif, Res_act_initial);
} else {
for (;;) {
maxill = ipmp_illgrp_max_ill(illg);
ASSERT(maxill != NULL);
if (ill->ill_bound_cnt + 1 >= maxill->ill_bound_cnt)
break;
ipif = ipmp_ill_unbind_ipif(maxill, NULL, B_TRUE);
ipmp_ill_bind_ipif(ill, ipif, Res_act_rebind);
}
}
rw_enter(&ipst->ips_ipmp_lock, RW_WRITER);
list_insert_tail(&illg->ig_actif, ill);
illg->ig_nactif++;
illg->ig_next_ill = ill;
rw_exit(&ipst->ips_ipmp_lock);
if (!ill->ill_isv6)
ipmp_illgrp_refresh_arpent(illg);
if (grp->gr_nactif++ == 0) {
ASSERT(grp->gr_linkdownmp == NULL);
grp->gr_linkdownmp = linkdownmp;
put(illg->ig_ipmp_ill->ill_rq, linkupmp);
}
return (B_TRUE);
fail:
freemsg(linkupmp);
freemsg(linkdownmp);
return (B_FALSE);
}
static void
ipmp_ill_deactivate(ill_t *ill)
{
ill_t *minill, *ipmp_ill;
ipif_t *ipif, *ubnextipif, *ubheadipif = NULL;
mblk_t *mp;
ipmp_grp_t *grp = ill->ill_phyint->phyint_grp;
ipmp_illgrp_t *illg = ill->ill_grp;
ip_stack_t *ipst = IPMP_ILLGRP_TO_IPST(illg);
ASSERT(IAM_WRITER_ILL(ill));
ASSERT(IS_UNDER_IPMP(ill));
ipmp_ill = illg->ig_ipmp_ill;
rw_enter(&ipst->ips_ipmp_lock, RW_WRITER);
list_remove(&illg->ig_actif, ill);
illg->ig_nactif--;
illg->ig_next_ill = list_head(&illg->ig_actif);
rw_exit(&ipst->ips_ipmp_lock);
if (ill == illg->ig_cast_ill)
ipmp_illgrp_set_cast(illg, list_head(&illg->ig_actif));
nce_flush(ill, B_TRUE);
while ((ipif = ipmp_ill_unbind_ipif(ill, NULL, B_TRUE)) != NULL) {
ipif->ipif_bound_next = ubheadipif;
ubheadipif = ipif;
}
if (!ill->ill_isv6) {
ipmp_illgrp_refresh_arpent(illg);
}
for (ipif = ubheadipif; ipif != NULL; ipif = ubnextipif) {
ubnextipif = ipif->ipif_bound_next;
ipif->ipif_bound_next = NULL;
if ((minill = ipmp_illgrp_min_ill(illg)) != NULL)
ipmp_ill_bind_ipif(minill, ipif, Res_act_rebind);
}
ire_walk_ill(MATCH_IRE_TYPE, IRE_IF_CLONE, ill_downi_if_clone, ill,
ill);
if (--grp->gr_nactif == 0) {
ncec_walk(ipmp_ill, ncec_delete_per_ill, ipmp_ill, ipst);
mp = grp->gr_linkdownmp;
grp->gr_linkdownmp = NULL;
ASSERT(mp != NULL);
put(ipmp_ill->ill_rq, mp);
}
}
static void
ipmp_ill_rtsaddrmsg(ill_t *ill, int cmd)
{
ipif_t *ipif;
ASSERT(IAM_WRITER_ILL(ill));
ASSERT(cmd == RTM_ADD || cmd == RTM_DELETE);
if (ill->ill_ipif_up_count == 0)
return;
if (cmd == RTM_ADD)
ip_rts_xifmsg(ill->ill_ipif, IPIF_UP, 0, RTSQ_NORMAL);
for (ipif = ill->ill_ipif; ipif != NULL; ipif = ipif->ipif_next)
if (ipif->ipif_flags & IPIF_UP)
ip_rts_newaddrmsg(cmd, 0, ipif, RTSQ_NORMAL);
if (cmd == RTM_DELETE)
ip_rts_xifmsg(ill->ill_ipif, 0, IPIF_UP, RTSQ_NORMAL);
}
static void
ipmp_ill_bind_ipif(ill_t *ill, ipif_t *ipif, enum ip_resolver_action act)
{
int err = 0;
ip_stack_t *ipst = ill->ill_ipst;
ASSERT(IAM_WRITER_ILL(ill) && IAM_WRITER_IPIF(ipif));
ASSERT(IS_UNDER_IPMP(ill) && IS_IPMP(ipif->ipif_ill));
ASSERT(act == Res_act_none || ipmp_ipif_is_up_dataaddr(ipif));
ASSERT(ipif->ipif_bound_ill == NULL);
ASSERT(ipif->ipif_bound_next == NULL);
ipif->ipif_bound_next = ill->ill_bound_ipif;
ill->ill_bound_ipif = ipif;
ill->ill_bound_cnt++;
rw_enter(&ipst->ips_ipmp_lock, RW_WRITER);
ipif->ipif_bound_ill = ill;
rw_exit(&ipst->ips_ipmp_lock);
if (act != Res_act_none) {
if (ill->ill_isv6) {
VERIFY(ipif_resolver_up(ipif, act) == 0);
err = ipif_ndp_up(ipif, act == Res_act_initial);
} else {
err = ipif_resolver_up(ipif, act);
}
ASSERT(err != EINPROGRESS);
}
ipif->ipif_bound = (err == 0);
}
static ipif_t *
ipmp_ill_unbind_ipif(ill_t *ill, ipif_t *ipif, boolean_t notifyres)
{
ipif_t *previpif;
ip_stack_t *ipst = ill->ill_ipst;
ASSERT(IAM_WRITER_ILL(ill));
ASSERT(IS_UNDER_IPMP(ill));
if (ipif == NULL) {
if ((ipif = ill->ill_bound_ipif) == NULL) {
ASSERT(ill->ill_bound_cnt == 0);
return (NULL);
}
}
ASSERT(IAM_WRITER_IPIF(ipif));
ASSERT(IS_IPMP(ipif->ipif_ill));
ASSERT(ipif->ipif_bound_ill == ill);
ASSERT(ill->ill_bound_cnt > 0);
rw_enter(&ipst->ips_ipmp_lock, RW_WRITER);
ipif->ipif_bound_ill = NULL;
rw_exit(&ipst->ips_ipmp_lock);
ill->ill_bound_cnt--;
if (ill->ill_bound_ipif == ipif) {
ill->ill_bound_ipif = ipif->ipif_bound_next;
} else {
previpif = ill->ill_bound_ipif;
while (previpif->ipif_bound_next != ipif)
previpif = previpif->ipif_bound_next;
previpif->ipif_bound_next = ipif->ipif_bound_next;
}
ipif->ipif_bound_next = NULL;
if (notifyres && ipif->ipif_bound) {
if (ill->ill_isv6)
ipif_ndp_down(ipif);
else
(void) ipif_arp_down(ipif);
}
ipif->ipif_bound = B_FALSE;
return (ipif);
}
boolean_t
ipmp_ill_is_active(ill_t *ill)
{
phyint_t *phyi = ill->ill_phyint;
ASSERT(IS_UNDER_IPMP(ill));
ASSERT(IAM_WRITER_ILL(ill) ||
(MUTEX_HELD(&ill->ill_lock) && MUTEX_HELD(&phyi->phyint_lock)));
return (!(ill->ill_ipif_up_count == 0 ||
(phyi->phyint_flags & (PHYI_OFFLINE|PHYI_INACTIVE|PHYI_FAILED))));
}
static void
ipmp_ill_ire_mark_testhidden(ire_t *ire, char *ill_arg)
{
ill_t *ill = (ill_t *)ill_arg;
ASSERT(IAM_WRITER_ILL(ill));
ASSERT(!IS_IPMP(ill));
if (ire->ire_ill != ill)
return;
if (IRE_HIDDEN_TYPE(ire->ire_type)) {
DTRACE_PROBE1(ipmp__mark__testhidden, ire_t *, ire);
ire->ire_testhidden = B_TRUE;
}
}
static void
ipmp_ill_ire_clear_testhidden(ire_t *ire, char *ill_arg)
{
ill_t *ill = (ill_t *)ill_arg;
ASSERT(IAM_WRITER_ILL(ill));
ASSERT(!IS_IPMP(ill));
if (ire->ire_ill == ill) {
DTRACE_PROBE1(ipmp__clear__testhidden, ire_t *, ire);
ire->ire_testhidden = B_FALSE;
}
}
ill_t *
ipmp_ill_hold_ipmp_ill(ill_t *ill)
{
ip_stack_t *ipst = ill->ill_ipst;
ipmp_illgrp_t *illg;
ASSERT(!IS_IPMP(ill));
rw_enter(&ipst->ips_ipmp_lock, RW_READER);
illg = ill->ill_grp;
if (illg != NULL && ill_check_and_refhold(illg->ig_ipmp_ill)) {
rw_exit(&ipst->ips_ipmp_lock);
return (illg->ig_ipmp_ill);
}
rw_exit(&ill->ill_ipst->ips_ipmp_lock);
return (NULL);
}
ill_t *
ipmp_ill_hold_xmit_ill(ill_t *ill, boolean_t is_unicast)
{
ill_t *xmit_ill;
ip_stack_t *ipst = ill->ill_ipst;
rw_enter(&ipst->ips_ill_g_lock, RW_READER);
if (ill->ill_grp == NULL) {
rw_exit(&ipst->ips_ill_g_lock);
ill_refhold(ill);
return (ill);
}
if (is_unicast)
xmit_ill = ipmp_illgrp_hold_next_ill(ill->ill_grp);
else
xmit_ill = ipmp_illgrp_hold_cast_ill(ill->ill_grp);
rw_exit(&ipst->ips_ill_g_lock);
return (xmit_ill);
}
uint_t
ipmp_ill_get_ipmp_ifindex(const ill_t *ill)
{
uint_t ifindex = 0;
ip_stack_t *ipst = ill->ill_ipst;
ipmp_grp_t *grp;
ASSERT(!IS_IPMP(ill));
rw_enter(&ipst->ips_ipmp_lock, RW_READER);
if ((grp = ill->ill_phyint->phyint_grp) != NULL)
ifindex = grp->gr_phyint->phyint_ifindex;
rw_exit(&ipst->ips_ipmp_lock);
return (ifindex);
}
void
ipmp_phyint_join_grp(phyint_t *phyi, ipmp_grp_t *grp)
{
ill_t *ill;
ipsq_t *ipsq = phyi->phyint_ipsq;
ipsq_t *grp_ipsq = grp->gr_phyint->phyint_ipsq;
ip_stack_t *ipst = PHYINT_TO_IPST(phyi);
ASSERT(IAM_WRITER_IPSQ(ipsq));
ASSERT(phyi->phyint_illv4 != NULL || phyi->phyint_illv6 != NULL);
ill = NULL;
if (phyi->phyint_illv4 != NULL) {
ill = phyi->phyint_illv4;
ipmp_ill_rtsaddrmsg(ill, RTM_DELETE);
}
if (phyi->phyint_illv6 != NULL) {
ill = phyi->phyint_illv6;
ipmp_ill_rtsaddrmsg(ill, RTM_DELETE);
}
ipmp_phyint_get_kstats(phyi, phyi->phyint_kstats0);
rw_enter(&ipst->ips_ipmp_lock, RW_WRITER);
phyi->phyint_grp = grp;
if (++grp->gr_nif == 1)
grp->gr_mactype = ill->ill_mactype;
else
ASSERT(grp->gr_mactype == ill->ill_mactype);
ASSERT(ipsq->ipsq_swxop == NULL);
ASSERT(grp_ipsq->ipsq_xop == &grp_ipsq->ipsq_ownxop);
ipsq->ipsq_swxop = &grp_ipsq->ipsq_ownxop;
rw_exit(&ipst->ips_ipmp_lock);
}
void
ipmp_phyint_leave_grp(phyint_t *phyi)
{
uint_t i;
ipsq_t *ipsq = phyi->phyint_ipsq;
ip_stack_t *ipst = PHYINT_TO_IPST(phyi);
uint64_t phyi_kstats[IPMP_KSTAT_MAX];
ASSERT(IAM_WRITER_IPSQ(ipsq));
if (phyi->phyint_illv4 != NULL && IS_UNDER_IPMP(phyi->phyint_illv4))
ipmp_ill_leave_illgrp(phyi->phyint_illv4);
if (phyi->phyint_illv6 != NULL && IS_UNDER_IPMP(phyi->phyint_illv6))
ipmp_ill_leave_illgrp(phyi->phyint_illv6);
if (phyi->phyint_illv4 != NULL)
ipmp_ill_rtsaddrmsg(phyi->phyint_illv4, RTM_ADD);
if (phyi->phyint_illv6 != NULL)
ipmp_ill_rtsaddrmsg(phyi->phyint_illv6, RTM_ADD);
ipmp_phyint_get_kstats(phyi, phyi_kstats);
for (i = 0; i < IPMP_KSTAT_MAX; i++) {
phyi_kstats[i] -= phyi->phyint_kstats0[i];
atomic_add_64(&phyi->phyint_grp->gr_kstats0[i], phyi_kstats[i]);
}
rw_enter(&ipst->ips_ipmp_lock, RW_WRITER);
phyi->phyint_grp->gr_nif--;
phyi->phyint_grp = NULL;
ASSERT(ipsq->ipsq_swxop == NULL);
ipsq->ipsq_swxop = &ipsq->ipsq_ownxop;
rw_exit(&ipst->ips_ipmp_lock);
}
static void
ipmp_phyint_get_kstats(phyint_t *phyi, uint64_t kstats[])
{
uint_t i, j;
const char *name;
kstat_t *ksp;
kstat_named_t *kn;
ip_stack_t *ipst = PHYINT_TO_IPST(phyi);
zoneid_t zoneid;
bzero(kstats, sizeof (kstats[0]) * IPMP_KSTAT_MAX);
zoneid = netstackid_to_zoneid(ipst->ips_netstack->netstack_stackid);
ksp = kstat_hold_byname("link", 0, phyi->phyint_name, zoneid);
if (ksp == NULL)
return;
KSTAT_ENTER(ksp);
if (ksp->ks_data != NULL && ksp->ks_type == KSTAT_TYPE_NAMED) {
(void) KSTAT_UPDATE(ksp, KSTAT_READ);
kn = KSTAT_NAMED_PTR(ksp);
for (i = 0; i < IPMP_KSTAT_MAX; i++) {
name = ipmp_kstats[i].name;
kstats[i] = 0;
for (j = 0; j < ksp->ks_ndata; j++) {
if (strcmp(kn[j].name, name) != 0)
continue;
switch (kn[j].data_type) {
case KSTAT_DATA_INT32:
case KSTAT_DATA_UINT32:
kstats[i] = kn[j].value.ui32;
break;
#ifdef _LP64
case KSTAT_DATA_LONG:
case KSTAT_DATA_ULONG:
kstats[i] = kn[j].value.ul;
break;
#endif
case KSTAT_DATA_INT64:
case KSTAT_DATA_UINT64:
kstats[i] = kn[j].value.ui64;
break;
}
break;
}
}
}
KSTAT_EXIT(ksp);
kstat_rele(ksp);
}
void
ipmp_phyint_refresh_active(phyint_t *phyi)
{
if (phyi->phyint_illv4 != NULL)
ipmp_ill_refresh_active(phyi->phyint_illv4);
if (phyi->phyint_illv6 != NULL)
ipmp_ill_refresh_active(phyi->phyint_illv6);
}
ill_t *
ipmp_ipif_hold_bound_ill(const ipif_t *ipif)
{
ill_t *boundill;
ip_stack_t *ipst = ipif->ipif_ill->ill_ipst;
ASSERT(IS_IPMP(ipif->ipif_ill));
rw_enter(&ipst->ips_ipmp_lock, RW_READER);
boundill = ipif->ipif_bound_ill;
if (boundill != NULL && ill_check_and_refhold(boundill)) {
rw_exit(&ipst->ips_ipmp_lock);
return (boundill);
}
rw_exit(&ipst->ips_ipmp_lock);
return (NULL);
}
ill_t *
ipmp_ipif_bound_ill(const ipif_t *ipif)
{
ASSERT(IAM_WRITER_ILL(ipif->ipif_ill));
ASSERT(IS_IPMP(ipif->ipif_ill));
return (ipif->ipif_bound_ill);
}
boolean_t
ipmp_ipif_is_stubaddr(const ipif_t *ipif)
{
if (ipif->ipif_flags & IPIF_UP)
return (B_FALSE);
if (ipif->ipif_ill->ill_isv6)
return (IN6_IS_ADDR_UNSPECIFIED(&ipif->ipif_v6lcl_addr));
else
return (ipif->ipif_lcl_addr == INADDR_ANY);
}
boolean_t
ipmp_ipif_is_dataaddr(const ipif_t *ipif)
{
if (ipif->ipif_flags & IPIF_NOFAILOVER)
return (B_FALSE);
if (ipif->ipif_ill->ill_isv6)
return (!IN6_IS_ADDR_UNSPECIFIED(&ipif->ipif_v6lcl_addr));
else
return (ipif->ipif_lcl_addr != INADDR_ANY);
}
static boolean_t
ipmp_ipif_is_up_dataaddr(const ipif_t *ipif)
{
return (ipmp_ipif_is_dataaddr(ipif) && (ipif->ipif_flags & IPIF_UP));
}
boolean_t
ipmp_packet_is_probe(mblk_t *mp, ill_t *ill)
{
ip6_t *ip6h = (ip6_t *)mp->b_rptr;
ipha_t *ipha = (ipha_t *)mp->b_rptr;
ASSERT(DB_TYPE(mp) != M_CTL);
if (!IS_UNDER_IPMP(ill))
return (B_FALSE);
if (ill->ill_isv6) {
if (!IN6_IS_ADDR_UNSPECIFIED(&ip6h->ip6_src) &&
ipif_lookup_testaddr_v6(ill, &ip6h->ip6_src, NULL))
return (B_TRUE);
} else {
if (ipha->ipha_src != INADDR_ANY &&
ipif_lookup_testaddr_v4(ill, &ipha->ipha_src, NULL))
return (B_TRUE);
}
return (B_FALSE);
}
static void
ipmp_ncec_delete_nonlocal(ncec_t *ncec, void *ill_arg)
{
if (!NCE_MYADDR(ncec) && ncec->ncec_ill == (ill_t *)ill_arg)
ncec_delete(ncec);
}
void
ipmp_ncec_delete_nce(ncec_t *ncec)
{
ipmp_illgrp_t *illg = ncec->ncec_ill->ill_grp;
ip_stack_t *ipst = ncec->ncec_ipst;
ill_t *ill;
nce_t *nce;
list_t dead;
ASSERT(IS_IPMP(ncec->ncec_ill));
list_create(&dead, sizeof (nce_t), offsetof(nce_t, nce_node));
rw_enter(&ipst->ips_ill_g_lock, RW_READER);
ill = list_head(&illg->ig_if);
for (; ill != NULL; ill = list_next(&illg->ig_if, ill))
nce_fastpath_list_delete(ill, ncec, &dead);
rw_exit(&ipst->ips_ill_g_lock);
while ((nce = list_remove_head(&dead)) != NULL)
nce_refrele(nce);
list_destroy(&dead);
}
void
ipmp_ncec_refresh_nce(ncec_t *ncec)
{
ipmp_illgrp_t *illg = ncec->ncec_ill->ill_grp;
ip_stack_t *ipst = ncec->ncec_ipst;
ill_t *ill;
nce_t *nce, *nce_next;
list_t replace;
ASSERT(IS_IPMP(ncec->ncec_ill));
if (!NCE_ISREACHABLE(ncec))
return;
list_create(&replace, sizeof (nce_t), offsetof(nce_t, nce_node));
rw_enter(&ipst->ips_ipmp_lock, RW_READER);
ill = list_head(&illg->ig_actif);
for (; ill != NULL; ill = list_next(&illg->ig_actif, ill)) {
mutex_enter(&ill->ill_lock);
nce = list_head(&ill->ill_nce);
for (; nce != NULL; nce = nce_next) {
nce_next = list_next(&ill->ill_nce, nce);
if (IN6_ARE_ADDR_EQUAL(&nce->nce_addr,
&ncec->ncec_addr)) {
nce_refhold(nce);
nce_delete(nce);
list_insert_tail(&replace, nce);
}
}
mutex_exit(&ill->ill_lock);
}
rw_exit(&ipst->ips_ipmp_lock);
while ((nce = list_remove_head(&replace)) != NULL) {
if (ncec->ncec_ill->ill_isv6) {
(void) nce_lookup_then_add_v6(nce->nce_ill,
ncec->ncec_lladdr, ncec->ncec_lladdr_length,
&nce->nce_addr, ncec->ncec_flags, ND_UNCHANGED,
NULL);
} else {
ipaddr_t ipaddr;
IN6_V4MAPPED_TO_IPADDR(&ncec->ncec_addr, ipaddr);
(void) nce_lookup_then_add_v4(nce->nce_ill,
ncec->ncec_lladdr, ncec->ncec_lladdr_length,
&ipaddr, ncec->ncec_flags, ND_UNCHANGED, NULL);
}
nce_refrele(nce);
}
list_destroy(&replace);
}