/*
* CDDL HEADER START
*
* The contents of this file are subject to the terms of the
* Common Development and Distribution License (the "License").
* You may not use this file except in compliance with the License.
*
* You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
* See the License for the specific language governing permissions
* and limitations under the License.
*
* When distributing Covered Code, include this CDDL HEADER in each
* file and include the License file at usr/src/OPENSOLARIS.LICENSE.
* If applicable, add the following below this CDDL HEADER, with the
* fields enclosed by brackets "[]" replaced with your own identifying
* information: Portions Copyright [yyyy] [name of copyright owner]
*
* CDDL HEADER END
*/
/*
* Copyright (c) 1990 Mentat Inc.
*/
#include <inet/ip_multi.h>
#include <inet/ipclassifier.h>
#include <inet/ipsec_impl.h>
#include <inet/ip_listutils.h>
#include <inet/udp_impl.h>
const in6_addr_t *v6src);
ill_t **);
/*
* MT notes:
*
* Multicast joins operate on both the ilg and ilm structures. Multiple
* threads operating on an conn (socket) trying to do multicast joins
* need to synchronize when operating on the ilg. Multiple threads
* potentially operating on different conn (socket endpoints) trying to
* do multicast joins could eventually end up trying to manipulate the
* ilm simulatenously and need to synchronize on the access to the ilm.
* The access and lookup of the ilm, as well as other ill multicast state,
* is under ill_mcast_lock.
* The modifications and lookup of ilg entries is serialized using conn_ilg_lock
* rwlock. An ilg will not be freed until ilg_refcnt drops to zero.
*
* In some cases we hold ill_mcast_lock and then acquire conn_ilg_lock, but
* never the other way around.
*
* An ilm is associated with a <multicast group, ipif> tuple in IPv4 and
* with just <multicast group> in IPv6. ilm_refcnt is the number of ilg's
* referencing the ilm.
* The modifications and lookup of ilm entries is serialized using the
* of the ilm state.
* ilms are created / destroyed only as writer. ilms
* are not passed around. The datapath (anything outside of this file
* and igmp.c) use functions that do not return ilms - just the number
* of members. So we don't need a dynamic refcount of the number
* of threads holding reference to an ilm.
*
* In the cases where we serially access the ilg and ilm, which happens when
* we handle the applications requests to join or leave groups and sources,
* we use the ill_mcast_serializer mutex to ensure that a multithreaded
* the same socket always results in a consistent order for the ilg and ilm
* modifications.
*
* When a multicast operation results in needing to send a message to
*/
/*
* Caller must ensure that the ilg has not been condemned
* The condemned flag is only set in ilg_delete under conn_ilg_lock.
*
* The caller must hold conn_ilg_lock as writer.
*/
static void
{
ilg->ilg_refcnt++;
}
static void
{
/* Unlink from list */
}
/*
* The caller must hold conn_ilg_lock as writer.
*/
static void
{
if (--ilg->ilg_refcnt == 0)
}
/*
* Acquire reference on ilg and drop reference on held_ilg.
* In the case when held_ilg is the same as ilg we already have
* a reference, but the held_ilg might be condemned. In that case
* we avoid the ilg_refhold/rele so that we can assert in ire_refhold
* that the ilg isn't condemned.
*/
static void
{
return;
}
/*
* Allocate a new ilg_t and links it into conn_ilg.
* Returns NULL on failure, in which case `*errp' will be
* filled in with the reason.
*
* Assumes connp->conn_ilg_lock is held.
*/
static ilg_t *
{
/*
* If CONN_CLOSING is set, conn_ilg cleanup has begun and we must not
* create any ilgs.
*/
return (NULL);
}
return (NULL);
}
/* Insert at head */
return (ilg);
}
typedef struct ilm_fbld_s {
int fbld_in_cnt;
int fbld_ex_cnt;
} ilm_fbld_t;
/*
* Caller must hold ill_mcast_lock
*/
static void
{
return;
/*
* Since we can't break out of the ipcl_walk once started, we still
* have to look at every conn. But if we've already found one
* (EXCLUDE, NULL) list, there's no need to keep checking individual
* ilgs--that will be our state.
*/
return;
/*
* Check this conn's ilgs to see if any are interested in our
* ilm (group, interface match). If so, update the master
* include and exclude lists we're building in the fbld struct
* with this ilg's filter info.
*
* Note that the caller has already serialized on the ill we care
* about.
*/
if (ilg->ilg_condemned)
continue;
/*
* Since we are under the ill_mcast_serializer we know
* that any ilg+ilm operations on this ilm have either
* not started or completed, except for the last ilg
* (the one that caused us to be called) which doesn't
* have ilg_ilm set yet. Hence we compare using ilg_ill
* and the address.
*/
fbld->fbld_in_cnt++;
if (!fbld->fbld_in_overflow)
&fbld->fbld_in_overflow);
} else {
fbld->fbld_ex_cnt++;
/*
* On the first exclude list, don't try to do
* an intersection, as the master exclude list
* is intentionally empty. If the master list
* is still empty on later iterations, that
* means we have at least one ilg with an empty
* exclude list, so that should be reflected
* when we take the intersection.
*/
} else {
ilg->ilg_filter);
}
}
/* there will only be one match, so break now. */
break;
}
}
}
/*
* Caller must hold ill_mcast_lock
*/
static void
{
/* first, construct our master include and exclude lists */
/* now use those master lists to generate the interface filter */
/* if include list overflowed, filter is (EXCLUDE, NULL) */
if (fbld.fbld_in_overflow) {
*fmode = MODE_IS_EXCLUDE;
return;
}
/* if nobody interested, interface filter is (INCLUDE, NULL) */
*fmode = MODE_IS_INCLUDE;
return;
}
/*
* If there are no exclude lists, then the interface filter
* is INCLUDE, with its filter list equal to fbld_in. A single
* exclude list makes the interface filter EXCLUDE, with its
* filter list equal to (fbld_ex - fbld_in).
*/
if (fbld.fbld_ex_cnt == 0) {
*fmode = MODE_IS_INCLUDE;
} else {
*fmode = MODE_IS_EXCLUDE;
}
}
/*
* Caller must hold ill_mcast_lock
*/
static int
{
/*
* There are several cases where the ilm's filter state
* defaults to (EXCLUDE, NULL):
* - we've had previous joins without associated ilgs
* - this join has no associated ilg
* - the ilg's filter state is (EXCLUDE, NULL)
*/
/* attempt mallocs (if needed) before doing anything else */
return (ENOMEM);
return (ENOMEM);
}
}
if (ilgstat != ILGSTAT_CHANGE)
ilm->ilm_refcnt++;
if (ilgstat == ILGSTAT_NONE)
ilm->ilm_no_ilg_cnt++;
/*
* Determine new filter state. If it's not the default
* (EXCLUDE, NULL), we must walk the conn list to find
* any ilgs interested in this group, and re-build the
* ilm filter.
*/
if (fdefault) {
} else {
}
/* make sure state actually changed; nothing to do if not. */
return (0);
}
/* send the state change report */
if (!IS_LOOPBACK(ill)) {
else
}
/* update the ilm state */
else
return (0);
}
/*
* Caller must hold ill_mcast_lock
*/
static int
{
ip1dbg(("ilm_update_del: still %d left; updating state\n",
ilm->ilm_refcnt));
return (ENOMEM);
/*
* If present, the ilg in question has already either been
* updated or removed from our list; so all we need to do
* now is walk the list to update the ilm filter state.
*
* Skip the list walk if we have any no-ilg joins, which
* cause the filter state to revert to (EXCLUDE, NULL).
*/
if (ilm->ilm_no_ilg_cnt != 0) {
} else {
}
/* check to see if state needs to be updated */
return (0);
}
if (!IS_LOOPBACK(ill)) {
else
}
ip1dbg(("ilm_update_del: failed to alloc ilm "
"filter; no source filtering for %s on %s",
return (0);
}
}
} else {
}
return (0);
}
/*
* do the ILGSTAT_NONE (no ilg), MODE_IS_EXCLUDE, with no slist join.
* Returns with a refhold on the ilm.
*
* The unspecified address means all multicast addresses for in both the
* case of IPv4 and IPv6.
*
* The caller should have already mapped an IPMP under ill to the upper.
*/
ilm_t *
int *errorp)
{
/* Acquire serializer to keep assert in ilm_bld_flists happy */
/*
* Now that all locks have been dropped, we can send any
*/
return (ilm);
}
/*
* then this returns with a refhold on the ilm.
*
* Internal routine which assumes the caller has already acquired
* ill_mcast_serializer. It is the caller's responsibility to send out
*
* The unspecified address means all multicast addresses for in both the
* case of IPv4 and IPv6.
*
* ilgstat tells us if there's an ilg associated with this join,
* and if so, if it's a new ilg or a change to an existing one.
* ilg_fmode and ilg_flist give us the current filter state of
* the ilg (and will be EXCLUDE {NULL} in the case of no ilg).
*
* The caller should have already mapped an IPMP under ill to the upper.
*/
static ilm_t *
int *errorp)
{
if (!IN6_IS_ADDR_MULTICAST(v6group) &&
return (NULL);
}
} else {
if (IN6_IS_ADDR_V4MAPPED(v6group)) {
return (NULL);
}
} else if (!IN6_IS_ADDR_UNSPECIFIED(v6group)) {
return (NULL);
}
}
if (IS_UNDER_IPMP(ill)) {
return (NULL);
}
/*
* We do the equivalent of a lookup by checking after we get the lock
* This is needed since the ill could have been condemned after
* we looked it up, and we need to check condemned after we hold
* ill_mcast_lock to synchronize with the unplumb code.
*/
return (NULL);
}
return (ilm);
}
static ilm_t *
int *errorp)
{
int ret = 0;
*errorp = 0;
/*
* An ilm is uniquely identified by the tuple of (group, ill) where
* `group' is the multicast group address, and `ill' is the interface
* on which it is currently joined.
*/
/* ilm_update_add bumps ilm_refcnt unless ILGSTAT_CHANGE */
if (ret == 0)
return (ilm);
return (NULL);
}
/*
* The callers checks on the ilg and the ilg+ilm consistency under
* ill_mcast_serializer ensures that we can not have ILGSTAT_CHANGE
* and no ilm.
*/
return (NULL);
}
if (IN6_IS_ADDR_UNSPECIFIED(v6group)) {
/*
* If we have more then one we should not tell the driver
* to join this time.
*/
}
} else {
if (!IS_LOOPBACK(ill)) {
else
}
/*
* If we have more then one we should not tell the driver
* to join this time.
*/
}
}
if (ret != 0) {
ip0dbg(("ip_addmulti: ENETDOWN for %s on %s",
}
return (NULL);
} else {
return (ilm);
}
}
/*
* Looks up the list of multicast physical addresses this interface
* listens to. Add to the list if not present already.
*/
{
mpa->mpa_refcnt++;
return (B_FALSE);
}
}
/*
* We risk not having the multiphysadd structure. At this
* point we can't fail. We can't afford to not send a
* DL_ENABMULTI_REQ also. It is better than pre-allocating
* the structure and having the code to track it also.
*/
ip0dbg(("ip_mphysaddr_add: ENOMEM. Some multicast apps"
" may have issues. hw_addr: %p ill_name: %s\n",
return (B_TRUE);
}
return (B_TRUE);
}
/*
* Look up hw_addr from the list of physical multicast addresses this interface
* listens to.
* Remove the entry if the refcnt is 0
*/
{
break;
}
/*
* Should be coming here only when there was a memory
* exhaustion and we were not able to allocate
* a multiphysaddr_t. We still send a DL_DISABMULTI_REQ down.
*/
ip0dbg(("ip_mphysaddr_del: No entry for this addr. Some "
"multicast apps might have had issues. hw_addr: %p "
} else if (--mpap->mpa_refcnt == 0) {
}
return (ret);
}
/*
* Send a multicast request to the driver for enabling or disabling
* multicast reception for v6groupp address. The caller has already
* checked whether it is appropriate to send one or not.
*
* For IPMP we switch to the cast_ill since it has the right hardware
* information.
*/
static int
{
int err = 0;
/* On the upper IPMP ill. */
if (release_ill == NULL) {
/*
* Avoid sending it down to the ipmpstub.
* We will be called again once the members of the
* group are in place
*/
ip1dbg(("ip_ll_send_multireq: no cast_ill for %s %d\n",
return (0);
}
ill = release_ill;
}
/* Create a DL_ENABMULTI_REQ or DL_DISABMULTI_REQ message. */
goto done;
}
goto done;
}
case DL_ENABMULTI_REQ:
err = 0;
goto done;
}
/* Track the state if this is the first enabmulti */
break;
case DL_DISABMULTI_REQ:
err = 0;
goto done;
}
}
done:
if (release_ill != NULL)
return (err);
}
/*
* Send a multicast request to the driver for enabling multicast
* membership for v6group if appropriate.
*/
static int
{
ip1dbg(("ip_ll_multireq: not resolver\n"));
return (0); /* Must be IRE_IF_NORESOLVER */
}
ip1dbg(("ip_ll_multireq: MULTI_BCAST\n"));
return (0);
}
}
/*
* being true.
*/
int
{
int error;
/* Acquire serializer to keep assert in ilm_bld_flists happy */
/*
* Now that all locks have been dropped, we can send any
*/
return (error);
}
/*
* Delete the ilm.
* Assumes ill_mcast_serializer is held by the caller.
* all locks.
*/
static int
{
int ret;
return (ret);
}
static int
{
int error;
/* Update counters */
if (no_ilg)
ilm->ilm_no_ilg_cnt--;
if (leaving)
ilm->ilm_refcnt--;
if (ilm->ilm_refcnt > 0)
return (ilm_update_del(ilm));
/*
* If we have some left then one we should not tell the driver
* to leave.
*/
return (0);
return (0);
}
if (!IS_LOOPBACK(ill)) {
else
}
/*
* If we have some left then one we should not tell the driver
* to leave.
*/
return (0);
/* We ignore the case when ill_dl_up is not set */
ip0dbg(("ip_delmulti: ENETDOWN for %s on %s",
}
return (error);
}
/*
* Make the driver pass up all multicast packets.
*/
int
{
if (IS_LOOPBACK(ill))
return (0);
/*
* Nobody there. All multicast addresses will be re-joined
* when we get the DL_BIND_ACK bringing the interface up.
*/
return (ENETDOWN);
}
/* On the upper IPMP ill. */
if (release_ill == NULL) {
/*
* Avoid sending it down to the ipmpstub.
* We will be called again once the members of the
* group are in place
*/
ip1dbg(("ill_join_allmulti: no cast_ill for %s %d\n",
return (0);
}
ill = release_ill;
return (ENETDOWN);
}
}
/*
* Create a DL_PROMISCON_REQ message and send it directly to the DLPI
* provider. We don't need to do this for certain media types for
* which we never need to turn promiscuous mode on. While we're here,
* pre-allocate a DL_PROMISCOFF_REQ message to make sure that
* ill_leave_allmulti() will not fail due to low memory conditions.
*/
if (promiscon_mp == NULL ||
if (release_ill != NULL)
return (ENOMEM);
}
}
if (release_ill != NULL)
return (0);
}
/*
* Make the driver stop passing up all multicast packets
*/
void
{
if (IS_LOOPBACK(ill))
return;
/*
* Nobody there. All multicast addresses will be re-joined
* when we get the DL_BIND_ACK bringing the interface up.
*/
return;
}
/* On the upper IPMP ill. */
if (release_ill == NULL) {
/*
* Avoid sending it down to the ipmpstub.
* We will be called again once the members of the
* group are in place
*/
ip1dbg(("ill_leave_allmulti: no cast_ill on %s %d\n",
return;
}
ill = release_ill;
goto done;
}
/*
* In the case of IPMP and ill_dl_up not being set when we joined
* we didn't allocate a promiscoff_mp. In that case we have
* nothing to do when we leave.
* Ditto for PHYI_MULTI_BCAST
*/
if (promiscoff_mp != NULL) {
}
done:
if (release_ill != NULL)
}
int
{
int ret;
return (ENODEV);
/*
* The ip_addmulti() function doesn't allow IPMP underlying interfaces
* to join allmulti since only the nominated underlying interface in
* the group should receive multicast. We silently succeed to avoid
* having to teach IPobs (currently the only caller of this routine)
* to ignore failures in this case.
*/
if (IS_UNDER_IPMP(ill)) {
return (0);
}
if (ill->ill_ipallmulti_cnt > 0) {
/* Already joined */
goto done;
}
return (ret);
}
if (ill->ill_ipallmulti_cnt > 0) {
/* Another thread added it concurrently */
(void) ip_delmulti(ilm);
goto done;
}
done:
return (0);
}
int
{
return (ENODEV);
if (IS_UNDER_IPMP(ill)) {
return (0);
}
if (ill->ill_ipallmulti_cnt == 0) {
/* ip_purge_allmulti could have removed them all */
goto done;
}
if (ill->ill_ipallmulti_cnt == 0) {
/* Last one */
} else {
}
(void) ip_delmulti(ilm);
done:
return (0);
}
/*
* Delete the allmulti memberships that were added as part of
* ip_join_allmulti().
*/
void
{
ill->ill_ipallmulti_cnt = 0;
(void) ip_delmulti(ilm);
}
/*
* Create a dlpi message with room for phys+sap. Later
* we will strip the sap for those primitives which
* only need a physical address.
*/
static mblk_t *
{
char *cp;
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);
break;
case DL_DISABMULTI_REQ:
length = sizeof (dl_disabmulti_req_t);
break;
case DL_PROMISCON_REQ:
case DL_PROMISCOFF_REQ:
break;
default:
return (NULL);
}
if (!mp)
return (NULL);
switch (dl_primitive) {
case DL_ENABMULTI_REQ: {
break;
}
case DL_DISABMULTI_REQ: {
break;
}
case DL_PROMISCON_REQ:
case DL_PROMISCOFF_REQ: {
break;
}
}
ip1dbg(("ill_create_dl: addr_len %d, addr_off %d\n",
return (mp);
}
/*
* Rejoin any groups for which we have ilms.
*
* This is only needed for IPMP when the cast_ill changes since that
* change is invisible to the ilm. Other interface changes are handled
* by conn_update_ill.
*/
void
{
/*
* If we have more then one ilm for the group (e.g., with
* different zoneid) then we should not tell the driver
* to join unless this is the first ilm for the group.
*/
continue;
}
(void) ill_join_allmulti(ill);
} else {
else
}
}
}
/*
* The opposite of ill_recover_multicast() -- leaves all multicast groups
* that were explicitly joined.
*
* This is only needed for IPMP when the cast_ill changes since that
* change is invisible to the ilm. Other interface changes are handled
* by conn_update_ill.
*/
void
{
/*
* If we have more then one ilm for the group (e.g., with
* different zoneid) then we should not tell the driver
* to leave unless this is the first ilm for the group.
*/
continue;
}
} else {
else
}
}
}
/*
* Returns true if there is a member on the ill for any zoneid.
*/
{
}
/*
* Returns true if there is a member on the ill for any zoneid.
*
* The group and source can't be INADDR_ANY here so no need to translate to
* the unspecified IPv6 address.
*/
{
}
/*
* Returns true if there is a member on the ill for any zoneid except skipzone.
*/
{
return (B_TRUE);
}
}
return (B_FALSE);
}
/*
* Returns true if there is a member on the ill for any zoneid except skipzone.
*
* The group and source can't be INADDR_ANY here so no need to translate to
* the unspecified IPv6 address.
*/
{
}
/*
* Interface used by IP input.
* Returns the next numerically larger zoneid that has a member. If none exist
* then returns -1 (ALL_ZONES).
* The normal usage is for the caller to start with a -1 zoneid (ALL_ZONES)
* to find the first zoneid which has a member, and then pass that in for
* subsequent calls until ALL_ZONES is returned.
*
* The implementation of ill_hasmembers_nextzone() assumes the ilms
* are sorted by zoneid for efficiency.
*/
{
return (zoneid);
}
}
return (ALL_ZONES);
}
/*
* Interface used by IP input.
* Returns the next numerically larger zoneid that has a member. If none exist
* then returns -1 (ALL_ZONES).
*
* The group and source can't be INADDR_ANY here so no need to translate to
* the unspecified IPv6 address.
*/
{
}
/*
* Find an ilm matching the ill, group, and zoneid.
*/
static ilm_t *
{
continue;
continue;
return (ilm);
}
return (NULL);
}
/*
* How many members on this ill?
* we can have several.
*/
static int
{
int i = 0;
i++;
}
}
return (i);
}
/* Caller guarantees that the group is not already on the list */
static ilm_t *
{
return (NULL);
return (NULL);
}
}
ill->ill_ilm_cnt++;
/*
* To make ill_hasmembers_nextzone_v6 work we keep the list
* sorted by zoneid.
*/
}
/*
* If we have an associated ilg, use its filter state; if not,
* default to (EXCLUDE, NULL) and set no_ilg_cnt to track this.
*/
if (ilgstat != ILGSTAT_NONE) {
if (!SLIST_IS_EMPTY(ilg_flist))
} else {
}
return (ilm);
}
void
{
}
/*
* Unlink ilm and free it.
*/
static void
{
/*
* Delete under lock protection so that readers don't stumble
* on bad ilm_next
*/
;
/*
* if we are the last reference to the ill, we may need to wakeup any
* pending FREE or unplumb operations. This is because conn_update_ill
* bails if there is a ilg_delete_all in progress.
*/
ill->ill_ilm_cnt--;
if (ILL_FREE_OK(ill))
if (need_wakeup) {
/* drops ill lock */
} else {
}
}
/*
* Lookup an ill based on the group, ifindex, ifaddr, and zoneid.
* Applies to both IPv4 and IPv6, although ifaddr is only used with
* IPv4.
* Returns an error for IS_UNDER_IPMP and VNI interfaces.
* On error it sets *errorp.
*/
static ill_t *
{
if (IN6_IS_ADDR_V4MAPPED(group)) {
if (ifindex != 0) {
} else if (ifaddr != INADDR_ANY) {
} else {
}
} else {
NULL);
}
} else {
if (ifindex != 0) {
} else {
NULL);
}
}
if (ifindex != 0)
else
*errorp = EADDRNOTAVAIL;
return (NULL);
}
/* operation not supported on the virtual network interface */
return (NULL);
}
return (ill);
}
/*
* Looks up the appropriate ill given an interface index (or interface address)
* and multicast group. On success, returns 0, with *illpp pointing to the
* found struct. On failure, returns an errno and *illpp is set to NULL.
*
* Returns an error for IS_UNDER_IPMP and VNI interfaces.
*
* Handles both IPv4 and IPv6. The ifaddr argument only applies in the
* case of IPv4.
*/
int
{
int error = 0;
if (IN6_IS_ADDR_V4MAPPED(v6group)) {
return (EINVAL);
if (src_unspec) {
v4src = INADDR_ANY;
} else {
}
return (EINVAL);
} else {
return (EINVAL);
if (!IN6_IS_ADDR_MULTICAST(v6group) ||
return (EINVAL);
}
}
return (error);
}
static int
{
ifindex = 0;
} else {
ifaddr = INADDR_ANY;
}
/* No need to use ill_mcast_serializer for the reader */
return (EADDRNOTAVAIL);
}
/*
* In the kernel, we use the state definitions MODE_IS_[IN|EX]CLUDE
* to identify the filter mode; but the API uses MCAST_[IN|EX]CLUDE.
* So we need to translate here.
*/
numsrc = 0;
} else {
for (i = 0; i < outsrcs; i++) {
break;
if (issin6) {
} else {
if (is_v4only_api) {
} else {
sin = (struct sockaddr_in *)
}
}
}
}
if (is_v4only_api) {
} else {
}
return (0);
}
/*
* Common for IPv4 and IPv6.
*/
static int
{
ifindex = 0;
} else {
ifaddr = INADDR_ANY;
}
/* Make sure we can handle the source list */
if (insrcs > MAX_FILTER_SIZE)
return (ENOBUFS);
/*
* setting the filter to (INCLUDE, NULL) is treated
* as a request to leave the group.
*/
/*
* if the request was actually to leave, and we
* didn't find an ilg, there's nothing to do.
*/
if (leave_group) {
return (0);
}
return (err);
}
} else if (leave_group) {
/*
* Make sure we have the correct serializer. The ill argument
* might not match ilg_ill.
*/
/*
* Now that all locks have been dropped, we can send any
*/
return (0);
} else {
/* Preserve existing state in case ip_addmulti() fails */
orig_filter = NULL;
} else {
if (orig_filter == NULL) {
return (ENOMEM);
}
}
}
/*
* Alloc buffer to copy new state into (see below) before
* we make any changes, so we can bail if it fails.
*/
goto free_and_exit;
}
if (insrcs == 0) {
} else {
if (ilgstat == ILGSTAT_NEW)
goto free_and_exit;
}
} else {
}
for (i = 0; i < insrcs; i++) {
if (issin6) {
} else {
if (is_v4only_api) {
} else {
sin = (struct sockaddr_in *)
}
}
}
}
/*
* In the kernel, we use the state definitions MODE_IS_[IN|EX]CLUDE
* to identify the filter mode; but the API uses MCAST_[IN|EX]CLUDE.
* So we need to translate here.
*/
/*
* Save copy of ilg's filter state to pass to other functions,
* so we can release conn_ilg_lock now.
*/
/*
* Now update the ill. We wait to do this until after the ilg
* has been updated because we need to update the src filter
* info for the ill, which involves looking at the status of
*/
/*
* Must look up the ilg again since we've not been holding
* conn_ilg_lock. The ilg could have disappeared due to an unplumb
* having called conn_update_ill, which can run once we dropped the
* conn_ilg_lock above.
*/
(ilgstat == ILGSTAT_NEW));
}
goto free_and_exit;
}
/* some other thread is re-attaching this. */
(ilgstat == ILGSTAT_NEW));
err = 0;
goto free_and_exit;
}
/* Succeeded. Update the ilg to point at the ilm */
if (ilgstat == ILGSTAT_NEW) {
} else {
/* some other thread is re-attaching this. */
err = 0;
goto free_and_exit;
}
} else {
/*
* ip_addmulti didn't get a held ilm for
* ILGSTAT_CHANGE; ilm_refcnt was unchanged.
*/
}
} else {
/*
* Failed to allocate the ilm.
* Restore the original filter state, or delete the
* newly-created ilg.
* If ENETDOWN just clear ill_ilg since so that we
* will rejoin when the ill comes back; don't report ENETDOWN
* to application.
*/
if (ilgstat == ILGSTAT_NEW) {
err = 0;
} else {
}
} else {
if (SLIST_IS_EMPTY(orig_filter)) {
} else {
/*
* We didn't free the filter, even if we
* were trying to make the source list empty;
* so if orig_filter isn't empty, the ilg
* must still have a filter alloc'd.
*/
}
}
}
return (err);
}
/*
* Process the SIOC[GS]MSFILTER and SIOC[GS]IPMSFILTER ioctls.
*/
/* ARGSUSED */
int
{
/* existence verified in ip_wput_nondata() */
if (err != 0)
return (err);
return (ENOMEM);
}
return (EINVAL);
/*
* now we know we have at least have the initial structure,
* but need to check for the source list array.
*/
if (is_v4only_api) {
} else {
} else {
}
}
return (EINVAL);
if (isv6) {
if (getcmd) {
B_TRUE);
} else {
B_TRUE);
}
} else {
if (is_v4only_api) {
} else {
} else {
v4group);
}
}
/*
* INADDR_ANY is represented as the IPv6 unspecifed addr.
*/
if (v4group == INADDR_ANY)
else
if (getcmd) {
issin6);
} else {
issin6);
}
}
return (err);
}
/*
* Determine the ill for the SIOC*MSFILTER ioctls
*
* Returns an error for IS_UNDER_IPMP interfaces.
*
* Finds the ill based on information in the ioctl headers.
*/
static int
{
int err = 0;
/* caller has verified this mblk exists */
/* don't allow multicast operations on a tcp conn */
if (IPCL_IS_TCP(connp))
return (ENOPROTOOPT);
/* don't allow v4-specific ioctls on v6 socket */
return (EAFNOSUPPORT);
} else {
} else {
return (EAFNOSUPPORT);
}
}
return (err);
}
/*
* The structures used for the SIOC*MSFILTER ioctls usually must be copied
* in in two stages, as the first copyin tells us the size of the attached
* source buffer. This function is called by ip_wput_nondata() after the
* first copyin has completed; it figures out how big the second stage
* needs to be, and kicks it off.
*
* In some cases (numsrc < 2), the second copyin is not needed as the
* first one gets a complete structure containing 1 source addr.
*
* The function returns 0 if a second copyin has been started (i.e. there's
* no more work to be done right now), or 1 if the second copyin is not
* needed and ip_wput_nondata() can continue its processing.
*/
int
{
/* validity of this checked in ip_wput_nondata() */
int copysize = 0;
int offset;
offset = sizeof (struct group_filter);
}
} else {
offset = sizeof (struct ip_msfilter);
}
}
if (copysize > 0) {
return (0);
}
return (1);
}
/*
* Handle the following optmgmt:
* IP_ADD_MEMBERSHIP must not have joined already
* IPV6_JOIN_GROUP must not have joined already
* MCAST_JOIN_GROUP must not have joined already
* IP_BLOCK_SOURCE must have joined already
* MCAST_BLOCK_SOURCE must have joined already
* IP_JOIN_SOURCE_GROUP may have joined already
* MCAST_JOIN_SOURCE_GROUP may have joined already
*
* fmode and src parameters may be used to determine which option is
* being set, as follows (IPV6_JOIN_GROUP and MCAST_JOIN_GROUP options
* are functionally equivalent):
* opt fmode v6src
* IP_ADD_MEMBERSHIP MODE_IS_EXCLUDE unspecified
* IPV6_JOIN_GROUP MODE_IS_EXCLUDE unspecified
* MCAST_JOIN_GROUP MODE_IS_EXCLUDE unspecified
* IP_BLOCK_SOURCE MODE_IS_EXCLUDE IPv4-mapped addr
* MCAST_BLOCK_SOURCE MODE_IS_EXCLUDE v6 addr
* IP_JOIN_SOURCE_GROUP MODE_IS_INCLUDE IPv4-mapped addr
* MCAST_JOIN_SOURCE_GROUP MODE_IS_INCLUDE v6 addr
*
* Changing the filter mode is not allowed; if a matching ilg already
* exists and fmode != ilg->ilg_fmode, EINVAL is returned.
*
* Verifies that there is a source address of appropriate scope for
* the group; if not, EADDRNOTAVAIL is returned.
*
* The interface to be used may be identified by an IPv4 address or by an
* interface index.
*
* Handles IPv4-mapped IPv6 multicast addresses by associating them
* with the IPv4 address. Assumes that if v6group is v4-mapped,
* v6src is also v4-mapped.
*/
int
{
int err;
if (err != 0) {
ip1dbg(("ip_opt_add_group: no ill for group %s/"
return (err);
}
if (checkonly) {
/*
* do not do operation, just pretend to - new T_CHECK
* semantics. The error return case above if encountered
* considered a good enough "check" here.
*/
return (0);
}
/*
* Multicast groups may not be joined on interfaces that are either
* already underlying interfaces in an IPMP group, or in the process
* of joining the IPMP group. The latter condition is enforced by
* checking the value of ill->ill_grp_pending under the
* ill_mcast_serializer lock. We cannot serialize the
* ill_grp_pending check on the ill_g_lock across ilg_add() because
* ill_mcast_send_queued -> ip_output_simple -> ill_lookup_on_ifindex
* will take the ill_g_lock itself. Instead, we hold the
* ill_mcast_serializer.
*/
in6_addr_t *, v6group);
return (EADDRNOTAVAIL);
}
/*
* All locks have been dropped, we can send any
*/
return (err);
}
/*
* Common for IPv6 and IPv4.
* Here we handle ilgs that are still attached to their original ill
* The detached ones might have been attached to some other ill.
*/
static int
const in6_addr_t *v6src)
{
int err = 0;
/*
* Since we didn't have any ilg we now do the error checks
* to determine the best errno.
*/
&ill);
/* The only error was a missing ilg for the group */
err = EADDRNOTAVAIL;
}
return (err);
}
/* If the ilg is attached then we serialize using that ill */
/* Prevent the ill and ilg from being freed */
if (ilg->ilg_condemned) {
/* Disappeared */
goto retry;
}
}
/*
* Decide if we're actually deleting the ilg or just removing a
* source filter address; if just removing an addr, make sure we
* aren't trying to change the filter mode, and that the addr is
* actually in our filter list already. If we're removing the
* last src in an include list, just delete the ilg.
*/
if (IN6_IS_ADDR_UNSPECIFIED(v6src)) {
} else {
err = EADDRNOTAVAIL;
if (err != 0) {
goto done;
}
if (fmode == MODE_IS_INCLUDE &&
} else {
}
}
if (leaving)
}
done:
/*
* Now that all locks have been dropped, we can
*/
}
return (err);
}
/*
* Handle the following optmgmt:
* IP_DROP_MEMBERSHIP will leave
* IPV6_LEAVE_GROUP will leave
* MCAST_LEAVE_GROUP will leave
* IP_UNBLOCK_SOURCE will not leave
* MCAST_UNBLOCK_SOURCE will not leave
* IP_LEAVE_SOURCE_GROUP may leave (if leaving last source)
* MCAST_LEAVE_SOURCE_GROUP may leave (if leaving last source)
*
* fmode and src parameters may be used to determine which option is
* being set, as follows:
* opt fmode v6src
* IP_DROP_MEMBERSHIP MODE_IS_INCLUDE unspecified
* IPV6_LEAVE_GROUP MODE_IS_INCLUDE unspecified
* MCAST_LEAVE_GROUP MODE_IS_INCLUDE unspecified
* IP_UNBLOCK_SOURCE MODE_IS_EXCLUDE IPv4-mapped addr
* MCAST_UNBLOCK_SOURCE MODE_IS_EXCLUDE v6 addr
* IP_LEAVE_SOURCE_GROUP MODE_IS_INCLUDE IPv4-mapped addr
* MCAST_LEAVE_SOURCE_GROUP MODE_IS_INCLUDE v6 addr
*
* Changing the filter mode is not allowed; if a matching ilg already
* exists and fmode != ilg->ilg_fmode, EINVAL is returned.
*
* The interface to be used may be identified by an IPv4 address or by an
* interface index.
*
* Handles IPv4-mapped IPv6 multicast addresses by associating them
* with the IPv4 address. Assumes that if v6group is v4-mapped,
* v6src is also v4-mapped.
*/
int
{
/*
* In the normal case below we don't check for the ill existing.
* Instead we look for an existing ilg in _excl.
* If checkonly we sanity check the arguments
*/
if (checkonly) {
int err;
&ill);
/*
* do not do operation, just pretend to - new T_CHECK semantics.
* ip_opt_check is considered a good enough "check" here.
*/
return (err);
}
}
/*
* Group mgmt for upper conn that passes things down
* to the interface multicast list (and DLPI)
* These routines can handle new style options that specify an interface name
* as opposed to an interface address (needed for general handling of
* unnumbered interfaces.)
*/
/*
* Add a group to an upper conn group data structure and pass things down
* to the interface multicast list (and DLPI)
* Common for IPv4 and IPv6; for IPv4 we can have an ifaddr.
*/
static int
{
int error = 0;
int new_fmode;
return (EADDRNOTAVAIL);
/* conn_ilg_lock protects the ilg list. */
/*
* Depending on the option we're handling, may or may not be okay
* if group has already been added. Figure out our rules based
* on fmode and src params. Also make sure there's enough room
* in the filter if we're adding a source to an existing filter.
*/
if (IN6_IS_ADDR_UNSPECIFIED(v6src)) {
/* we're joining for all sources, must not have joined */
error = EADDRINUSE;
} else {
if (fmode == MODE_IS_EXCLUDE) {
/* (excl {addr}) => block source, must have joined */
}
/* (incl {addr}) => join source, may have joined */
}
if (error != 0) {
return (error);
}
/*
* Alloc buffer to copy new state into (see below) before
* we make any changes, so we can bail if it fails.
*/
return (ENOMEM);
}
return (error);
}
if (!IN6_IS_ADDR_UNSPECIFIED(v6src)) {
return (ENOMEM);
}
}
} else {
int index;
return (EINVAL);
}
return (ENOMEM);
}
}
return (EADDRNOTAVAIL);
}
}
/*
* Save copy of ilg's filter state to pass to other functions,
* so we can release conn_ilg_lock now.
*/
/*
* Now update the ill. We wait to do this until after the ilg
* has been updated because we need to update the src filter
* info for the ill, which involves looking at the status of
*/
/*
* Must look up the ilg again since we've not been holding
* conn_ilg_lock. The ilg could have disappeared due to an unplumb
* having called conn_update_ill, which can run once we dropped the
* conn_ilg_lock above.
*/
(ilgstat == ILGSTAT_NEW));
}
goto free_and_exit;
}
/* some other thread is re-attaching this. */
(ilgstat == ILGSTAT_NEW));
error = 0;
goto free_and_exit;
}
/* Succeeded. Update the ilg to point at the ilm */
if (ilgstat == ILGSTAT_NEW) {
} else {
/* some other thread is re-attaching this. */
error = 0;
goto free_and_exit;
}
} else {
/*
* ip_addmulti didn't get a held ilm for
* ILGSTAT_CHANGE; ilm_refcnt was unchanged.
*/
}
} else {
/*
* Failed to allocate the ilm.
* Need to undo what we did before calling ip_addmulti()
* If ENETDOWN just clear ill_ilg since so that we
* will rejoin when the ill comes back; don't report ENETDOWN
* to application.
*/
error = 0;
} else {
}
}
return (error);
}
/*
* Find an IPv4 ilg matching group, ill and source.
* The group and source can't be INADDR_ANY here so no need to translate to
* the unspecified IPv6 address.
*/
{
int i;
if (ilg->ilg_condemned)
continue;
/* ilg_ill could be NULL if an add is in progress */
continue;
/* The callers use upper ill for IPMP */
/* no source filter, so this is a match */
return (B_TRUE);
}
break;
}
}
return (B_FALSE);
}
/*
* we have an ilg with matching ill and group; but
* the ilg has a source list that we must check.
*/
break;
}
}
return (B_TRUE);
}
return (B_FALSE);
}
/*
* Find an IPv6 ilg matching group, ill, and source
*/
{
int i;
if (ilg->ilg_condemned)
continue;
/* ilg_ill could be NULL if an add is in progress */
continue;
/* The callers use upper ill for IPMP */
/* no source filter, so this is a match */
return (B_TRUE);
}
break;
}
}
return (B_FALSE);
}
/*
* we have an ilg with matching ill and group; but
* the ilg has a source list that we must check.
*/
break;
}
}
return (B_TRUE);
}
return (B_FALSE);
}
/*
* We check both ifaddr and ifindex even though at most one of them
* will be non-zero; that way we always find the right one.
*/
static ilg_t *
{
if (ilg->ilg_condemned)
continue;
return (ilg);
}
return (NULL);
}
/*
* If a source address is passed in (src != NULL and src is not
* unspecified), remove the specified src addr from the given ilg's
* filter list, else delete the ilg.
*/
static void
{
/* ilg_inactive will unlink from the list */
} else {
}
}
/*
* Called from conn close. No new ilg can be added or removed
* because CONN_CLOSING has been set by ip_close. ilg_add / ilg_delete
* will return error if conn has started closing.
*
* We handle locking as follows.
* Under conn_ilg_lock we get the first ilg. As we drop the conn_ilg_lock to
* proceed with the ilm part of the delete we hold a reference on both the ill
* and the ilg. This doesn't prevent changes to the ilg, but prevents it from
* being deleted.
*
* Since the ilg_add code path uses two locks (conn_ilg_lock for the ilg part,
* and ill_mcast_lock for the ip_addmulti part) we can run at a point between
* the two. At that point ilg_ill is set, but ilg_ilm hasn't yet been set. In
* that case we delete the ilg here, which makes ilg_add discover that the ilg
* has disappeared when ip_addmulti returns, so it will discard the ilm it just
* added.
*/
void
{
/*
* Can not run if there is a conn_update_ill already running.
* Wait for it to complete. Caller should have already set CONN_CLOSING
* which prevents any new threads to run in conn_update_ill.
*/
if (ilg->ilg_condemned) {
continue;
}
/* If the ilg is detached then no need to serialize */
continue;
}
/*
* In order to serialize on the ill we try to enter
* and if that fails we unlock and relock and then
* check that we still have an ilm.
*/
if (ilg->ilg_condemned) {
goto next;
}
}
next:
/*
* Now that all locks have been dropped, we can send any
*/
if (need_refrele) {
/* Drop ill reference while we hold no locks */
}
}
}
/*
* Attach the ilg to an ilm on the ill. If it fails we leave ilg_ill as NULL so
* that a subsequent attempt can attach it. Drops and reacquires conn_ilg_lock.
*/
static void
{
int new_fmode;
int error = 0;
/*
* Alloc buffer to copy new state into (see below) before
* we make any changes, so we can bail if it fails.
*/
return;
/*
* Save copy of ilg's filter state to pass to other functions, so
* we can release conn_ilg_lock now.
* Set ilg_ill so that an unplumb can find us.
*/
/*
* Must look up the ilg again since we've not been holding
* conn_ilg_lock. The ilg could have disappeared due to an unplumb
* having called conn_update_ill, which can run once we dropped the
* conn_ilg_lock above. Alternatively, the ilg could have been attached
* when the lock was dropped
*/
(ilgstat == ILGSTAT_NEW));
}
return;
}
return;
}
}
/*
* Called when an ill is unplumbed to make sure that there are no
* dangling conn references to that ill. In that case ill is non-NULL and
* we make sure we remove all references to it.
* Also called when we should revisit the ilg_ill used for multicast
* memberships, in which case ill is NULL.
*
* conn is held by caller.
*
* Note that ipcl_walk only walks conns that are not yet condemned.
* condemned conns can't be refheld. For this reason, conn must become clean
* condemned flag.
*
* We leave ixa_multicast_ifindex in place. We prefer dropping
* packets instead of sending them out the wrong interface.
*
* We keep the ilg around in a detached state (with ilg_ill and ilg_ilm being
* NULL) so that the application can leave it later. Also, if ilg_ifaddr and
* ilg_ifindex are zero, indicating that the system should pick the interface,
* then we attempt to reselect the ill and join on it.
*
* Locking notes:
* Under conn_ilg_lock we get the first ilg. As we drop the conn_ilg_lock to
* proceed with the ilm part of the delete we hold a reference on both the ill
* and the ilg. This doesn't prevent changes to the ilg, but prevents it from
* being deleted.
*
* (SIOCSLIFINDEX, SIOCSLIFADDR) then we will attempt to attach any ilgs with
*/
static void
{
/*
* We have to prevent ip_close/ilg_delete_all from running at
* the same time. ip_close sets CONN_CLOSING before doing the ilg_delete
* all, and we set CONN_UPDATE_ILL. That ensures that only one of
* ilg_delete_all and conn_update_ill run at a time for a given conn.
* If ilg_delete_all got here first, then we have nothing to do.
*/
/* Caller has to wait for ill_ilm_cnt to drop to zero */
return;
}
/* Do we need to wake up a thread in ilg_delete_all? */
}
/* Detach from an ill that is going away */
static void
{
if (ilg->ilg_condemned)
continue;
continue;
/* Detach from current ill */
ip1dbg(("ilg_check_detach: detach %s on %s\n",
continue;
/* Prevent ilg from disappearing */
}
/*
* Now that all locks have been dropped, we can send any
*/
}
/*
* Check if there is a place to attach the conn_ilgs. We do this for both
* detached ilgs and attached ones, since for the latter there could be
* a better ill to attach them to. oill is non-null if we just detached from
* that ill.
*/
static void
{
int error;
if (ilg->ilg_condemned)
continue;
/* Check if the conn_ill matches what we would pick now */
/*
* Make sure the ill is usable for multicast and that
* we can send the DL_ADDMULTI_REQ before we create an
* ilm.
*/
/* Drop locks across ill_refrele */
/* Note that ilg could have become condemned */
}
/*
* Is the ill unchanged, even if both are NULL?
* Did we just detach from that ill?
*/
/* Drop locks across ill_refrele */
}
continue;
}
/* Something changed; detach from old first if needed */
/*
* In order to serialize on the ill we try to enter
* and if that fails we unlock and relock.
*/
/* Note that ilg could have become condemned */
}
/*
* Check that nobody else re-attached the ilg while we
* dropped the lock.
*/
/* Detach from current ill */
ip1dbg(("conn_check_reattach: detach %s/%s\n",
} else {
}
/*
* Now that all locks have been dropped, we can send any
*/
if (need_refrele) {
/* Drop ill reference while we hold no locks */
}
/*
* While we dropped conn_ilg_lock some other thread
* could have attached this ilg, thus we check again.
*/
/* Drop locks across ill_refrele */
}
continue;
}
}
/*
* In order to serialize on the ill we try to enter
* and if that fails we unlock and relock.
*/
/* Already have a refhold on ill */
/* Note that ilg could have become condemned */
}
/*
* Check that nobody else attached the ilg and that
* it wasn't condemned while we dropped the lock.
*/
/*
* Attach to the new ill. Can fail in which
* case ilg_ill will remain NULL. ilg_attach
* drops and reacquires conn_ilg_lock.
*/
ip1dbg(("conn_check_reattach: attach %s/%s\n",
}
/* Drop locks across ill_refrele */
/*
* Now that all locks have been
* dropped, we can send any
*/
}
}
}
/*
* Called when an ill is unplumbed to make sure that there are no
* dangling conn references to that ill. In that case ill is non-NULL and
* we make sure we remove all references to it.
* Also called when we should revisit the ilg_ill used for multicast
* memberships, in which case ill is NULL.
*/
void
{
}