#include <sys/types.h>
#include <sys/stream.h>
#include <sys/dlpi.h>
#include <sys/stropts.h>
#include <sys/strsun.h>
#include <sys/ddi.h>
#include <sys/cmn_err.h>
#include <sys/sdt.h>
#include <sys/zone.h>
#include <sys/param.h>
#include <sys/socket.h>
#include <sys/sockio.h>
#include <net/if.h>
#include <sys/systm.h>
#include <sys/strsubr.h>
#include <net/route.h>
#include <netinet/in.h>
#include <net/if_dl.h>
#include <netinet/ip6.h>
#include <netinet/icmp6.h>
#include <inet/common.h>
#include <inet/mi.h>
#include <inet/nd.h>
#include <inet/arp.h>
#include <inet/ip.h>
#include <inet/ip6.h>
#include <inet/ip_if.h>
#include <inet/ip_ndp.h>
#include <inet/ip_multi.h>
#include <inet/ipclassifier.h>
#include <inet/ipsec_impl.h>
#include <inet/sctp_ip.h>
#include <inet/ip_listutils.h>
#include <inet/udp_impl.h>
static void ilm_bld_flists(conn_t *conn, void *arg);
static void ilm_gen_filter(ilm_t *ilm, mcast_record_t *fmode,
slist_t *flist);
static ilm_t *ilm_add(ill_t *ill, const in6_addr_t *group,
ilg_stat_t ilgstat, mcast_record_t ilg_fmode, slist_t *ilg_flist,
zoneid_t zoneid);
static void ilm_delete(ilm_t *ilm);
static int ilm_numentries(ill_t *, const in6_addr_t *);
static ilm_t *ip_addmulti_serial(const in6_addr_t *, ill_t *, zoneid_t,
ilg_stat_t, mcast_record_t, slist_t *, int *);
static ilm_t *ip_addmulti_impl(const in6_addr_t *, ill_t *,
zoneid_t, ilg_stat_t, mcast_record_t, slist_t *, int *);
static int ip_delmulti_serial(ilm_t *, boolean_t, boolean_t);
static int ip_delmulti_impl(ilm_t *, boolean_t, boolean_t);
static int ip_ll_multireq(ill_t *ill, const in6_addr_t *group,
t_uscalar_t);
static ilg_t *ilg_lookup(conn_t *, const in6_addr_t *, ipaddr_t ifaddr,
uint_t ifindex);
static int ilg_add(conn_t *connp, const in6_addr_t *group,
ipaddr_t ifaddr, uint_t ifindex, ill_t *ill, mcast_record_t fmode,
const in6_addr_t *v6src);
static void ilg_delete(conn_t *connp, ilg_t *ilg, const in6_addr_t *src);
static mblk_t *ill_create_dl(ill_t *ill, uint32_t dl_primitive,
uint32_t *addr_lenp, uint32_t *addr_offp);
static int ip_opt_delete_group_excl(conn_t *connp,
const in6_addr_t *v6group, ipaddr_t ifaddr, uint_t ifindex,
mcast_record_t fmode, const in6_addr_t *v6src);
static ilm_t *ilm_lookup(ill_t *, const in6_addr_t *, zoneid_t);
static int ip_msfilter_ill(conn_t *, mblk_t *, const ip_ioctl_cmd_t *,
ill_t **);
static void ilg_check_detach(conn_t *, ill_t *);
static void ilg_check_reattach(conn_t *, ill_t *);
#define GETSTRUCT(structure, number) \
((structure *)mi_zalloc(sizeof (structure) * (number)))
static void
ilg_refhold(ilg_t *ilg)
{
ASSERT(ilg->ilg_refcnt != 0);
ASSERT(!ilg->ilg_condemned);
ASSERT(RW_WRITE_HELD(&ilg->ilg_connp->conn_ilg_lock));
ilg->ilg_refcnt++;
}
static void
ilg_inactive(ilg_t *ilg)
{
ASSERT(ilg->ilg_ill == NULL);
ASSERT(ilg->ilg_ilm == NULL);
ASSERT(ilg->ilg_filter == NULL);
ASSERT(ilg->ilg_condemned);
*ilg->ilg_ptpn = ilg->ilg_next;
if (ilg->ilg_next != NULL)
ilg->ilg_next->ilg_ptpn = ilg->ilg_ptpn;
ilg->ilg_next = NULL;
ilg->ilg_ptpn = NULL;
ilg->ilg_connp = NULL;
kmem_free(ilg, sizeof (*ilg));
}
static void
ilg_refrele(ilg_t *ilg)
{
ASSERT(RW_WRITE_HELD(&ilg->ilg_connp->conn_ilg_lock));
ASSERT(ilg->ilg_refcnt != 0);
if (--ilg->ilg_refcnt == 0)
ilg_inactive(ilg);
}
static void
ilg_transfer_hold(ilg_t *held_ilg, ilg_t *ilg)
{
if (held_ilg == ilg)
return;
ilg_refhold(ilg);
if (held_ilg != NULL)
ilg_refrele(held_ilg);
}
static ilg_t *
conn_ilg_alloc(conn_t *connp, int *errp)
{
ilg_t *ilg;
ASSERT(RW_WRITE_HELD(&connp->conn_ilg_lock));
if (connp->conn_state_flags & CONN_CLOSING) {
*errp = EINVAL;
return (NULL);
}
ilg = kmem_zalloc(sizeof (ilg_t), KM_NOSLEEP);
if (ilg == NULL) {
*errp = ENOMEM;
return (NULL);
}
ilg->ilg_refcnt = 1;
if (connp->conn_ilg != NULL)
connp->conn_ilg->ilg_ptpn = &ilg->ilg_next;
ilg->ilg_next = connp->conn_ilg;
ilg->ilg_ptpn = &connp->conn_ilg;
connp->conn_ilg = ilg;
ilg->ilg_connp = connp;
return (ilg);
}
typedef struct ilm_fbld_s {
ilm_t *fbld_ilm;
int fbld_in_cnt;
int fbld_ex_cnt;
slist_t fbld_in;
slist_t fbld_ex;
boolean_t fbld_in_overflow;
} ilm_fbld_t;
static void
ilm_bld_flists(conn_t *connp, void *arg)
{
ilg_t *ilg;
ilm_fbld_t *fbld = (ilm_fbld_t *)(arg);
ilm_t *ilm = fbld->fbld_ilm;
in6_addr_t *v6group = &ilm->ilm_v6addr;
if (connp->conn_ilg == NULL)
return;
if (fbld->fbld_ex_cnt > 0 && fbld->fbld_ex.sl_numsrc == 0)
return;
ASSERT(MUTEX_HELD(&ilm->ilm_ill->ill_mcast_serializer));
rw_enter(&connp->conn_ilg_lock, RW_READER);
for (ilg = connp->conn_ilg; ilg != NULL; ilg = ilg->ilg_next) {
if (ilg->ilg_condemned)
continue;
if ((ilg->ilg_ill == ilm->ilm_ill) &&
IN6_ARE_ADDR_EQUAL(&ilg->ilg_v6group, v6group)) {
if (ilg->ilg_fmode == MODE_IS_INCLUDE) {
fbld->fbld_in_cnt++;
if (!fbld->fbld_in_overflow)
l_union_in_a(&fbld->fbld_in,
ilg->ilg_filter,
&fbld->fbld_in_overflow);
} else {
fbld->fbld_ex_cnt++;
if (fbld->fbld_ex_cnt == 1) {
if (ilg->ilg_filter != NULL)
l_copy(ilg->ilg_filter,
&fbld->fbld_ex);
} else {
l_intersection_in_a(&fbld->fbld_ex,
ilg->ilg_filter);
}
}
break;
}
}
rw_exit(&connp->conn_ilg_lock);
}
static void
ilm_gen_filter(ilm_t *ilm, mcast_record_t *fmode, slist_t *flist)
{
ilm_fbld_t fbld;
ip_stack_t *ipst = ilm->ilm_ipst;
fbld.fbld_ilm = ilm;
fbld.fbld_in_cnt = fbld.fbld_ex_cnt = 0;
fbld.fbld_in.sl_numsrc = fbld.fbld_ex.sl_numsrc = 0;
fbld.fbld_in_overflow = B_FALSE;
ipcl_walk(ilm_bld_flists, (caddr_t)&fbld, ipst);
if (fbld.fbld_in_overflow) {
*fmode = MODE_IS_EXCLUDE;
flist->sl_numsrc = 0;
return;
}
if (fbld.fbld_in_cnt == 0 && fbld.fbld_ex_cnt == 0) {
*fmode = MODE_IS_INCLUDE;
flist->sl_numsrc = 0;
return;
}
if (fbld.fbld_ex_cnt == 0) {
*fmode = MODE_IS_INCLUDE;
l_copy(&fbld.fbld_in, flist);
} else {
*fmode = MODE_IS_EXCLUDE;
l_difference(&fbld.fbld_ex, &fbld.fbld_in, flist);
}
}
static int
ilm_update_add(ilm_t *ilm, ilg_stat_t ilgstat, slist_t *ilg_flist)
{
mcast_record_t fmode;
slist_t *flist;
boolean_t fdefault;
char buf[INET6_ADDRSTRLEN];
ill_t *ill = ilm->ilm_ill;
fdefault = (ilm->ilm_no_ilg_cnt > 0) ||
(ilgstat == ILGSTAT_NONE) || SLIST_IS_EMPTY(ilg_flist);
if ((flist = l_alloc()) == NULL)
return (ENOMEM);
if (!fdefault && ilm->ilm_filter == NULL) {
ilm->ilm_filter = l_alloc();
if (ilm->ilm_filter == NULL) {
l_free(flist);
return (ENOMEM);
}
}
if (ilgstat != ILGSTAT_CHANGE)
ilm->ilm_refcnt++;
if (ilgstat == ILGSTAT_NONE)
ilm->ilm_no_ilg_cnt++;
if (fdefault) {
fmode = MODE_IS_EXCLUDE;
flist->sl_numsrc = 0;
} else {
ilm_gen_filter(ilm, &fmode, flist);
}
if ((ilm->ilm_fmode == fmode) &&
!lists_are_different(ilm->ilm_filter, flist)) {
l_free(flist);
return (0);
}
if (!IS_LOOPBACK(ill)) {
if (ill->ill_isv6)
mld_statechange(ilm, fmode, flist);
else
igmp_statechange(ilm, fmode, flist);
}
ilm->ilm_fmode = fmode;
if (flist->sl_numsrc > 0)
l_copy(flist, ilm->ilm_filter);
else
CLEAR_SLIST(ilm->ilm_filter);
ip1dbg(("ilm_update: new if filter mode %d, group %s\n", ilm->ilm_fmode,
inet_ntop(AF_INET6, &ilm->ilm_v6addr, buf, sizeof (buf))));
l_free(flist);
return (0);
}
static int
ilm_update_del(ilm_t *ilm)
{
mcast_record_t fmode;
slist_t *flist;
ill_t *ill = ilm->ilm_ill;
ip1dbg(("ilm_update_del: still %d left; updating state\n",
ilm->ilm_refcnt));
if ((flist = l_alloc()) == NULL)
return (ENOMEM);
if (ilm->ilm_no_ilg_cnt != 0) {
fmode = MODE_IS_EXCLUDE;
flist->sl_numsrc = 0;
} else {
ilm_gen_filter(ilm, &fmode, flist);
}
if ((ilm->ilm_fmode == fmode) &&
(!lists_are_different(ilm->ilm_filter, flist))) {
l_free(flist);
return (0);
}
if (!IS_LOOPBACK(ill)) {
if (ill->ill_isv6)
mld_statechange(ilm, fmode, flist);
else
igmp_statechange(ilm, fmode, flist);
}
ilm->ilm_fmode = fmode;
if (flist->sl_numsrc > 0) {
if (ilm->ilm_filter == NULL) {
ilm->ilm_filter = l_alloc();
if (ilm->ilm_filter == NULL) {
char buf[INET6_ADDRSTRLEN];
ip1dbg(("ilm_update_del: failed to alloc ilm "
"filter; no source filtering for %s on %s",
inet_ntop(AF_INET6, &ilm->ilm_v6addr,
buf, sizeof (buf)), ill->ill_name));
ilm->ilm_fmode = MODE_IS_EXCLUDE;
l_free(flist);
return (0);
}
}
l_copy(flist, ilm->ilm_filter);
} else {
CLEAR_SLIST(ilm->ilm_filter);
}
l_free(flist);
return (0);
}
ilm_t *
ip_addmulti(const in6_addr_t *v6group, ill_t *ill, zoneid_t zoneid,
int *errorp)
{
ilm_t *ilm;
mutex_enter(&ill->ill_mcast_serializer);
ilm = ip_addmulti_serial(v6group, ill, zoneid, ILGSTAT_NONE,
MODE_IS_EXCLUDE, NULL, errorp);
mutex_exit(&ill->ill_mcast_serializer);
ill_mcast_send_queued(ill);
ill_dlpi_send_queued(ill);
return (ilm);
}
static ilm_t *
ip_addmulti_serial(const in6_addr_t *v6group, ill_t *ill, zoneid_t zoneid,
ilg_stat_t ilgstat, mcast_record_t ilg_fmode, slist_t *ilg_flist,
int *errorp)
{
ilm_t *ilm;
ASSERT(MUTEX_HELD(&ill->ill_mcast_serializer));
if (ill->ill_isv6) {
if (!IN6_IS_ADDR_MULTICAST(v6group) &&
!IN6_IS_ADDR_UNSPECIFIED(v6group)) {
*errorp = EINVAL;
return (NULL);
}
} else {
if (IN6_IS_ADDR_V4MAPPED(v6group)) {
ipaddr_t v4group;
IN6_V4MAPPED_TO_IPADDR(v6group, v4group);
ASSERT(!IS_UNDER_IPMP(ill));
if (!CLASSD(v4group)) {
*errorp = EINVAL;
return (NULL);
}
} else if (!IN6_IS_ADDR_UNSPECIFIED(v6group)) {
*errorp = EINVAL;
return (NULL);
}
}
if (IS_UNDER_IPMP(ill)) {
*errorp = EINVAL;
return (NULL);
}
rw_enter(&ill->ill_mcast_lock, RW_WRITER);
if (ill->ill_state_flags & ILL_CONDEMNED) {
rw_exit(&ill->ill_mcast_lock);
*errorp = ENXIO;
return (NULL);
}
ilm = ip_addmulti_impl(v6group, ill, zoneid, ilgstat, ilg_fmode,
ilg_flist, errorp);
rw_exit(&ill->ill_mcast_lock);
ill_mcast_timer_start(ill->ill_ipst);
return (ilm);
}
static ilm_t *
ip_addmulti_impl(const in6_addr_t *v6group, ill_t *ill, zoneid_t zoneid,
ilg_stat_t ilgstat, mcast_record_t ilg_fmode, slist_t *ilg_flist,
int *errorp)
{
ilm_t *ilm;
int ret = 0;
ASSERT(RW_WRITE_HELD(&ill->ill_mcast_lock));
*errorp = 0;
ilm = ilm_lookup(ill, v6group, zoneid);
if (ilm != NULL) {
ret = ilm_update_add(ilm, ilgstat, ilg_flist);
if (ret == 0)
return (ilm);
*errorp = ret;
return (NULL);
}
ASSERT(ilgstat != ILGSTAT_CHANGE);
ilm = ilm_add(ill, v6group, ilgstat, ilg_fmode, ilg_flist, zoneid);
if (ilm == NULL) {
*errorp = ENOMEM;
return (NULL);
}
if (IN6_IS_ADDR_UNSPECIFIED(v6group)) {
if (ilm_numentries(ill, v6group) == 1) {
ret = ill_join_allmulti(ill);
}
} else {
if (!IS_LOOPBACK(ill)) {
if (ill->ill_isv6)
mld_joingroup(ilm);
else
igmp_joingroup(ilm);
}
if (ilm_numentries(ill, v6group) == 1) {
ret = ip_ll_multireq(ill, v6group, DL_ENABMULTI_REQ);
}
}
if (ret != 0) {
if (ret == ENETDOWN) {
char buf[INET6_ADDRSTRLEN];
ip0dbg(("ip_addmulti: ENETDOWN for %s on %s",
inet_ntop(AF_INET6, &ilm->ilm_v6addr,
buf, sizeof (buf)), ill->ill_name));
}
ilm_delete(ilm);
*errorp = ret;
return (NULL);
} else {
return (ilm);
}
}
boolean_t
ip_mphysaddr_add(ill_t *ill, uchar_t *hw_addr)
{
multiphysaddr_t *mpa = NULL;
int hw_addr_length = ill->ill_phys_addr_length;
mutex_enter(&ill->ill_lock);
for (mpa = ill->ill_mphysaddr_list; mpa != NULL; mpa = mpa->mpa_next) {
if (bcmp(hw_addr, &(mpa->mpa_addr[0]), hw_addr_length) == 0) {
mpa->mpa_refcnt++;
mutex_exit(&ill->ill_lock);
return (B_FALSE);
}
}
mpa = kmem_zalloc(sizeof (multiphysaddr_t), KM_NOSLEEP);
if (mpa == NULL) {
ip0dbg(("ip_mphysaddr_add: ENOMEM. Some multicast apps"
" may have issues. hw_addr: %p ill_name: %s\n",
(void *)hw_addr, ill->ill_name));
mutex_exit(&ill->ill_lock);
return (B_TRUE);
}
bcopy(hw_addr, &(mpa->mpa_addr[0]), hw_addr_length);
mpa->mpa_refcnt = 1;
mpa->mpa_next = ill->ill_mphysaddr_list;
ill->ill_mphysaddr_list = mpa;
mutex_exit(&ill->ill_lock);
return (B_TRUE);
}
boolean_t
ip_mphysaddr_del(ill_t *ill, uchar_t *hw_addr)
{
multiphysaddr_t *mpap = NULL, **mpapp = NULL;
int hw_addr_length = ill->ill_phys_addr_length;
boolean_t ret = B_FALSE;
mutex_enter(&ill->ill_lock);
for (mpapp = &ill->ill_mphysaddr_list; (mpap = *mpapp) != NULL;
mpapp = &(mpap->mpa_next)) {
if (bcmp(hw_addr, &(mpap->mpa_addr[0]), hw_addr_length) == 0)
break;
}
if (mpap == NULL) {
ip0dbg(("ip_mphysaddr_del: No entry for this addr. Some "
"multicast apps might have had issues. hw_addr: %p "
" ill_name: %s\n", (void *)hw_addr, ill->ill_name));
ret = B_TRUE;
} else if (--mpap->mpa_refcnt == 0) {
*mpapp = mpap->mpa_next;
kmem_free(mpap, sizeof (multiphysaddr_t));
ret = B_TRUE;
}
mutex_exit(&ill->ill_lock);
return (ret);
}
static int
ip_ll_send_multireq(ill_t *ill, const in6_addr_t *v6groupp, t_uscalar_t prim)
{
mblk_t *mp;
uint32_t addrlen, addroff;
ill_t *release_ill = NULL;
uchar_t *cp;
int err = 0;
ASSERT(RW_LOCK_HELD(&ill->ill_mcast_lock));
if (IS_IPMP(ill)) {
release_ill = ipmp_illgrp_hold_cast_ill(ill->ill_grp);
if (release_ill == NULL) {
ip1dbg(("ip_ll_send_multireq: no cast_ill for %s %d\n",
ill->ill_name, ill->ill_isv6));
return (0);
}
ill = release_ill;
}
mp = ill_create_dl(ill, prim, &addrlen, &addroff);
if (mp == NULL) {
err = ENOMEM;
goto done;
}
mp = ndp_mcastreq(ill, v6groupp, addrlen, addroff, mp);
if (mp == NULL) {
ip0dbg(("null from ndp_mcastreq(ill %s)\n", ill->ill_name));
err = ENOMEM;
goto done;
}
cp = mp->b_rptr;
switch (((union DL_primitives *)cp)->dl_primitive) {
case DL_ENABMULTI_REQ:
cp += ((dl_enabmulti_req_t *)cp)->dl_addr_offset;
if (!ip_mphysaddr_add(ill, cp)) {
freemsg(mp);
err = 0;
goto done;
}
mutex_enter(&ill->ill_lock);
if (ill->ill_dlpi_multicast_state == IDS_UNKNOWN)
ill->ill_dlpi_multicast_state = IDS_INPROGRESS;
mutex_exit(&ill->ill_lock);
break;
case DL_DISABMULTI_REQ:
cp += ((dl_disabmulti_req_t *)cp)->dl_addr_offset;
if (!ip_mphysaddr_del(ill, cp)) {
freemsg(mp);
err = 0;
goto done;
}
}
ill_dlpi_queue(ill, mp);
done:
if (release_ill != NULL)
ill_refrele(release_ill);
return (err);
}
static int
ip_ll_multireq(ill_t *ill, const in6_addr_t *v6groupp, t_uscalar_t prim)
{
if (ill->ill_net_type != IRE_IF_RESOLVER ||
ill->ill_ipif->ipif_flags & IPIF_POINTOPOINT) {
ip1dbg(("ip_ll_multireq: not resolver\n"));
return (0);
}
if (ill->ill_phyint->phyint_flags & PHYI_MULTI_BCAST) {
ip1dbg(("ip_ll_multireq: MULTI_BCAST\n"));
return (0);
}
return (ip_ll_send_multireq(ill, v6groupp, prim));
}
int
ip_delmulti(ilm_t *ilm)
{
ill_t *ill = ilm->ilm_ill;
int error;
mutex_enter(&ill->ill_mcast_serializer);
error = ip_delmulti_serial(ilm, B_TRUE, B_TRUE);
mutex_exit(&ill->ill_mcast_serializer);
ill_mcast_send_queued(ill);
ill_dlpi_send_queued(ill);
return (error);
}
static int
ip_delmulti_serial(ilm_t *ilm, boolean_t no_ilg, boolean_t leaving)
{
ill_t *ill = ilm->ilm_ill;
int ret;
ASSERT(MUTEX_HELD(&ill->ill_mcast_serializer));
ASSERT(!(IS_UNDER_IPMP(ill)));
rw_enter(&ill->ill_mcast_lock, RW_WRITER);
ret = ip_delmulti_impl(ilm, no_ilg, leaving);
rw_exit(&ill->ill_mcast_lock);
ill_mcast_timer_start(ill->ill_ipst);
return (ret);
}
static int
ip_delmulti_impl(ilm_t *ilm, boolean_t no_ilg, boolean_t leaving)
{
ill_t *ill = ilm->ilm_ill;
int error;
in6_addr_t v6group;
ASSERT(RW_WRITE_HELD(&ill->ill_mcast_lock));
if (no_ilg)
ilm->ilm_no_ilg_cnt--;
if (leaving)
ilm->ilm_refcnt--;
if (ilm->ilm_refcnt > 0)
return (ilm_update_del(ilm));
v6group = ilm->ilm_v6addr;
if (IN6_IS_ADDR_UNSPECIFIED(&ilm->ilm_v6addr)) {
ilm_delete(ilm);
if (ilm_numentries(ill, &v6group) != 0)
return (0);
ill_leave_allmulti(ill);
return (0);
}
if (!IS_LOOPBACK(ill)) {
if (ill->ill_isv6)
mld_leavegroup(ilm);
else
igmp_leavegroup(ilm);
}
ilm_delete(ilm);
if (ilm_numentries(ill, &v6group) != 0)
return (0);
error = ip_ll_multireq(ill, &v6group, DL_DISABMULTI_REQ);
if (error == ENETDOWN) {
char buf[INET6_ADDRSTRLEN];
ip0dbg(("ip_delmulti: ENETDOWN for %s on %s",
inet_ntop(AF_INET6, &v6group, buf, sizeof (buf)),
ill->ill_name));
}
return (error);
}
int
ill_join_allmulti(ill_t *ill)
{
mblk_t *promiscon_mp, *promiscoff_mp = NULL;
uint32_t addrlen, addroff;
ill_t *release_ill = NULL;
ASSERT(RW_WRITE_HELD(&ill->ill_mcast_lock));
if (IS_LOOPBACK(ill))
return (0);
if (!ill->ill_dl_up) {
return (ENETDOWN);
}
if (IS_IPMP(ill)) {
release_ill = ipmp_illgrp_hold_cast_ill(ill->ill_grp);
if (release_ill == NULL) {
ip1dbg(("ill_join_allmulti: no cast_ill for %s %d\n",
ill->ill_name, ill->ill_isv6));
return (0);
}
ill = release_ill;
if (!ill->ill_dl_up) {
ill_refrele(ill);
return (ENETDOWN);
}
}
if ((ill->ill_net_type == IRE_IF_RESOLVER) &&
!(ill->ill_phyint->phyint_flags & PHYI_MULTI_BCAST)) {
promiscon_mp = ill_create_dl(ill, DL_PROMISCON_REQ,
&addrlen, &addroff);
if (ill->ill_promiscoff_mp == NULL)
promiscoff_mp = ill_create_dl(ill, DL_PROMISCOFF_REQ,
&addrlen, &addroff);
if (promiscon_mp == NULL ||
(ill->ill_promiscoff_mp == NULL && promiscoff_mp == NULL)) {
freemsg(promiscon_mp);
freemsg(promiscoff_mp);
if (release_ill != NULL)
ill_refrele(release_ill);
return (ENOMEM);
}
if (ill->ill_promiscoff_mp == NULL)
ill->ill_promiscoff_mp = promiscoff_mp;
ill_dlpi_queue(ill, promiscon_mp);
}
if (release_ill != NULL)
ill_refrele(release_ill);
return (0);
}
void
ill_leave_allmulti(ill_t *ill)
{
mblk_t *promiscoff_mp;
ill_t *release_ill = NULL;
ASSERT(RW_WRITE_HELD(&ill->ill_mcast_lock));
if (IS_LOOPBACK(ill))
return;
if (!ill->ill_dl_up) {
return;
}
if (IS_IPMP(ill)) {
release_ill = ipmp_illgrp_hold_cast_ill(ill->ill_grp);
if (release_ill == NULL) {
ip1dbg(("ill_leave_allmulti: no cast_ill on %s %d\n",
ill->ill_name, ill->ill_isv6));
return;
}
ill = release_ill;
if (!ill->ill_dl_up)
goto done;
}
promiscoff_mp = ill->ill_promiscoff_mp;
if (promiscoff_mp != NULL) {
ill->ill_promiscoff_mp = NULL;
ill_dlpi_queue(ill, promiscoff_mp);
}
done:
if (release_ill != NULL)
ill_refrele(release_ill);
}
int
ip_join_allmulti(uint_t ifindex, boolean_t isv6, ip_stack_t *ipst)
{
ill_t *ill;
int ret;
ilm_t *ilm;
ill = ill_lookup_on_ifindex(ifindex, isv6, ipst);
if (ill == NULL)
return (ENODEV);
if (IS_UNDER_IPMP(ill)) {
ill_refrele(ill);
return (0);
}
mutex_enter(&ill->ill_lock);
if (ill->ill_ipallmulti_cnt > 0) {
ASSERT(ill->ill_ipallmulti_ilm != NULL);
ill->ill_ipallmulti_cnt++;
mutex_exit(&ill->ill_lock);
goto done;
}
mutex_exit(&ill->ill_lock);
ilm = ip_addmulti(&ipv6_all_zeros, ill, ill->ill_zoneid, &ret);
if (ilm == NULL) {
ASSERT(ret != 0);
ill_refrele(ill);
return (ret);
}
mutex_enter(&ill->ill_lock);
if (ill->ill_ipallmulti_cnt > 0) {
(void) ip_delmulti(ilm);
mutex_exit(&ill->ill_lock);
goto done;
}
ASSERT(ill->ill_ipallmulti_ilm == NULL);
ill->ill_ipallmulti_ilm = ilm;
ill->ill_ipallmulti_cnt++;
mutex_exit(&ill->ill_lock);
done:
ill_refrele(ill);
return (0);
}
int
ip_leave_allmulti(uint_t ifindex, boolean_t isv6, ip_stack_t *ipst)
{
ill_t *ill;
ilm_t *ilm;
ill = ill_lookup_on_ifindex(ifindex, isv6, ipst);
if (ill == NULL)
return (ENODEV);
if (IS_UNDER_IPMP(ill)) {
ill_refrele(ill);
return (0);
}
mutex_enter(&ill->ill_lock);
if (ill->ill_ipallmulti_cnt == 0) {
mutex_exit(&ill->ill_lock);
goto done;
}
ill->ill_ipallmulti_cnt--;
if (ill->ill_ipallmulti_cnt == 0) {
ilm = ill->ill_ipallmulti_ilm;
ill->ill_ipallmulti_ilm = NULL;
} else {
ilm = NULL;
}
mutex_exit(&ill->ill_lock);
if (ilm != NULL)
(void) ip_delmulti(ilm);
done:
ill_refrele(ill);
return (0);
}
void
ip_purge_allmulti(ill_t *ill)
{
ilm_t *ilm;
ASSERT(IAM_WRITER_ILL(ill));
mutex_enter(&ill->ill_lock);
ilm = ill->ill_ipallmulti_ilm;
ill->ill_ipallmulti_ilm = NULL;
ill->ill_ipallmulti_cnt = 0;
mutex_exit(&ill->ill_lock);
if (ilm != NULL)
(void) ip_delmulti(ilm);
}
static mblk_t *
ill_create_dl(ill_t *ill, uint32_t dl_primitive,
uint32_t *addr_lenp, uint32_t *addr_offp)
{
mblk_t *mp;
uint32_t hw_addr_length;
char *cp;
uint32_t offset;
uint32_t length;
uint32_t size;
*addr_lenp = *addr_offp = 0;
hw_addr_length = ill->ill_phys_addr_length;
if (!hw_addr_length) {
ip0dbg(("ip_create_dl: hw addr length = 0\n"));
return (NULL);
}
switch (dl_primitive) {
case DL_ENABMULTI_REQ:
length = sizeof (dl_enabmulti_req_t);
size = length + hw_addr_length;
break;
case DL_DISABMULTI_REQ:
length = sizeof (dl_disabmulti_req_t);
size = length + hw_addr_length;
break;
case DL_PROMISCON_REQ:
case DL_PROMISCOFF_REQ:
size = length = sizeof (dl_promiscon_req_t);
break;
default:
return (NULL);
}
mp = allocb(size, BPRI_HI);
if (!mp)
return (NULL);
mp->b_wptr += size;
mp->b_datap->db_type = M_PROTO;
cp = (char *)mp->b_rptr;
offset = length;
switch (dl_primitive) {
case DL_ENABMULTI_REQ: {
dl_enabmulti_req_t *dl = (dl_enabmulti_req_t *)cp;
dl->dl_primitive = dl_primitive;
dl->dl_addr_offset = offset;
*addr_lenp = dl->dl_addr_length = hw_addr_length;
*addr_offp = offset;
break;
}
case DL_DISABMULTI_REQ: {
dl_disabmulti_req_t *dl = (dl_disabmulti_req_t *)cp;
dl->dl_primitive = dl_primitive;
dl->dl_addr_offset = offset;
*addr_lenp = dl->dl_addr_length = hw_addr_length;
*addr_offp = offset;
break;
}
case DL_PROMISCON_REQ:
case DL_PROMISCOFF_REQ: {
dl_promiscon_req_t *dl = (dl_promiscon_req_t *)cp;
dl->dl_primitive = dl_primitive;
dl->dl_level = DL_PROMISC_MULTI;
break;
}
}
ip1dbg(("ill_create_dl: addr_len %d, addr_off %d\n",
*addr_lenp, *addr_offp));
return (mp);
}
void
ill_recover_multicast(ill_t *ill)
{
ilm_t *ilm;
char addrbuf[INET6_ADDRSTRLEN];
ill->ill_need_recover_multicast = 0;
rw_enter(&ill->ill_mcast_lock, RW_WRITER);
for (ilm = ill->ill_ilm; ilm; ilm = ilm->ilm_next) {
if (ilm_numentries(ill, &ilm->ilm_v6addr) > 1 &&
ilm_lookup(ill, &ilm->ilm_v6addr, ALL_ZONES) != ilm) {
continue;
}
ip1dbg(("ill_recover_multicast: %s\n", inet_ntop(AF_INET6,
&ilm->ilm_v6addr, addrbuf, sizeof (addrbuf))));
if (IN6_IS_ADDR_UNSPECIFIED(&ilm->ilm_v6addr)) {
(void) ill_join_allmulti(ill);
} else {
if (ill->ill_isv6)
mld_joingroup(ilm);
else
igmp_joingroup(ilm);
(void) ip_ll_multireq(ill, &ilm->ilm_v6addr,
DL_ENABMULTI_REQ);
}
}
rw_exit(&ill->ill_mcast_lock);
ill_mcast_send_queued(ill);
ill_dlpi_send_queued(ill);
ill_mcast_timer_start(ill->ill_ipst);
}
void
ill_leave_multicast(ill_t *ill)
{
ilm_t *ilm;
char addrbuf[INET6_ADDRSTRLEN];
ill->ill_need_recover_multicast = 1;
rw_enter(&ill->ill_mcast_lock, RW_WRITER);
for (ilm = ill->ill_ilm; ilm; ilm = ilm->ilm_next) {
if (ilm_numentries(ill, &ilm->ilm_v6addr) > 1 &&
ilm_lookup(ill, &ilm->ilm_v6addr, ALL_ZONES) != ilm) {
continue;
}
ip1dbg(("ill_leave_multicast: %s\n", inet_ntop(AF_INET6,
&ilm->ilm_v6addr, addrbuf, sizeof (addrbuf))));
if (IN6_IS_ADDR_UNSPECIFIED(&ilm->ilm_v6addr)) {
ill_leave_allmulti(ill);
} else {
if (ill->ill_isv6)
mld_leavegroup(ilm);
else
igmp_leavegroup(ilm);
(void) ip_ll_multireq(ill, &ilm->ilm_v6addr,
DL_DISABMULTI_REQ);
}
}
rw_exit(&ill->ill_mcast_lock);
ill_mcast_send_queued(ill);
ill_dlpi_send_queued(ill);
ill_mcast_timer_start(ill->ill_ipst);
}
boolean_t
ill_hasmembers_v6(ill_t *ill, const in6_addr_t *v6group)
{
ilm_t *ilm;
rw_enter(&ill->ill_mcast_lock, RW_READER);
ilm = ilm_lookup(ill, v6group, ALL_ZONES);
rw_exit(&ill->ill_mcast_lock);
return (ilm != NULL);
}
boolean_t
ill_hasmembers_v4(ill_t *ill, ipaddr_t group)
{
in6_addr_t v6group;
IN6_IPADDR_TO_V4MAPPED(group, &v6group);
return (ill_hasmembers_v6(ill, &v6group));
}
boolean_t
ill_hasmembers_otherzones_v6(ill_t *ill, const in6_addr_t *v6group,
zoneid_t skipzone)
{
ilm_t *ilm;
rw_enter(&ill->ill_mcast_lock, RW_READER);
for (ilm = ill->ill_ilm; ilm; ilm = ilm->ilm_next) {
if (IN6_ARE_ADDR_EQUAL(&ilm->ilm_v6addr, v6group) &&
ilm->ilm_zoneid != skipzone) {
rw_exit(&ill->ill_mcast_lock);
return (B_TRUE);
}
}
rw_exit(&ill->ill_mcast_lock);
return (B_FALSE);
}
boolean_t
ill_hasmembers_otherzones_v4(ill_t *ill, ipaddr_t group, zoneid_t skipzone)
{
in6_addr_t v6group;
IN6_IPADDR_TO_V4MAPPED(group, &v6group);
return (ill_hasmembers_otherzones_v6(ill, &v6group, skipzone));
}
zoneid_t
ill_hasmembers_nextzone_v6(ill_t *ill, const in6_addr_t *v6group,
zoneid_t zoneid)
{
ilm_t *ilm;
rw_enter(&ill->ill_mcast_lock, RW_READER);
for (ilm = ill->ill_ilm; ilm; ilm = ilm->ilm_next) {
if (IN6_ARE_ADDR_EQUAL(&ilm->ilm_v6addr, v6group) &&
ilm->ilm_zoneid > zoneid) {
zoneid = ilm->ilm_zoneid;
rw_exit(&ill->ill_mcast_lock);
return (zoneid);
}
}
rw_exit(&ill->ill_mcast_lock);
return (ALL_ZONES);
}
zoneid_t
ill_hasmembers_nextzone_v4(ill_t *ill, ipaddr_t group, zoneid_t zoneid)
{
in6_addr_t v6group;
IN6_IPADDR_TO_V4MAPPED(group, &v6group);
return (ill_hasmembers_nextzone_v6(ill, &v6group, zoneid));
}
static ilm_t *
ilm_lookup(ill_t *ill, const in6_addr_t *v6group, zoneid_t zoneid)
{
ilm_t *ilm;
ASSERT(RW_LOCK_HELD(&ill->ill_mcast_lock));
for (ilm = ill->ill_ilm; ilm; ilm = ilm->ilm_next) {
if (!IN6_ARE_ADDR_EQUAL(&ilm->ilm_v6addr, v6group))
continue;
if (zoneid != ALL_ZONES && zoneid != ilm->ilm_zoneid)
continue;
ASSERT(ilm->ilm_ill == ill);
return (ilm);
}
return (NULL);
}
static int
ilm_numentries(ill_t *ill, const in6_addr_t *v6group)
{
ilm_t *ilm;
int i = 0;
ASSERT(RW_LOCK_HELD(&ill->ill_mcast_lock));
for (ilm = ill->ill_ilm; ilm; ilm = ilm->ilm_next) {
if (IN6_ARE_ADDR_EQUAL(&ilm->ilm_v6addr, v6group)) {
i++;
}
}
return (i);
}
static ilm_t *
ilm_add(ill_t *ill, const in6_addr_t *v6group, ilg_stat_t ilgstat,
mcast_record_t ilg_fmode, slist_t *ilg_flist, zoneid_t zoneid)
{
ilm_t *ilm;
ilm_t *ilm_cur;
ilm_t **ilm_ptpn;
ASSERT(RW_WRITE_HELD(&ill->ill_mcast_lock));
ilm = GETSTRUCT(ilm_t, 1);
if (ilm == NULL)
return (NULL);
if (ilgstat != ILGSTAT_NONE && !SLIST_IS_EMPTY(ilg_flist)) {
ilm->ilm_filter = l_alloc();
if (ilm->ilm_filter == NULL) {
mi_free(ilm);
return (NULL);
}
}
ilm->ilm_v6addr = *v6group;
ilm->ilm_refcnt = 1;
ilm->ilm_zoneid = zoneid;
ilm->ilm_timer = INFINITY;
ilm->ilm_rtx.rtx_timer = INFINITY;
ilm->ilm_ill = ill;
DTRACE_PROBE3(ill__incr__cnt, (ill_t *), ill,
(char *), "ilm", (void *), ilm);
ill->ill_ilm_cnt++;
ASSERT(ill->ill_ipst);
ilm->ilm_ipst = ill->ill_ipst;
ilm_cur = ill->ill_ilm;
ilm_ptpn = &ill->ill_ilm;
while (ilm_cur != NULL && ilm_cur->ilm_zoneid < ilm->ilm_zoneid) {
ilm_ptpn = &ilm_cur->ilm_next;
ilm_cur = ilm_cur->ilm_next;
}
ilm->ilm_next = ilm_cur;
*ilm_ptpn = ilm;
if (ilgstat != ILGSTAT_NONE) {
if (!SLIST_IS_EMPTY(ilg_flist))
l_copy(ilg_flist, ilm->ilm_filter);
ilm->ilm_fmode = ilg_fmode;
} else {
ilm->ilm_no_ilg_cnt = 1;
ilm->ilm_fmode = MODE_IS_EXCLUDE;
}
return (ilm);
}
void
ilm_inactive(ilm_t *ilm)
{
FREE_SLIST(ilm->ilm_filter);
FREE_SLIST(ilm->ilm_pendsrcs);
FREE_SLIST(ilm->ilm_rtx.rtx_allow);
FREE_SLIST(ilm->ilm_rtx.rtx_block);
ilm->ilm_ipst = NULL;
mi_free((char *)ilm);
}
static void
ilm_delete(ilm_t *ilm)
{
ill_t *ill = ilm->ilm_ill;
ilm_t **ilmp;
boolean_t need_wakeup;
ASSERT(RW_WRITE_HELD(&ill->ill_mcast_lock));
for (ilmp = &ill->ill_ilm; *ilmp != ilm; ilmp = &(*ilmp)->ilm_next)
;
*ilmp = ilm->ilm_next;
mutex_enter(&ill->ill_lock);
need_wakeup = B_FALSE;
DTRACE_PROBE3(ill__decr__cnt, (ill_t *), ill,
(char *), "ilm", (void *), ilm);
ASSERT(ill->ill_ilm_cnt > 0);
ill->ill_ilm_cnt--;
if (ILL_FREE_OK(ill))
need_wakeup = B_TRUE;
ilm_inactive(ilm);
if (need_wakeup) {
ipif_ill_refrele_tail(ill);
} else {
mutex_exit(&ill->ill_lock);
}
}
static ill_t *
ill_mcast_lookup(const in6_addr_t *group, ipaddr_t ifaddr, uint_t ifindex,
zoneid_t zoneid, ip_stack_t *ipst, int *errorp)
{
ill_t *ill;
ipaddr_t v4group;
if (IN6_IS_ADDR_V4MAPPED(group)) {
IN6_V4MAPPED_TO_IPADDR(group, v4group);
if (ifindex != 0) {
ill = ill_lookup_on_ifindex_zoneid(ifindex, zoneid,
B_FALSE, ipst);
} else if (ifaddr != INADDR_ANY) {
ipif_t *ipif;
ipif = ipif_lookup_addr(ifaddr, NULL, zoneid, ipst);
if (ipif == NULL) {
ill = NULL;
} else {
ill = ipif->ipif_ill;
ill_refhold(ill);
ipif_refrele(ipif);
}
} else {
ill = ill_lookup_group_v4(v4group, zoneid, ipst, NULL,
NULL);
}
} else {
if (ifindex != 0) {
ill = ill_lookup_on_ifindex_zoneid(ifindex, zoneid,
B_TRUE, ipst);
} else {
ill = ill_lookup_group_v6(group, zoneid, ipst, NULL,
NULL);
}
}
if (ill == NULL) {
if (ifindex != 0)
*errorp = ENXIO;
else
*errorp = EADDRNOTAVAIL;
return (NULL);
}
if (IS_UNDER_IPMP(ill) || IS_VNI(ill)) {
ill_refrele(ill);
*errorp = EINVAL;
return (NULL);
}
return (ill);
}
int
ip_opt_check(conn_t *connp, const in6_addr_t *v6group,
const in6_addr_t *v6src, ipaddr_t ifaddr, uint_t ifindex, ill_t **illpp)
{
boolean_t src_unspec;
ill_t *ill = NULL;
ip_stack_t *ipst = connp->conn_netstack->netstack_ip;
int error = 0;
*illpp = NULL;
src_unspec = IN6_IS_ADDR_UNSPECIFIED(v6src);
if (IN6_IS_ADDR_V4MAPPED(v6group)) {
ipaddr_t v4group;
ipaddr_t v4src;
if (!IN6_IS_ADDR_V4MAPPED(v6src) && !src_unspec)
return (EINVAL);
IN6_V4MAPPED_TO_IPADDR(v6group, v4group);
if (src_unspec) {
v4src = INADDR_ANY;
} else {
IN6_V4MAPPED_TO_IPADDR(v6src, v4src);
}
if (!CLASSD(v4group) || CLASSD(v4src))
return (EINVAL);
} else {
if (IN6_IS_ADDR_V4MAPPED(v6src) && !src_unspec)
return (EINVAL);
if (!IN6_IS_ADDR_MULTICAST(v6group) ||
IN6_IS_ADDR_MULTICAST(v6src)) {
return (EINVAL);
}
}
ill = ill_mcast_lookup(v6group, ifaddr, ifindex, IPCL_ZONEID(connp),
ipst, &error);
*illpp = ill;
return (error);
}
static int
ip_get_srcfilter(conn_t *connp, struct group_filter *gf,
struct ip_msfilter *imsf, const struct in6_addr *group, boolean_t issin6)
{
ilg_t *ilg;
int i, numsrc, fmode, outsrcs;
struct sockaddr_in *sin;
struct sockaddr_in6 *sin6;
struct in_addr *addrp;
slist_t *fp;
boolean_t is_v4only_api;
ipaddr_t ifaddr;
uint_t ifindex;
if (gf == NULL) {
ASSERT(imsf != NULL);
ASSERT(!issin6);
is_v4only_api = B_TRUE;
outsrcs = imsf->imsf_numsrc;
ifaddr = imsf->imsf_interface.s_addr;
ifindex = 0;
} else {
ASSERT(imsf == NULL);
is_v4only_api = B_FALSE;
outsrcs = gf->gf_numsrc;
ifaddr = INADDR_ANY;
ifindex = gf->gf_interface;
}
rw_enter(&connp->conn_ilg_lock, RW_READER);
ilg = ilg_lookup(connp, group, ifaddr, ifindex);
if (ilg == NULL) {
rw_exit(&connp->conn_ilg_lock);
return (EADDRNOTAVAIL);
}
fmode = (ilg->ilg_fmode == MODE_IS_INCLUDE) ?
MCAST_INCLUDE : MCAST_EXCLUDE;
if ((fp = ilg->ilg_filter) == NULL) {
numsrc = 0;
} else {
for (i = 0; i < outsrcs; i++) {
if (i == fp->sl_numsrc)
break;
if (issin6) {
sin6 = (struct sockaddr_in6 *)&gf->gf_slist[i];
sin6->sin6_family = AF_INET6;
sin6->sin6_addr = fp->sl_addr[i];
} else {
if (is_v4only_api) {
addrp = &imsf->imsf_slist[i];
} else {
sin = (struct sockaddr_in *)
&gf->gf_slist[i];
sin->sin_family = AF_INET;
addrp = &sin->sin_addr;
}
IN6_V4MAPPED_TO_INADDR(&fp->sl_addr[i], addrp);
}
}
numsrc = fp->sl_numsrc;
}
if (is_v4only_api) {
imsf->imsf_numsrc = numsrc;
imsf->imsf_fmode = fmode;
} else {
gf->gf_numsrc = numsrc;
gf->gf_fmode = fmode;
}
rw_exit(&connp->conn_ilg_lock);
return (0);
}
static int
ip_set_srcfilter(conn_t *connp, struct group_filter *gf,
struct ip_msfilter *imsf, const struct in6_addr *group, ill_t *ill,
boolean_t issin6)
{
ilg_t *ilg;
int i, err, infmode, new_fmode;
uint_t insrcs;
struct sockaddr_in *sin;
struct sockaddr_in6 *sin6;
struct in_addr *addrp;
slist_t *orig_filter = NULL;
slist_t *new_filter = NULL;
mcast_record_t orig_fmode;
boolean_t leave_group, is_v4only_api;
ilg_stat_t ilgstat;
ilm_t *ilm;
ipaddr_t ifaddr;
uint_t ifindex;
if (gf == NULL) {
ASSERT(imsf != NULL);
ASSERT(!issin6);
is_v4only_api = B_TRUE;
insrcs = imsf->imsf_numsrc;
infmode = imsf->imsf_fmode;
ifaddr = imsf->imsf_interface.s_addr;
ifindex = 0;
} else {
ASSERT(imsf == NULL);
is_v4only_api = B_FALSE;
insrcs = gf->gf_numsrc;
infmode = gf->gf_fmode;
ifaddr = INADDR_ANY;
ifindex = gf->gf_interface;
}
if (insrcs > MAX_FILTER_SIZE)
return (ENOBUFS);
leave_group = (infmode == MCAST_INCLUDE && insrcs == 0);
mutex_enter(&ill->ill_mcast_serializer);
rw_enter(&connp->conn_ilg_lock, RW_WRITER);
ilg = ilg_lookup(connp, group, ifaddr, ifindex);
if (ilg == NULL) {
if (leave_group) {
rw_exit(&connp->conn_ilg_lock);
mutex_exit(&ill->ill_mcast_serializer);
return (0);
}
ilg = conn_ilg_alloc(connp, &err);
if (ilg == NULL) {
rw_exit(&connp->conn_ilg_lock);
mutex_exit(&ill->ill_mcast_serializer);
return (err);
}
ilgstat = ILGSTAT_NEW;
ilg->ilg_v6group = *group;
ilg->ilg_ill = ill;
ilg->ilg_ifaddr = ifaddr;
ilg->ilg_ifindex = ifindex;
} else if (leave_group) {
ilg_refhold(ilg);
mutex_exit(&ill->ill_mcast_serializer);
ill = ilg->ilg_ill;
rw_exit(&connp->conn_ilg_lock);
mutex_enter(&ill->ill_mcast_serializer);
rw_enter(&connp->conn_ilg_lock, RW_WRITER);
ilm = ilg->ilg_ilm;
ilg->ilg_ilm = NULL;
ilg_delete(connp, ilg, NULL);
ilg_refrele(ilg);
rw_exit(&connp->conn_ilg_lock);
if (ilm != NULL)
(void) ip_delmulti_serial(ilm, B_FALSE, B_TRUE);
mutex_exit(&ill->ill_mcast_serializer);
ill_mcast_send_queued(ill);
ill_dlpi_send_queued(ill);
return (0);
} else {
ilgstat = ILGSTAT_CHANGE;
orig_fmode = ilg->ilg_fmode;
if (ilg->ilg_filter == NULL) {
orig_filter = NULL;
} else {
orig_filter = l_alloc_copy(ilg->ilg_filter);
if (orig_filter == NULL) {
rw_exit(&connp->conn_ilg_lock);
mutex_exit(&ill->ill_mcast_serializer);
return (ENOMEM);
}
}
}
if ((new_filter = l_alloc()) == NULL) {
rw_exit(&connp->conn_ilg_lock);
err = ENOMEM;
goto free_and_exit;
}
if (insrcs == 0) {
CLEAR_SLIST(ilg->ilg_filter);
} else {
slist_t *fp;
if (ilg->ilg_filter == NULL) {
fp = l_alloc();
if (fp == NULL) {
if (ilgstat == ILGSTAT_NEW)
ilg_delete(connp, ilg, NULL);
rw_exit(&connp->conn_ilg_lock);
err = ENOMEM;
goto free_and_exit;
}
} else {
fp = ilg->ilg_filter;
}
for (i = 0; i < insrcs; i++) {
if (issin6) {
sin6 = (struct sockaddr_in6 *)&gf->gf_slist[i];
fp->sl_addr[i] = sin6->sin6_addr;
} else {
if (is_v4only_api) {
addrp = &imsf->imsf_slist[i];
} else {
sin = (struct sockaddr_in *)
&gf->gf_slist[i];
addrp = &sin->sin_addr;
}
IN6_INADDR_TO_V4MAPPED(addrp, &fp->sl_addr[i]);
}
}
fp->sl_numsrc = insrcs;
ilg->ilg_filter = fp;
}
ilg->ilg_fmode = (infmode == MCAST_INCLUDE) ?
MODE_IS_INCLUDE : MODE_IS_EXCLUDE;
new_fmode = ilg->ilg_fmode;
l_copy(ilg->ilg_filter, new_filter);
rw_exit(&connp->conn_ilg_lock);
ilm = ip_addmulti_serial(group, ill, connp->conn_zoneid, ilgstat,
new_fmode, new_filter, &err);
rw_enter(&connp->conn_ilg_lock, RW_WRITER);
ilg = ilg_lookup(connp, group, ifaddr, ifindex);
if (ilg == NULL) {
rw_exit(&connp->conn_ilg_lock);
if (ilm != NULL) {
(void) ip_delmulti_serial(ilm, B_FALSE,
(ilgstat == ILGSTAT_NEW));
}
err = ENXIO;
goto free_and_exit;
}
if (ilm != NULL) {
if (ilg->ilg_ill == NULL) {
rw_exit(&connp->conn_ilg_lock);
(void) ip_delmulti_serial(ilm, B_FALSE,
(ilgstat == ILGSTAT_NEW));
err = 0;
goto free_and_exit;
}
if (ilgstat == ILGSTAT_NEW) {
if (ilg->ilg_ilm == NULL) {
ilg->ilg_ilm = ilm;
ilm->ilm_ifaddr = ifaddr;
} else {
rw_exit(&connp->conn_ilg_lock);
(void) ip_delmulti_serial(ilm, B_FALSE, B_TRUE);
err = 0;
goto free_and_exit;
}
} else {
ASSERT(ilg->ilg_ilm == ilm);
}
} else {
ASSERT(err != 0);
if (ilgstat == ILGSTAT_NEW) {
if (err == ENETDOWN) {
ilg->ilg_ill = NULL;
err = 0;
} else {
ilg_delete(connp, ilg, NULL);
}
} else {
ilg->ilg_fmode = orig_fmode;
if (SLIST_IS_EMPTY(orig_filter)) {
CLEAR_SLIST(ilg->ilg_filter);
} else {
l_copy(orig_filter, ilg->ilg_filter);
}
}
}
rw_exit(&connp->conn_ilg_lock);
free_and_exit:
mutex_exit(&ill->ill_mcast_serializer);
ill_mcast_send_queued(ill);
ill_dlpi_send_queued(ill);
l_free(orig_filter);
l_free(new_filter);
return (err);
}
int
ip_sioctl_msfilter(ipif_t *ipif, sin_t *dummy_sin, queue_t *q, mblk_t *mp,
ip_ioctl_cmd_t *ipip, void *ifreq)
{
struct iocblk *iocp = (struct iocblk *)mp->b_rptr;
mblk_t *data_mp = mp->b_cont->b_cont;
int datalen, err, cmd, minsize;
uint_t expsize = 0;
conn_t *connp;
boolean_t isv6, is_v4only_api, getcmd;
struct sockaddr_in *gsin;
struct sockaddr_in6 *gsin6;
ipaddr_t v4group;
in6_addr_t v6group;
struct group_filter *gf = NULL;
struct ip_msfilter *imsf = NULL;
mblk_t *ndp;
ill_t *ill;
connp = Q_TO_CONN(q);
err = ip_msfilter_ill(connp, mp, ipip, &ill);
if (err != 0)
return (err);
if (data_mp->b_cont != NULL) {
if ((ndp = msgpullup(data_mp, -1)) == NULL)
return (ENOMEM);
freemsg(data_mp);
data_mp = ndp;
mp->b_cont->b_cont = data_mp;
}
cmd = iocp->ioc_cmd;
getcmd = (cmd == SIOCGIPMSFILTER || cmd == SIOCGMSFILTER);
is_v4only_api = (cmd == SIOCGIPMSFILTER || cmd == SIOCSIPMSFILTER);
minsize = (is_v4only_api) ? IP_MSFILTER_SIZE(0) : GROUP_FILTER_SIZE(0);
datalen = MBLKL(data_mp);
if (datalen < minsize)
return (EINVAL);
if (is_v4only_api) {
imsf = (struct ip_msfilter *)data_mp->b_rptr;
isv6 = B_FALSE;
expsize = IP_MSFILTER_SIZE(imsf->imsf_numsrc);
} else {
gf = (struct group_filter *)data_mp->b_rptr;
if (gf->gf_group.ss_family == AF_INET6) {
gsin6 = (struct sockaddr_in6 *)&gf->gf_group;
isv6 = !(IN6_IS_ADDR_V4MAPPED(&gsin6->sin6_addr));
} else {
isv6 = B_FALSE;
}
expsize = GROUP_FILTER_SIZE(gf->gf_numsrc);
}
if (datalen < expsize)
return (EINVAL);
if (isv6) {
gsin6 = (struct sockaddr_in6 *)&gf->gf_group;
v6group = gsin6->sin6_addr;
if (getcmd) {
err = ip_get_srcfilter(connp, gf, NULL, &v6group,
B_TRUE);
} else {
err = ip_set_srcfilter(connp, gf, NULL, &v6group, ill,
B_TRUE);
}
} else {
boolean_t issin6 = B_FALSE;
if (is_v4only_api) {
v4group = (ipaddr_t)imsf->imsf_multiaddr.s_addr;
IN6_IPADDR_TO_V4MAPPED(v4group, &v6group);
} else {
if (gf->gf_group.ss_family == AF_INET) {
gsin = (struct sockaddr_in *)&gf->gf_group;
v4group = (ipaddr_t)gsin->sin_addr.s_addr;
IN6_IPADDR_TO_V4MAPPED(v4group, &v6group);
} else {
gsin6 = (struct sockaddr_in6 *)&gf->gf_group;
IN6_V4MAPPED_TO_IPADDR(&gsin6->sin6_addr,
v4group);
issin6 = B_TRUE;
}
}
if (v4group == INADDR_ANY)
v6group = ipv6_all_zeros;
else
IN6_IPADDR_TO_V4MAPPED(v4group, &v6group);
if (getcmd) {
err = ip_get_srcfilter(connp, gf, imsf, &v6group,
issin6);
} else {
err = ip_set_srcfilter(connp, gf, imsf, &v6group, ill,
issin6);
}
}
ill_refrele(ill);
return (err);
}
static int
ip_msfilter_ill(conn_t *connp, mblk_t *mp, const ip_ioctl_cmd_t *ipip,
ill_t **illp)
{
int cmd = ipip->ipi_cmd;
int err = 0;
ill_t *ill;
char *dbuf = (char *)mp->b_cont->b_cont->b_rptr;
struct ip_msfilter *imsf;
struct group_filter *gf;
ipaddr_t v4addr, v4group;
in6_addr_t v6group;
uint32_t index;
ip_stack_t *ipst;
ipst = connp->conn_netstack->netstack_ip;
*illp = NULL;
if (IPCL_IS_TCP(connp))
return (ENOPROTOOPT);
if (cmd == SIOCSIPMSFILTER || cmd == SIOCGIPMSFILTER) {
if (connp->conn_family == AF_INET6)
return (EAFNOSUPPORT);
imsf = (struct ip_msfilter *)dbuf;
v4addr = imsf->imsf_interface.s_addr;
v4group = imsf->imsf_multiaddr.s_addr;
IN6_IPADDR_TO_V4MAPPED(v4group, &v6group);
ill = ill_mcast_lookup(&v6group, v4addr, 0, IPCL_ZONEID(connp),
ipst, &err);
if (ill == NULL && v4addr != INADDR_ANY)
err = ENXIO;
} else {
gf = (struct group_filter *)dbuf;
index = gf->gf_interface;
if (gf->gf_group.ss_family == AF_INET6) {
struct sockaddr_in6 *sin6;
sin6 = (struct sockaddr_in6 *)&gf->gf_group;
v6group = sin6->sin6_addr;
} else if (gf->gf_group.ss_family == AF_INET) {
struct sockaddr_in *sin;
sin = (struct sockaddr_in *)&gf->gf_group;
v4group = sin->sin_addr.s_addr;
IN6_IPADDR_TO_V4MAPPED(v4group, &v6group);
} else {
return (EAFNOSUPPORT);
}
ill = ill_mcast_lookup(&v6group, INADDR_ANY, index,
IPCL_ZONEID(connp), ipst, &err);
}
*illp = ill;
return (err);
}
int
ip_copyin_msfilter(queue_t *q, mblk_t *mp)
{
struct iocblk *iocp = (struct iocblk *)mp->b_rptr;
int cmd = iocp->ioc_cmd;
mblk_t *mp1 = mp->b_cont->b_cont;
int copysize = 0;
int offset;
if (cmd == SIOCSMSFILTER || cmd == SIOCGMSFILTER) {
struct group_filter *gf = (struct group_filter *)mp1->b_rptr;
if (gf->gf_numsrc >= 2) {
offset = sizeof (struct group_filter);
copysize = GROUP_FILTER_SIZE(gf->gf_numsrc) - offset;
}
} else {
struct ip_msfilter *imsf = (struct ip_msfilter *)mp1->b_rptr;
if (imsf->imsf_numsrc >= 2) {
offset = sizeof (struct ip_msfilter);
copysize = IP_MSFILTER_SIZE(imsf->imsf_numsrc) - offset;
}
}
if (copysize > 0) {
mi_copyin_n(q, mp, offset, copysize);
return (0);
}
return (1);
}
int
ip_opt_add_group(conn_t *connp, boolean_t checkonly,
const in6_addr_t *v6group, ipaddr_t ifaddr, uint_t ifindex,
mcast_record_t fmode, const in6_addr_t *v6src)
{
ill_t *ill;
char buf[INET6_ADDRSTRLEN];
int err;
err = ip_opt_check(connp, v6group, v6src, ifaddr, ifindex, &ill);
if (err != 0) {
ip1dbg(("ip_opt_add_group: no ill for group %s/"
"index %d\n", inet_ntop(AF_INET6, v6group, buf,
sizeof (buf)), ifindex));
return (err);
}
if (checkonly) {
ill_refrele(ill);
return (0);
}
mutex_enter(&ill->ill_mcast_serializer);
if (ill->ill_grp_pending || IS_UNDER_IPMP(ill)) {
DTRACE_PROBE2(group__add__on__under, ill_t *, ill,
in6_addr_t *, v6group);
mutex_exit(&ill->ill_mcast_serializer);
ill_refrele(ill);
return (EADDRNOTAVAIL);
}
err = ilg_add(connp, v6group, ifaddr, ifindex, ill, fmode, v6src);
mutex_exit(&ill->ill_mcast_serializer);
ill_mcast_send_queued(ill);
ill_dlpi_send_queued(ill);
ill_refrele(ill);
return (err);
}
static int
ip_opt_delete_group_excl(conn_t *connp, const in6_addr_t *v6group,
ipaddr_t ifaddr, uint_t ifindex, mcast_record_t fmode,
const in6_addr_t *v6src)
{
ilg_t *ilg;
boolean_t leaving;
ilm_t *ilm;
ill_t *ill;
int err = 0;
retry:
rw_enter(&connp->conn_ilg_lock, RW_WRITER);
ilg = ilg_lookup(connp, v6group, ifaddr, ifindex);
if (ilg == NULL) {
rw_exit(&connp->conn_ilg_lock);
err = ip_opt_check(connp, v6group, v6src, ifaddr, ifindex,
&ill);
if (ill != NULL) {
ill_refrele(ill);
err = EADDRNOTAVAIL;
}
return (err);
}
ill = ilg->ilg_ill;
if (ill != NULL) {
ill_refhold(ill);
ilg_refhold(ilg);
rw_exit(&connp->conn_ilg_lock);
mutex_enter(&ill->ill_mcast_serializer);
rw_enter(&connp->conn_ilg_lock, RW_WRITER);
if (ilg->ilg_condemned) {
ilg_refrele(ilg);
rw_exit(&connp->conn_ilg_lock);
mutex_exit(&ill->ill_mcast_serializer);
ill_refrele(ill);
goto retry;
}
}
if (IN6_IS_ADDR_UNSPECIFIED(v6src)) {
leaving = B_TRUE;
} else {
if (fmode != ilg->ilg_fmode)
err = EINVAL;
else if (ilg->ilg_filter == NULL ||
!list_has_addr(ilg->ilg_filter, v6src))
err = EADDRNOTAVAIL;
if (err != 0) {
if (ill != NULL)
ilg_refrele(ilg);
rw_exit(&connp->conn_ilg_lock);
goto done;
}
if (fmode == MODE_IS_INCLUDE &&
ilg->ilg_filter->sl_numsrc == 1) {
leaving = B_TRUE;
v6src = NULL;
} else {
leaving = B_FALSE;
}
}
ilm = ilg->ilg_ilm;
if (leaving)
ilg->ilg_ilm = NULL;
ilg_delete(connp, ilg, v6src);
if (ill != NULL)
ilg_refrele(ilg);
rw_exit(&connp->conn_ilg_lock);
if (ilm != NULL) {
ASSERT(ill != NULL);
(void) ip_delmulti_serial(ilm, B_FALSE, leaving);
}
done:
if (ill != NULL) {
mutex_exit(&ill->ill_mcast_serializer);
ill_mcast_send_queued(ill);
ill_dlpi_send_queued(ill);
ill_refrele(ill);
}
return (err);
}
int
ip_opt_delete_group(conn_t *connp, boolean_t checkonly,
const in6_addr_t *v6group, ipaddr_t ifaddr, uint_t ifindex,
mcast_record_t fmode, const in6_addr_t *v6src)
{
if (checkonly) {
ill_t *ill;
int err;
err = ip_opt_check(connp, v6group, v6src, ifaddr, ifindex,
&ill);
if (ill != NULL)
ill_refrele(ill);
return (err);
}
return (ip_opt_delete_group_excl(connp, v6group, ifaddr, ifindex,
fmode, v6src));
}
static int
ilg_add(conn_t *connp, const in6_addr_t *v6group, ipaddr_t ifaddr,
uint_t ifindex, ill_t *ill, mcast_record_t fmode, const in6_addr_t *v6src)
{
int error = 0;
ilg_t *ilg;
ilg_stat_t ilgstat;
slist_t *new_filter = NULL;
int new_fmode;
ilm_t *ilm;
if (!(ill->ill_flags & ILLF_MULTICAST))
return (EADDRNOTAVAIL);
ASSERT(MUTEX_HELD(&ill->ill_mcast_serializer));
rw_enter(&connp->conn_ilg_lock, RW_WRITER);
ilg = ilg_lookup(connp, v6group, ifaddr, ifindex);
if (IN6_IS_ADDR_UNSPECIFIED(v6src)) {
if (ilg != NULL)
error = EADDRINUSE;
} else {
if (fmode == MODE_IS_EXCLUDE) {
if (ilg == NULL)
error = EADDRNOTAVAIL;
}
if (ilg != NULL &&
SLIST_CNT(ilg->ilg_filter) == MAX_FILTER_SIZE)
error = ENOBUFS;
}
if (error != 0) {
rw_exit(&connp->conn_ilg_lock);
return (error);
}
if ((new_filter = l_alloc()) == NULL) {
rw_exit(&connp->conn_ilg_lock);
return (ENOMEM);
}
if (ilg == NULL) {
if ((ilg = conn_ilg_alloc(connp, &error)) == NULL) {
rw_exit(&connp->conn_ilg_lock);
l_free(new_filter);
return (error);
}
ilg->ilg_ifindex = ifindex;
ilg->ilg_ifaddr = ifaddr;
if (!IN6_IS_ADDR_UNSPECIFIED(v6src)) {
ilg->ilg_filter = l_alloc();
if (ilg->ilg_filter == NULL) {
ilg_delete(connp, ilg, NULL);
rw_exit(&connp->conn_ilg_lock);
l_free(new_filter);
return (ENOMEM);
}
ilg->ilg_filter->sl_numsrc = 1;
ilg->ilg_filter->sl_addr[0] = *v6src;
}
ilgstat = ILGSTAT_NEW;
ilg->ilg_v6group = *v6group;
ilg->ilg_fmode = fmode;
ilg->ilg_ill = ill;
} else {
int index;
if (ilg->ilg_fmode != fmode || IN6_IS_ADDR_UNSPECIFIED(v6src)) {
rw_exit(&connp->conn_ilg_lock);
l_free(new_filter);
return (EINVAL);
}
if (ilg->ilg_filter == NULL) {
ilg->ilg_filter = l_alloc();
if (ilg->ilg_filter == NULL) {
rw_exit(&connp->conn_ilg_lock);
l_free(new_filter);
return (ENOMEM);
}
}
if (list_has_addr(ilg->ilg_filter, v6src)) {
rw_exit(&connp->conn_ilg_lock);
l_free(new_filter);
return (EADDRNOTAVAIL);
}
ilgstat = ILGSTAT_CHANGE;
index = ilg->ilg_filter->sl_numsrc++;
ilg->ilg_filter->sl_addr[index] = *v6src;
}
new_fmode = ilg->ilg_fmode;
l_copy(ilg->ilg_filter, new_filter);
rw_exit(&connp->conn_ilg_lock);
ilm = ip_addmulti_serial(v6group, ill, connp->conn_zoneid, ilgstat,
new_fmode, new_filter, &error);
rw_enter(&connp->conn_ilg_lock, RW_WRITER);
ilg = ilg_lookup(connp, v6group, ifaddr, ifindex);
if (ilg == NULL) {
rw_exit(&connp->conn_ilg_lock);
if (ilm != NULL) {
(void) ip_delmulti_serial(ilm, B_FALSE,
(ilgstat == ILGSTAT_NEW));
}
error = ENXIO;
goto free_and_exit;
}
if (ilm != NULL) {
if (ilg->ilg_ill == NULL) {
rw_exit(&connp->conn_ilg_lock);
(void) ip_delmulti_serial(ilm, B_FALSE,
(ilgstat == ILGSTAT_NEW));
error = 0;
goto free_and_exit;
}
if (ilgstat == ILGSTAT_NEW) {
if (ilg->ilg_ilm == NULL) {
ilg->ilg_ilm = ilm;
ilm->ilm_ifaddr = ifaddr;
} else {
rw_exit(&connp->conn_ilg_lock);
(void) ip_delmulti_serial(ilm, B_FALSE, B_TRUE);
error = 0;
goto free_and_exit;
}
} else {
ASSERT(ilg->ilg_ilm == ilm);
}
} else {
ASSERT(error != 0);
if (ilgstat == ILGSTAT_NEW && error == ENETDOWN) {
ilg->ilg_ill = NULL;
error = 0;
} else {
in6_addr_t delsrc =
(ilgstat == ILGSTAT_NEW) ? ipv6_all_zeros : *v6src;
ilg_delete(connp, ilg, &delsrc);
}
}
rw_exit(&connp->conn_ilg_lock);
free_and_exit:
l_free(new_filter);
return (error);
}
boolean_t
conn_hasmembers_ill_withsrc_v4(conn_t *connp, ipaddr_t group, ipaddr_t src,
ill_t *ill)
{
in6_addr_t v6group, v6src;
int i;
boolean_t isinlist;
ilg_t *ilg;
rw_enter(&connp->conn_ilg_lock, RW_READER);
IN6_IPADDR_TO_V4MAPPED(group, &v6group);
for (ilg = connp->conn_ilg; ilg != NULL; ilg = ilg->ilg_next) {
if (ilg->ilg_condemned)
continue;
if (ilg->ilg_ill != ill)
continue;
ASSERT(!IS_UNDER_IPMP(ill));
if (IN6_ARE_ADDR_EQUAL(&ilg->ilg_v6group, &v6group)) {
if (SLIST_IS_EMPTY(ilg->ilg_filter)) {
rw_exit(&connp->conn_ilg_lock);
return (B_TRUE);
}
break;
}
}
if (ilg == NULL) {
rw_exit(&connp->conn_ilg_lock);
return (B_FALSE);
}
IN6_IPADDR_TO_V4MAPPED(src, &v6src);
isinlist = B_FALSE;
for (i = 0; i < ilg->ilg_filter->sl_numsrc; i++) {
if (IN6_ARE_ADDR_EQUAL(&v6src, &ilg->ilg_filter->sl_addr[i])) {
isinlist = B_TRUE;
break;
}
}
if ((isinlist && ilg->ilg_fmode == MODE_IS_INCLUDE) ||
(!isinlist && ilg->ilg_fmode == MODE_IS_EXCLUDE)) {
rw_exit(&connp->conn_ilg_lock);
return (B_TRUE);
}
rw_exit(&connp->conn_ilg_lock);
return (B_FALSE);
}
boolean_t
conn_hasmembers_ill_withsrc_v6(conn_t *connp, const in6_addr_t *v6group,
const in6_addr_t *v6src, ill_t *ill)
{
int i;
boolean_t isinlist;
ilg_t *ilg;
rw_enter(&connp->conn_ilg_lock, RW_READER);
for (ilg = connp->conn_ilg; ilg != NULL; ilg = ilg->ilg_next) {
if (ilg->ilg_condemned)
continue;
if (ilg->ilg_ill != ill)
continue;
ASSERT(!IS_UNDER_IPMP(ill));
if (IN6_ARE_ADDR_EQUAL(&ilg->ilg_v6group, v6group)) {
if (SLIST_IS_EMPTY(ilg->ilg_filter)) {
rw_exit(&connp->conn_ilg_lock);
return (B_TRUE);
}
break;
}
}
if (ilg == NULL) {
rw_exit(&connp->conn_ilg_lock);
return (B_FALSE);
}
isinlist = B_FALSE;
for (i = 0; i < ilg->ilg_filter->sl_numsrc; i++) {
if (IN6_ARE_ADDR_EQUAL(v6src, &ilg->ilg_filter->sl_addr[i])) {
isinlist = B_TRUE;
break;
}
}
if ((isinlist && ilg->ilg_fmode == MODE_IS_INCLUDE) ||
(!isinlist && ilg->ilg_fmode == MODE_IS_EXCLUDE)) {
rw_exit(&connp->conn_ilg_lock);
return (B_TRUE);
}
rw_exit(&connp->conn_ilg_lock);
return (B_FALSE);
}
static ilg_t *
ilg_lookup(conn_t *connp, const in6_addr_t *v6group, ipaddr_t ifaddr,
uint_t ifindex)
{
ilg_t *ilg;
ASSERT(RW_LOCK_HELD(&connp->conn_ilg_lock));
for (ilg = connp->conn_ilg; ilg != NULL; ilg = ilg->ilg_next) {
if (ilg->ilg_condemned)
continue;
if (ilg->ilg_ifaddr == ifaddr &&
ilg->ilg_ifindex == ifindex &&
IN6_ARE_ADDR_EQUAL(&ilg->ilg_v6group, v6group))
return (ilg);
}
return (NULL);
}
static void
ilg_delete(conn_t *connp, ilg_t *ilg, const in6_addr_t *src)
{
ASSERT(RW_WRITE_HELD(&connp->conn_ilg_lock));
ASSERT(ilg->ilg_ptpn != NULL);
ASSERT(!ilg->ilg_condemned);
if (src == NULL || IN6_IS_ADDR_UNSPECIFIED(src)) {
FREE_SLIST(ilg->ilg_filter);
ilg->ilg_filter = NULL;
ASSERT(ilg->ilg_ilm == NULL);
ilg->ilg_ill = NULL;
ilg->ilg_condemned = B_TRUE;
ilg_refrele(ilg);
} else {
l_remove(ilg->ilg_filter, src);
}
}
void
ilg_delete_all(conn_t *connp)
{
ilg_t *ilg, *next_ilg, *held_ilg;
ilm_t *ilm;
ill_t *ill;
boolean_t need_refrele;
mutex_enter(&connp->conn_lock);
ASSERT(connp->conn_state_flags & CONN_CLOSING);
while (connp->conn_state_flags & CONN_UPDATE_ILL)
cv_wait(&connp->conn_cv, &connp->conn_lock);
mutex_exit(&connp->conn_lock);
rw_enter(&connp->conn_ilg_lock, RW_WRITER);
ilg = connp->conn_ilg;
held_ilg = NULL;
while (ilg != NULL) {
if (ilg->ilg_condemned) {
ilg = ilg->ilg_next;
continue;
}
if (ilg->ilg_ilm == NULL) {
next_ilg = ilg->ilg_next;
ilg_delete(connp, ilg, NULL);
ilg = next_ilg;
continue;
}
ill = ilg->ilg_ilm->ilm_ill;
need_refrele = B_FALSE;
if (!mutex_tryenter(&ill->ill_mcast_serializer)) {
ill_refhold(ill);
need_refrele = B_TRUE;
ilg_refhold(ilg);
if (held_ilg != NULL)
ilg_refrele(held_ilg);
held_ilg = ilg;
rw_exit(&connp->conn_ilg_lock);
mutex_enter(&ill->ill_mcast_serializer);
rw_enter(&connp->conn_ilg_lock, RW_WRITER);
if (ilg->ilg_condemned) {
ilg = ilg->ilg_next;
goto next;
}
}
ilm = ilg->ilg_ilm;
ilg->ilg_ilm = NULL;
next_ilg = ilg->ilg_next;
ilg_delete(connp, ilg, NULL);
ilg = next_ilg;
rw_exit(&connp->conn_ilg_lock);
if (ilm != NULL)
(void) ip_delmulti_serial(ilm, B_FALSE, B_TRUE);
next:
mutex_exit(&ill->ill_mcast_serializer);
ill_mcast_send_queued(ill);
ill_dlpi_send_queued(ill);
if (need_refrele) {
ill_refrele(ill);
}
rw_enter(&connp->conn_ilg_lock, RW_WRITER);
}
if (held_ilg != NULL)
ilg_refrele(held_ilg);
rw_exit(&connp->conn_ilg_lock);
}
static void
ilg_attach(conn_t *connp, ilg_t *ilg, ill_t *ill)
{
ilg_stat_t ilgstat;
slist_t *new_filter;
int new_fmode;
in6_addr_t v6group;
ipaddr_t ifaddr;
uint_t ifindex;
ilm_t *ilm;
int error = 0;
ASSERT(RW_WRITE_HELD(&connp->conn_ilg_lock));
if ((new_filter = l_alloc()) == NULL)
return;
new_fmode = ilg->ilg_fmode;
l_copy(ilg->ilg_filter, new_filter);
v6group = ilg->ilg_v6group;
ifaddr = ilg->ilg_ifaddr;
ifindex = ilg->ilg_ifindex;
ilgstat = ILGSTAT_NEW;
ilg->ilg_ill = ill;
ASSERT(ilg->ilg_ilm == NULL);
rw_exit(&connp->conn_ilg_lock);
ilm = ip_addmulti_serial(&v6group, ill, connp->conn_zoneid, ilgstat,
new_fmode, new_filter, &error);
l_free(new_filter);
rw_enter(&connp->conn_ilg_lock, RW_WRITER);
ilg = ilg_lookup(connp, &v6group, ifaddr, ifindex);
if (ilg == NULL || ilg->ilg_ilm != NULL) {
if (ilm != NULL) {
rw_exit(&connp->conn_ilg_lock);
(void) ip_delmulti_serial(ilm, B_FALSE,
(ilgstat == ILGSTAT_NEW));
rw_enter(&connp->conn_ilg_lock, RW_WRITER);
}
return;
}
if (ilm == NULL) {
ilg->ilg_ill = NULL;
return;
}
ilg->ilg_ilm = ilm;
ilm->ilm_ifaddr = ifaddr;
}
static void
conn_update_ill(conn_t *connp, caddr_t arg)
{
ill_t *ill = (ill_t *)arg;
mutex_enter(&connp->conn_lock);
if (connp->conn_state_flags & (CONN_CLOSING|CONN_UPDATE_ILL)) {
mutex_exit(&connp->conn_lock);
return;
}
connp->conn_state_flags |= CONN_UPDATE_ILL;
mutex_exit(&connp->conn_lock);
if (ill != NULL)
ilg_check_detach(connp, ill);
ilg_check_reattach(connp, ill);
mutex_enter(&connp->conn_lock);
connp->conn_state_flags &= ~CONN_UPDATE_ILL;
if (connp->conn_state_flags & CONN_CLOSING)
cv_broadcast(&connp->conn_cv);
mutex_exit(&connp->conn_lock);
}
static void
ilg_check_detach(conn_t *connp, ill_t *ill)
{
char group_buf[INET6_ADDRSTRLEN];
ilg_t *ilg, *held_ilg;
ilm_t *ilm;
mutex_enter(&ill->ill_mcast_serializer);
rw_enter(&connp->conn_ilg_lock, RW_WRITER);
held_ilg = NULL;
for (ilg = connp->conn_ilg; ilg != NULL; ilg = ilg->ilg_next) {
if (ilg->ilg_condemned)
continue;
if (ilg->ilg_ill != ill)
continue;
ip1dbg(("ilg_check_detach: detach %s on %s\n",
inet_ntop(AF_INET6, &ilg->ilg_v6group,
group_buf, sizeof (group_buf)),
ilg->ilg_ill->ill_name));
ilm = ilg->ilg_ilm;
ilg->ilg_ilm = NULL;
ilg->ilg_ill = NULL;
if (ilm == NULL)
continue;
ilg_transfer_hold(held_ilg, ilg);
held_ilg = ilg;
rw_exit(&connp->conn_ilg_lock);
(void) ip_delmulti_serial(ilm, B_FALSE, B_TRUE);
rw_enter(&connp->conn_ilg_lock, RW_WRITER);
}
if (held_ilg != NULL)
ilg_refrele(held_ilg);
rw_exit(&connp->conn_ilg_lock);
mutex_exit(&ill->ill_mcast_serializer);
ill_mcast_send_queued(ill);
ill_dlpi_send_queued(ill);
}
static void
ilg_check_reattach(conn_t *connp, ill_t *oill)
{
ill_t *ill;
char group_buf[INET6_ADDRSTRLEN];
ilg_t *ilg, *held_ilg;
ilm_t *ilm;
zoneid_t zoneid = IPCL_ZONEID(connp);
int error;
ip_stack_t *ipst = connp->conn_netstack->netstack_ip;
rw_enter(&connp->conn_ilg_lock, RW_WRITER);
held_ilg = NULL;
for (ilg = connp->conn_ilg; ilg != NULL; ilg = ilg->ilg_next) {
if (ilg->ilg_condemned)
continue;
ill = ill_mcast_lookup(&ilg->ilg_v6group, ilg->ilg_ifaddr,
ilg->ilg_ifindex, zoneid, ipst, &error);
if (ill != NULL &&
(!(ill->ill_flags & ILLF_MULTICAST) || !ill->ill_dl_up)) {
ilg_transfer_hold(held_ilg, ilg);
held_ilg = ilg;
rw_exit(&connp->conn_ilg_lock);
ill_refrele(ill);
ill = NULL;
rw_enter(&connp->conn_ilg_lock, RW_WRITER);
}
if (ill == ilg->ilg_ill || (ill != NULL && ill == oill)) {
if (ill != NULL) {
ilg_transfer_hold(held_ilg, ilg);
held_ilg = ilg;
rw_exit(&connp->conn_ilg_lock);
ill_refrele(ill);
rw_enter(&connp->conn_ilg_lock, RW_WRITER);
}
continue;
}
if (ilg->ilg_ill != NULL) {
ill_t *ill2 = ilg->ilg_ill;
boolean_t need_refrele = B_FALSE;
if (!mutex_tryenter(&ill2->ill_mcast_serializer)) {
ill_refhold(ill2);
need_refrele = B_TRUE;
ilg_transfer_hold(held_ilg, ilg);
held_ilg = ilg;
rw_exit(&connp->conn_ilg_lock);
mutex_enter(&ill2->ill_mcast_serializer);
rw_enter(&connp->conn_ilg_lock, RW_WRITER);
}
if (ilg->ilg_ill == ill2) {
ASSERT(!ilg->ilg_condemned);
ip1dbg(("conn_check_reattach: detach %s/%s\n",
inet_ntop(AF_INET6, &ilg->ilg_v6group,
group_buf, sizeof (group_buf)),
ill2->ill_name));
ilm = ilg->ilg_ilm;
ilg->ilg_ilm = NULL;
ilg->ilg_ill = NULL;
} else {
ilm = NULL;
}
ilg_transfer_hold(held_ilg, ilg);
held_ilg = ilg;
rw_exit(&connp->conn_ilg_lock);
if (ilm != NULL)
(void) ip_delmulti_serial(ilm, B_FALSE, B_TRUE);
mutex_exit(&ill2->ill_mcast_serializer);
ill_mcast_send_queued(ill2);
ill_dlpi_send_queued(ill2);
if (need_refrele) {
ill_refrele(ill2);
}
rw_enter(&connp->conn_ilg_lock, RW_WRITER);
if (ilg->ilg_ill != NULL) {
if (ill != NULL) {
ilg_transfer_hold(held_ilg, ilg);
held_ilg = ilg;
rw_exit(&connp->conn_ilg_lock);
ill_refrele(ill);
rw_enter(&connp->conn_ilg_lock,
RW_WRITER);
}
continue;
}
}
if (ill != NULL) {
if (!mutex_tryenter(&ill->ill_mcast_serializer)) {
ilg_transfer_hold(held_ilg, ilg);
held_ilg = ilg;
rw_exit(&connp->conn_ilg_lock);
mutex_enter(&ill->ill_mcast_serializer);
rw_enter(&connp->conn_ilg_lock, RW_WRITER);
}
ilg_transfer_hold(held_ilg, ilg);
held_ilg = ilg;
if (ilg->ilg_ill == NULL && !ilg->ilg_condemned) {
ip1dbg(("conn_check_reattach: attach %s/%s\n",
inet_ntop(AF_INET6, &ilg->ilg_v6group,
group_buf, sizeof (group_buf)),
ill->ill_name));
ilg_attach(connp, ilg, ill);
ASSERT(RW_WRITE_HELD(&connp->conn_ilg_lock));
}
rw_exit(&connp->conn_ilg_lock);
mutex_exit(&ill->ill_mcast_serializer);
ill_mcast_send_queued(ill);
ill_dlpi_send_queued(ill);
ill_refrele(ill);
rw_enter(&connp->conn_ilg_lock, RW_WRITER);
}
}
if (held_ilg != NULL)
ilg_refrele(held_ilg);
rw_exit(&connp->conn_ilg_lock);
}
void
update_conn_ill(ill_t *ill, ip_stack_t *ipst)
{
ipcl_walk(conn_update_ill, (caddr_t)ill, ipst);
}