igmp.c revision 91785ffff883655a89eb843ed89bcd24d717e320
/*
* 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 2007 Sun Microsystems, Inc. All rights reserved.
* Use is subject to license terms.
*/
/* Copyright (c) 1990 Mentat Inc. */
#pragma ident "%Z%%M% %I% %E% SMI"
/*
* Internet Group Management Protocol (IGMP) routines.
* Multicast Listener Discovery Protocol (MLD) routines.
*
* Written by Steve Deering, Stanford, May 1988.
* Modified by Rosen Sharma, Stanford, Aug 1994.
* Modified by Bill Fenner, Xerox PARC, Feb. 1995.
*
* MULTICAST 3.5.1.1
*/
#include <inet/ipclassifier.h>
#include <netinet/igmp_var.h>
#include <inet/ip_multi.h>
#include <inet/ip_listutils.h>
#include <inet/ipsec_info.h>
/*
* Macros used to do timer len conversions. Timer values are always
* stored and passed to the timer functions as milliseconds; but the
* default values and values from the wire may not be.
*
* And yes, it's obscure, but decisecond is easier to abbreviate than
* "tenths of a second".
*/
/*
* The first multicast join will trigger the igmp timers / mld timers
* The unit for next is milliseconds.
*/
void
{
int time_left;
int ret;
if (ipst->ips_igmp_timer_setter_active) {
/*
* Serialize timer setters, one at a time. If the
* timer is currently being set by someone,
* just record the next time when it has to be
* invoked and return. The current setter will
* take care.
*/
return;
} else {
}
if (ipst->ips_igmp_timeout_id == 0) {
/*
* The timer is inactive. We need to start a timer
*/
return;
}
/*
* The timer was scheduled sometime back for firing in
* 'igmp_time_to_next' ms and is active. We need to
* reschedule the timeout if the new 'next' will happen
* earlier than the currently scheduled timeout
*/
return;
}
/*
* The timeout was cancelled, or the timeout handler
* completed, while we were blocked in the untimeout.
* No other thread could have set the timer meanwhile
* since we serialized all the timer setters. Thus
* no timer is currently active nor executing nor will
* any timer fire in the future. We start the timer now
* if needed.
*/
if (ret == -1) {
} else {
ipst->ips_igmp_timeout_id = 0;
}
if (ipst->ips_igmp_time_to_next != 0) {
}
}
/*
* mld_start_timers:
* The unit for next is milliseconds.
*/
void
{
int time_left;
int ret;
if (ipst->ips_mld_timer_setter_active) {
/*
* Serialize timer setters, one at a time. If the
* timer is currently being set by someone,
* just record the next time when it has to be
* invoked and return. The current setter will
* take care.
*/
return;
} else {
}
if (ipst->ips_mld_timeout_id == 0) {
/*
* The timer is inactive. We need to start a timer
*/
return;
}
/*
* The timer was scheduled sometime back for firing in
* 'igmp_time_to_next' ms and is active. We need to
* reschedule the timeout if the new 'next' will happen
* earlier than the currently scheduled timeout
*/
return;
}
/*
* The timeout was cancelled, or the timeout handler
* completed, while we were blocked in the untimeout.
* No other thread could have set the timer meanwhile
* since we serialized all the timer setters. Thus
* no timer is currently active nor executing nor will
* any timer fire in the future. We start the timer now
* if needed.
*/
if (ret == -1) {
} else {
ipst->ips_mld_timeout_id = 0;
}
if (ipst->ips_mld_time_to_next != 0) {
}
}
/*
* igmp_input:
* Return NULL for a bad packet that is discarded here.
* Return mp if the message is OK and should be handed to "raw" receivers.
* Callers of igmp_input() may need to reinitialize variables that were copied
* from the mblk as this calls pullupmsg().
*/
/* ARGSUSED */
mblk_t *
{
goto bad_pkt;
}
/*
* Since msg sizes are more variable with v3, just pullup the
* whole thing now.
*/
goto bad_pkt;
}
}
/*
* Validate lengths
*/
if (igmplen < IGMP_MINLEN) {
goto bad_pkt;
}
/*
* Validate checksum
*/
goto bad_pkt;
}
if (ip_debug > 1)
"igmp_input: src 0x%x, dst 0x%x on %s\n",
switch (igmpa->igmpa_type) {
case IGMP_MEMBERSHIP_QUERY:
/*
*/
if (igmplen == IGMP_MINLEN) {
} else if (igmplen >= IGMP_V3_QUERY_MINLEN) {
igmplen);
} else {
goto bad_pkt;
}
if (next == 0)
goto bad_pkt;
break;
/*
* For fast leave to work, we have to know that we are the
* last person to send a report for this group. Reports
* generated by us are looped back since we could potentially
* be a multicast router, so discard reports sourced by me.
*/
if (ip_debug > 1) {
1,
"igmp_input: we are only "
"member src 0x%x ipif_local 0x%x",
(int)
}
return (mp);
}
}
goto bad_pkt;
}
/*
* KLUDGE: if the IP source address of the report has an
* unspecified (i.e., zero) subnet number, as is allowed for
* a booting host, replace it with the correct subnet number
* so that a process-level multicast routing demon can
* determine which subnet it arrived from. This is necessary
* to compensate for the lack of any way for a process to
* determine the arrival interface of an incoming packet.
*
* Requires that a copy of *this* message it passed up
* to the raw interface which is done by our caller.
*/
/* Pick the first ipif on this ill */
ip1dbg(("igmp_input: changed src to 0x%x\n",
}
/*
* If we belong to the group being reported, and
* we are a 'Delaying member' in the RFC terminology,
* stop our timer for that group and 'clear flag' i.e.
* mark as IGMP_OTHERMEMBER. Do this for all logical
* interfaces on the given physical interface.
*/
}
} /* for */
break;
/*
* Currently nothing to do here; IGMP router is not
* implemented in ip, and v3 hosts don't pay attention
* to membership reports.
*/
break;
}
/*
* Pass all valid IGMP packets up to any process(es) listening
* on a raw IGMP socket. Do not free the packet.
*/
return (mp);
return (NULL);
}
static uint_t
{
int timer;
/*
* In the IGMPv2 specification, there are 3 states and a flag.
*
* In Non-Member state, we simply don't have a membership record.
* In Delaying Member state, our timer is running (ilm->ilm_timer
* < INFINITY). In Idle Member state, our timer is not running
* (ilm->ilm_timer == INFINITY).
*
* The flag is ilm->ilm_state, it is set to IGMP_OTHERMEMBER if
* we have heard a report from another member, or IGMP_IREPORTEDLAST
* if I sent the last report.
*/
if (igmpa->igmpa_code == 0) {
/*
* Query from an old router.
* Remember that the querier on this interface is old,
* and set the timer to the value in RFC 1112.
*/
ill->ill_mcast_v1_time = 0;
ip1dbg(("Received IGMPv1 Query on %s, switching mode "
}
igmpa->igmpa_group != 0) {
return (0);
}
} else {
/*
* Query from a new router
* Simply do a validity check
*/
return (0);
}
/*
* Switch interface state to v2 on receipt of a v2 query
* ONLY IF current state is v3. Let things be if current
* state if v1 but do reset the v2-querier-present timer.
*/
ip1dbg(("Received IGMPv2 Query on %s, switching mode "
}
ill->ill_mcast_v2_time = 0;
}
if (ip_debug > 1) {
"igmp_input: TIMER = igmp_code %d igmp_type 0x%x",
}
/*
* -Start the timers in all of our membership records
* for the physical interface on which the query
* arrived, excluding those that belong to the "all
* hosts" group (224.0.0.1).
*
* -Restart any timer that is already running but has
* a value longer than the requested timeout.
*
* -Use the value specified in the query message as
* the maximum timeout.
*/
/*
* A multicast router joins INADDR_ANY address
* to enable promiscuous reception of all
* mcasts from the interface. This INADDR_ANY
* is stored in the ilm_v6addr as V6 unspec addr
*/
continue;
continue;
(igmpa->igmpa_group == 0) ||
}
}
}
return (next);
}
static uint_t
{
/* make sure numsrc matches packet size */
return (0);
}
}
if (mrd == 0)
else
}
/*
* If we have a pending general query response that's scheduled
* sooner than the delay we calculated for this response, then
* no action is required (RFC3376 section 5.2 rule 1)
*/
return (next);
}
/*
* Now take action depending upon query type:
*/
/*
* general query
* We know global timer is either not running or is
* greater than our calculated delay, so reset it to
* our delay (random value in range [0, response time]).
*/
} else {
continue;
/*
* If the query is group specific or we have a
* pending group specific query, the response is
* group specific (pending sources list should be
* empty). Otherwise, need to update the pending
* sources list for the group and source specific
* response.
*/
} else {
if (numsrc > MAX_FILTER_SIZE ||
/*
* We've been sent more sources than
* we can deal with; or we can't deal
* with a source list at all. Revert
* to a group specific query.
*/
goto group_query;
}
goto group_query;
for (i = 0; i < numsrc; i++)
&overflow);
if (overflow)
goto group_query;
}
/* choose soonest timer */
}
}
return (next);
}
void
{
} else {
ip1dbg(("Querier mode %d, sending report, group %x\n",
/*
* The possible state changes we need to handle here:
* Old State New State Report
*
* INCLUDE(0) INCLUDE(X) ALLOW(X),BLOCK(0)
* INCLUDE(0) EXCLUDE(X) TO_EX(X)
*
* No need to send the BLOCK(0) report; ALLOW(X)
* is enough.
*/
/*
* Set up retransmission state. Timer is set below,
* for both v3 and older versions.
*/
ilm->ilm_filter);
}
/* Set the ilm timer value */
/*
* To avoid deadlock, we don't call igmp_start_timers from
* here. igmp_start_timers needs to call untimeout, and we
* can't hold the ipsq across untimeout since
* igmp_timeout_handler could be blocking trying to
* acquire the ipsq. Instead we start the timer after we get
* out of the ipsq in ipsq_exit.
*/
}
if (ip_debug > 1) {
"igmp_joingroup: multicast_type %d timer %d",
}
}
void
{
} else {
} else {
/*
* The possible state changes we need to handle here:
* Old State New State Report
*
* INCLUDE(0) INCLUDE(X) ALLOW(X),BLOCK(0)
* INCLUDE(0) EXCLUDE(X) TO_EX(X)
*
* No need to send the BLOCK(0) report; ALLOW(X)
* is enough
*/
/*
* Set up retransmission state. Timer is set below,
* for both v2 and v1.
*/
ilm->ilm_filter);
}
/* Set the ilm timer value */
/*
* To avoid deadlock, we don't call mld_start_timers from
* here. mld_start_timers needs to call untimeout, and we
* can't hold the ipsq (i.e. the lock) across untimeout
* since mld_timeout_handler could be blocking trying to
* acquire the ipsq. Instead we start the timer after we get
* out of the ipsq in ipsq_exit
*/
}
if (ip_debug > 1) {
"mld_joingroup: multicast_type %d timer %d",
}
}
void
{
return;
/*
* The possible state changes we need to handle here:
* Old State New State Report
*
* INCLUDE(X) INCLUDE(0) ALLOW(0),BLOCK(X)
* EXCLUDE(X) INCLUDE(0) TO_IN(0)
*
* No need to send the ALLOW(0) report; BLOCK(X) is enough
*/
} else {
}
return;
}
}
void
{
return;
/*
* The possible state changes we need to handle here:
* Old State New State Report
*
* INCLUDE(X) INCLUDE(0) ALLOW(0),BLOCK(X)
* EXCLUDE(X) INCLUDE(0) TO_IN(0)
*
* No need to send the ALLOW(0) report; BLOCK(X) is enough
*/
} else {
}
return;
}
}
void
{
/* state change reports should only be sent if the router is v3 */
return;
} else {
}
/*
* Compare existing(old) state with the new state and prepare
* State Change Report, according to the rules in RFC 3376:
*
* Old State New State State Change Report
*
* INCLUDE(A) INCLUDE(B) ALLOW(B-A),BLOCK(A-B)
* EXCLUDE(A) EXCLUDE(B) ALLOW(A-B),BLOCK(B-A)
* INCLUDE(A) EXCLUDE(B) TO_EX(B)
* EXCLUDE(A) INCLUDE(B) TO_IN(B)
*/
goto send_to_ex;
else
goto send_to_in;
}
} else {
}
if (!SLIST_IS_EMPTY(allow))
if (!SLIST_IS_EMPTY(block))
NULL);
} else {
NULL);
}
/*
* Need to set up retransmission state; merge the new info with the
* current state (which may be null). If the timer is not currently
* running, start it (need to do a delayed start of the timer as
* we're currently in the sq).
*/
}
}
void
{
/* only need to send if we have an mldv2-capable router */
return;
}
/*
* Compare existing (old) state with the new state passed in
* and send appropriate MLDv2 State Change Report.
*
* Old State New State State Change Report
*
* INCLUDE(A) INCLUDE(B) ALLOW(B-A),BLOCK(A-B)
* EXCLUDE(A) EXCLUDE(B) ALLOW(A-B),BLOCK(B-A)
* INCLUDE(A) EXCLUDE(B) TO_EX(B)
* EXCLUDE(A) INCLUDE(B) TO_IN(B)
*/
goto send_to_ex;
else
goto send_to_in;
}
} else {
}
if (!SLIST_IS_EMPTY(allow))
if (!SLIST_IS_EMPTY(block))
NULL);
} else {
NULL);
}
/*
* Need to set up retransmission state; merge the new info with the
* current state (which may be null). If the timer is not currently
* running, start it (need to do a deferred start of the timer as
* we're currently in the sq).
*/
}
}
{
/* First check the global timer on this interface */
goto per_ilm_timer;
/*
* Send report for each group on this interface.
* Since we just set the global timer (received a v3 general
* query), need to skip the all hosts addr (224.0.0.1), per
* RFC 3376 section 5.
*/
continue;
/*
* Since we're sending a report on this group, okay
* to delete pending group-specific timers. Note
* that group-specific retransmit timers still need
* to be checked in the per_ilm_timer for-loop.
*/
}
/*
* We've built per-ipif mrec lists; walk the ill's ipif list
* and send a report for each ipif that has an mrec list.
*/
continue;
/* mrec list was freed by igmpv3_sendrpt() */
}
} else {
}
goto per_ilm_rtxtimer;
if (ip_debug > 1) {
"igmp_timo_hlr 2: ilm_timr %d elap %d "
"typ %d nxt %d",
}
goto per_ilm_rtxtimer;
}
/* the timer has expired, need to take action */
} else {
/*
* Contents of reply depend on pending
* requested source list.
*/
} else {
}
if (!SLIST_IS_EMPTY(rsp))
} else {
/*
* Either the pending request is just group-
* specific, or we couldn't get the resources
* (rsp) to build a source-specific reply.
*/
}
}
if (ip_debug > 1) {
"igmp_timo_hlr 1: ilm_timr %d elap %d "
"typ %d nxt %d",
}
continue;
continue;
}
continue;
continue;
}
/*
* The retransmit timer has popped, and our router is
* IGMPv3. We have to delve into the retransmit state
* stored in the ilm.
*
* Decrement the retransmit count. If the fmode rtx
* count is active, decrement it, and send a filter
* mode change report with the ilm's source list.
* Otherwise, send a source list change report with
* the current retransmit lists.
*/
if (rtxp->rtx_fmode_cnt > 0) {
rtxp->rtx_fmode_cnt--;
} else {
}
} else {
}
}
return (next);
}
/*
* igmp_timeout_handler:
* Called when there are timeout events, every next * TMEOUT_INTERVAL (tick).
* Returns number of ticks to next event (or 0 if none).
*
* As part of multicast join and leave igmp we may need to send out an
* igmp request. The igmp related state variables in the ilm are protected
* by ill_lock. A single global igmp timer is used to track igmp timeouts.
* igmp_timer_lock protects the global igmp_timeout_id. igmp_start_timers
* starts the igmp timer if needed. It serializes multiple threads trying to
* simultaneously start the timer using the igmp_timer_setter_active flag.
*
* igmp_input() receives igmp queries and responds to the queries
* in a delayed fashion by posting a timer i.e. it calls igmp_start_timers().
* Later the igmp_timer fires, the timeout handler igmp_timerout_handler()
* performs the action exclusively after entering each ill's ipsq as writer.
* The actual igmp timeout handler needs to run in the ipsq since it has to
* access the ilm's and we don't want another exclusive operation like
* say an IPMP failover to be simultaneously moving the ilms from one ill to
* another.
*
* The igmp_slowtimeo() function is called thru another timer.
* igmp_slowtimeout_lock protects the igmp_slowtimeout_id
*/
void
igmp_timeout_handler(void *arg)
{
int elapsed; /* Since last call */
ipst->ips_igmp_time_to_next = 0;
/*
* is changing. But we need to make sure that the ill will
* not vanish. So we just bump up the ill_waiter count.
*/
if (!ill_waiter_inc(ill))
continue;
if (success) {
if (next < global_next)
global_next = next;
B_TRUE);
}
}
ipst->ips_igmp_timeout_id = 0;
if (global_next != INFINITY)
}
/*
* mld_timeout_handler:
* Called when there are timeout events, every next (tick).
* Returns number of ticks to next event (or 0 if none).
*/
/* ARGSUSED */
{
/*
* First check the global timer on this interface; the global timer
* is not used for MLDv1, so if it's set we can assume we're v2.
*/
goto per_ilm_timer;
/*
* Send report for each group on this interface.
* Since we just set the global timer (received a v2 general
* query), need to skip the all hosts addr (ff02::1), per
* RFC 3810 section 6.
*/
continue;
/*
* Since we're sending a report on this group, okay
* to delete pending group-specific timers. Note
* that group-specific retransmit timers still need
* to be checked in the per_ilm_timer for-loop.
*/
}
} else {
}
goto per_ilm_rtxtimer;
if (ip_debug > 1) {
"igmp_timo_hlr 2: ilm_timr"
" %d elap %d typ %d nxt %d",
}
goto per_ilm_rtxtimer;
}
/* the timer has expired, need to take action */
} else {
/*
* Contents of reply depend on pending
* requested source list.
*/
} else {
}
if (!SLIST_IS_EMPTY(rsp))
} else {
}
}
if (ip_debug > 1) {
"igmp_timo_hlr 1: ilm_timr %d elap %d "
"typ %d nxt %d",
}
continue;
continue;
}
continue;
}
/*
* The retransmit timer has popped, and our router is
* MLDv2. We have to delve into the retransmit state
* stored in the ilm.
*
* Decrement the retransmit count. If the fmode rtx
* count is active, decrement it, and send a filter
* mode change report with the ilm's source list.
* Otherwise, send a source list change report with
* the current retransmit lists.
*/
if (rtxp->rtx_fmode_cnt > 0) {
rtxp->rtx_fmode_cnt--;
} else {
}
} else {
}
}
return (next);
}
return (next);
}
/*
* mld_timeout_handler:
* Called when there are timeout events, every next * TMEOUT_INTERVAL (tick).
* Returns number of ticks to next event (or 0 if none).
* MT issues are same as igmp_timeout_handler
*/
void
mld_timeout_handler(void *arg)
{
int elapsed; /* Since last call */
ipst->ips_mld_time_to_next = 0;
/*
* is changing. But we need to make sure that the ill will
* not vanish. So we just bump up the ill_waiter count.
*/
if (!ill_waiter_inc(ill))
continue;
if (success) {
if (next < global_next)
global_next = next;
B_FALSE);
}
}
ipst->ips_mld_timeout_id = 0;
if (global_next != INFINITY)
}
/*
* Calculate the Older Version Querier Present timeout value, in number
* of slowtimo intervals, for the given ill.
*/
/*
* igmp_slowtimo:
* - Resets to new router if we didnt we hear from the router
* in IGMP_AGE_THRESHOLD seconds.
* - Resets slowtimeout.
*/
void
igmp_slowtimo(void *arg)
{
/* Hold the ill_g_lock so that we can safely walk the ill list */
/*
* The ill_if_t list is circular, hence the odd loop parameters.
*
* We can't use the ILL_START_WALK and ill_next() wrappers for this
* walk, as we need to check the illif_mcast_* fields in the ill_if_t
* structure (allowing us to skip if none of the instances have timers
* running).
*/
/*
* illif_mcast_v[12] are set using atomics. If an ill hears
* a V1 or V2 query now and we miss seeing the count now,
* we will see it the next time igmp_slowtimo is called.
*/
continue;
ill->ill_mcast_v1_time++;
ill->ill_mcast_v2_time++;
if (ill->ill_mcast_v2_tset > 0) {
ip1dbg(("V1 query timer "
"expired on %s; switching "
"mode to IGMP_V2\n",
} else {
ip1dbg(("V1 query timer "
"expired on %s; switching "
"mode to IGMP_V3\n",
}
ill->ill_mcast_v1_time = 0;
ill->ill_mcast_v1_tset = 0;
}
}
ip1dbg(("V2 query timer expired on "
"%s; switching mode to IGMP_V3\n",
ill->ill_mcast_v2_time = 0;
ill->ill_mcast_v2_tset = 0;
}
}
}
}
}
/*
* mld_slowtimo:
* - Resets to newer version if we didn't hear from the older version router
* in MLD_AGE_THRESHOLD seconds.
* - Restarts slowtimeout.
*/
/* ARGSUSED */
void
mld_slowtimo(void *arg)
{
/* See comments in igmp_slowtimo() above... */
if (ifp->illif_mcast_v1 == 0)
continue;
ill->ill_mcast_v1_time++;
ip1dbg(("MLD query timer expired on"
" %s; switching mode to MLD_V2\n",
ill->ill_mcast_v1_time = 0;
ill->ill_mcast_v1_tset = 0;
}
}
}
}
}
/*
* igmp_sendpkt:
* This will send to ip_wput like icmp_inbound.
* Note that the lower ill (on which the membership is kept) is used
* as an upper ill to pass in the multicast parameters.
*/
static void
{
/*
* We need to make sure this packet goes out on an ipif. If
* there is some global policy match in ip_wput_ire, we need
* to get to the right interface after IPSEC processing.
* To make sure this multicast packet goes out on the right
* interface, we attach an ipsec_out and initialize ill_index
* like we did in ip_wput. To make sure that this packet does
* not get forwarded on other interfaces or looped back, we
* set ipsec_out_dontroute to B_TRUE and ipsec_out_multicast_loop
* to B_FALSE.
*
* We also need to make sure that this does not get load balanced
* if it hits ip_newroute_ipif. So, we initialize ipsec_out_attach_if
* here. If it gets load balanced, switches supporting igmp snooping
* will send the packet that it receives for this multicast group
* to the interface that we are sending on. As we have joined the
* multicast group on this ill, by sending the packet out on this
* ill, we receive all the packets back on this ill.
*/
return;
/* ipsec_out_secure is B_FALSE now */
return;
}
igmpa->igmpa_code = 0;
igmpa->igmpa_cksum = 0;
rtralert[2] = 0;
rtralert[3] = 0;
ipha->ipha_type_of_service = 0;
ipha->ipha_ident = 0;
ipha->ipha_hdr_checksum = 0;
/*
* Request loopback of the report if we are acting as a multicast
* router, so that the process-level routing demon can hear it.
*/
/*
* This will run multiple times for the same group if there are members
* on the same group for multiple ipif's on the same ill. The
* igmp_input code will suppress this due to the loopback thus we
* always loopback membership report.
*/
}
/*
* Sends an IGMP_V3_MEMBERSHIP_REPORT message out the ill associated
* with the passed-in ipif. The report will contain one group record
* for each element of reclist. If this causes packet length to
* exceed ipif->ipif_ill->ill_max_frag, multiple reports are sent.
* reclist is assumed to be made up of buffers allocated by mcast_bldmrec(),
* and those buffers are freed here.
*/
static void
{
int i, j, numrec, more_src_cnt;
/* if there aren't any records, there's nothing to send */
return;
more_src_cnt = 0;
numrec = 0;
if (rp == cur_reclist) {
/*
* If the first mrec we looked at is too big
* to fit in a single packet (i.e the source
* list is too big), we must either truncate
* the list (if TO_EX or IS_EX), or send
* multiple reports for the same group (all
* other types).
*/
int srcspace, srcsperpkt;
sizeof (grphdra_t));
/*
* Increment size and numrec, because we will
* be sending a record for the mrec we're
* looking at now.
*/
(srcsperpkt * sizeof (ipaddr_t));
numrec++;
/* no more packets to send */
break;
} else {
/*
* more packets, but we're
* done with this mrec.
*/
}
} else {
- srcsperpkt;
/*
* We'll fix up this mrec (remove the
* srcs we've already sent) before
* returning to nextpkt above.
*/
next_reclist = rp;
}
} else {
next_reclist = rp;
}
break;
}
numrec++;
}
/*
* See comments in igmp_sendpkt() about initializing for ipsec and
* load balancing requirements.
*/
goto free_reclist;
/* ipsec_out_secure is B_FALSE now */
goto free_reclist;
}
rp = cur_reclist;
for (i = 0; i < numrec; i++) {
}
rtralert[2] = 0;
rtralert[3] = 0;
/*
* Request loopback of the report if we are acting as a multicast
* router, so that the process-level routing daemon can hear it.
*
* This will run multiple times for the same group if there are
* members on the same group for multiple ipifs on the same ill.
* The igmp_input code will suppress this due to the loopback;
* thus we always loopback membership report.
*/
if (morepkts) {
if (more_src_cnt > 0) {
mvsize);
}
goto nextpkt;
}
}
}
/*
* mld_input:
*/
/* ARGSUSED */
void
{
int mldlen;
/* Make sure the src address of the packet is link-local */
return;
}
return;
}
/* Get to the icmp header part */
} else {
exthdr_length = 0;
}
/* An MLD packet must at least be 24 octets to be valid */
if (mldlen < MLD_MINLEN) {
return;
}
case MLD_LISTENER_QUERY:
/*
* packet length differentiates between v1 and v2. v1
* query should be exactly 24 octets long; v2 is >= 28.
*/
if (mldlen == MLD_MINLEN) {
} else if (mldlen >= MLD_V2_QUERY_MINLEN) {
} else {
return;
}
if (next == 0) {
return;
}
break;
case MLD_LISTENER_REPORT: {
/*
* For fast leave to work, we have to know that we are the
* last person to send a report for this group. Reports
* generated by us are looped back since we could potentially
* be a multicast router, so discard reports sourced by me.
*/
lcladdr_ptr)) {
if (ip_debug > 1) {
char buf1[INET6_ADDRSTRLEN];
char buf2[INET6_ADDRSTRLEN];
1,
"mld_input: we are only "
"member src %s ipif_local %s",
}
return;
}
}
if (!IN6_IS_ADDR_MULTICAST(v6group_ptr)) {
return;
}
/*
* If we belong to the group being reported, and we are a
* 'Delaying member' per the RFC terminology, stop our timer
* for that group and 'clear flag' i.e. mark ilm_state as
* IGMP_OTHERMEMBER. With zones, there can be multiple group
* membership entries for the same group address (one per zone)
* so we need to walk the ill_ilm list.
*/
continue;
}
break;
}
case MLD_LISTENER_REDUCTION:
break;
}
/*
* All MLD packets have already been passed up to any
* process(es) listening on a ICMP6 raw socket. This
* has been accomplished in ip_deliver_local_v6 prior to
* this function call. It is assumed that the multicast daemon
* will have a SOCK_RAW IPPROTO_ICMPV6 (and presumbly use the
* ICMP6_FILTER socket option to only receive the MLD messages)
* Thus we can free the MLD message block here
*/
}
/*
* Handles an MLDv1 Listener Query. Returns 0 on error, or the appropriate
* (non-zero, unsigned) timer value to be set on success.
*/
static uint_t
{
int timer;
/*
* In the MLD specification, there are 3 states and a flag.
*
* In Non-Listener state, we simply don't have a membership record.
* In Delaying state, our timer is running (ilm->ilm_timer < INFINITY)
* In Idle Member state, our timer is not running (ilm->ilm_timer ==
* INFINITY)
*
* The flag is ilm->ilm_state, it is set to IGMP_OTHERMEMBER if
* we have heard a report from another member, or IGMP_IREPORTEDLAST
* if I sent the last report.
*/
if (!(IN6_IS_ADDR_UNSPECIFIED(v6group)) &&
((!IN6_IS_ADDR_MULTICAST(v6group)))) {
return (0);
}
/* Need to do compatibility mode checking */
ill->ill_mcast_v1_time = 0;
ip1dbg(("Received MLDv1 Query on %s, switching mode to "
}
if (ip_debug > 1) {
"mld_input: TIMER = mld_maxdelay %d mld_type 0x%x",
}
/*
* -Start the timers in all of our membership records for
* the physical interface on which the query arrived,
* excl:
* 1. those that belong to the "all hosts" group,
* 2. those with 0 scope, or 1 node-local scope.
*
* -Restart any timer that is already running but has a value
* longer that the requested timeout.
* -Use the value specified in the query message as the
* maximum timeout.
*/
continue;
&ipv6_all_hosts_mcast)) &&
if (timer == 0) {
/* Respond immediately */
break;
}
}
break;
}
}
return (next);
}
/*
* Handles an MLDv2 Listener Query. On error, returns 0; on success,
* returns the appropriate (non-zero, unsigned) timer value (which may
* be INFINITY) to be set.
*/
static uint_t
{
/* make sure numsrc matches packet size */
return (0);
}
/* extract Maximum Response Delay from code in header */
if (mrd >= MLD_V2_MAXRT_FPMIN) {
}
else
}
/*
* If we have a pending general query response that's scheduled
* sooner than the delay we calculated for this response, then
* no action is required (MLDv2 draft section 6.2 rule 1)
*/
return (next);
}
/*
* Now take action depending on query type: general,
*/
/*
* general query
* We know global timer is either not running or is
* greater than our calculated delay, so reset it to
* our delay (random value in range [0, response time])
*/
} else {
continue;
/*
* If the query is group specific or we have a
* pending group specific query, the response is
* group specific (pending sources list should be
* empty). Otherwise, need to update the pending
* sources list for the group and source specific
* response.
*/
} else {
if (numsrc > MAX_FILTER_SIZE ||
/*
* We've been sent more sources than
* we can deal with; or we can't deal
* with a source list at all. Revert
* to a group specific query.
*/
goto group_query;
}
goto group_query;
for (i = 0; i < numsrc; i++)
&overflow);
if (overflow)
goto group_query;
}
/* set timer to soonest value */
break;
}
}
return (next);
}
/*
* Send MLDv1 response packet with hoplimit 1
*/
static void
{
struct ip6_opt_router *ip6router;
/*
* We need to place a router alert option in this packet. The length
* of the options must be a multiple of 8. The hbh option header is 2
* bytes followed by the 4 byte router alert option. That leaves
* 2 bytes of pad for a total of 8 bytes.
*/
const int router_alert_length = 8;
/*
* We need to make sure that this packet does not get load balanced.
* So, we allocate an ip6i_t and set ATTACH_IF. ip_wput_v6 and
* ip_newroute_ipif_v6 knows how to handle such packets.
* If it gets load balanced, switches supporting MLD snooping
* (in the future) will send the packet that it receives for this
* multicast group to the interface that we are sending on. As we have
* joined the multicast group on this ill, by sending the packet out
* on this ill, we receive all the packets back on this ill.
*/
return;
/*
* A zero is a pad option of length 1. The bzero of the whole packet
* above will pad between ip6router and mld.
*/
ip6router->ip6or_value[0] = 0;
else
/* ipif returned by ipif_lookup_zoneid is link-local (if present) */
} else {
/* Otherwise, use IPv6 default address selection. */
}
/*
* Prepare for checksum by putting icmp length in the icmp
* checksum field. The checksum is calculated in ip_wput_v6.
*/
/*
* ip_wput will automatically loopback the multicast packet to
* the conn if multicast loopback is enabled.
* The MIB stats corresponding to this outgoing MLD packet
* will be accounted for in ip_wput->ip_wput_v6->ip_wput_ire_v6
* ->icmp_update_out_mib_v6 function call.
*/
}
/*
* Sends an MLD_V2_LISTENER_REPORT message out the passed-in ill. The
* report will contain one multicast address record for each element of
* reclist. If this causes packet length to exceed ill->ill_max_frag,
* multiple reports are sent. reclist is assumed to be made up of
* buffers allocated by mcast_bldmrec(), and those buffers are freed here.
*/
static void
{
struct ip6_opt_router *ip6router;
int i, numrec, more_src_cnt;
/* If there aren't any records, there's nothing to send */
return;
/*
* Total option length (optlen + padlen) must be a multiple of
* 8 bytes. We assume here that optlen <= 8, so the total option
* length will be 8. Assert this in case anything ever changes.
*/
more_src_cnt = 0;
if (rp == cur_reclist) {
/*
* If the first mrec we looked at is too big
* to fit in a single packet (i.e the source
* list is too big), we must either truncate
* the list (if TO_EX or IS_EX), or send
* multiple reports for the same group (all
* other types).
*/
int srcspace, srcsperpkt;
/*
* Increment icmpsize and size, because we will
* be sending a record for the mrec we're
* looking at now.
*/
(srcsperpkt * sizeof (in6_addr_t));
/* no more packets to send */
break;
} else {
/*
* more packets, but we're
* done with this mrec.
*/
}
} else {
- srcsperpkt;
/*
* We'll fix up this mrec (remove the
* srcs we've already sent) before
* returning to nextpkt above.
*/
next_reclist = rp;
}
} else {
next_reclist = rp;
}
break;
}
}
/*
* We need to make sure that this packet does not get load balanced.
* So, we allocate an ip6i_t and set ATTACH_IF. ip_wput_v6 and
* ip_newroute_ipif_v6 know how to handle such packets.
* If it gets load balanced, switches supporting MLD snooping
* (in the future) will send the packet that it receives for this
* multicast group to the interface that we are sending on. As we have
* joined the multicast group on this ill, by sending the packet out
* on this ill, we receive all the packets back on this ill.
*/
goto free_reclist;
/* ipif returned by ipif_lookup_zoneid is link-local (if present) */
} else {
/* otherwise, use IPv6 default address selection. */
}
/*
* ip6h_len is the number of 8-byte words, not including the first
* 8 bytes; we've assumed optlen + padlen == 8 bytes; hence len = 0.
*/
ip6router->ip6or_value[0] = 0;
/*
* Prepare for the checksum by putting icmp length in the icmp
* checksum field. The checksum is calculated in ip_wput_v6.
*/
mld2mar->mld2mar_auxlen = 0;
}
/*
* ip_wput will automatically loopback the multicast packet to
* the conn if multicast loopback is enabled.
* The MIB stats corresponding to this outgoing MLD packet
* will be accounted for in ip_wput->ip_wput_v6->ip_wput_ire_v6
* ->icmp_update_out_mib_v6 function call.
*/
if (morepkts) {
if (more_src_cnt > 0) {
mvsize);
}
goto nextpkt;
}
}
}
static mrec_t *
{
int i;
return (next);
return (next);
rp->mrec_auxlen = 0;
} else {
}
return (rp);
}
/*
* Set up initial retransmit state. If memory cannot be allocated for
* the source lists, simply create as much state as is possible; memory
* allocation failures are considered one type of transient error that
* the retransmissions are designed to overcome (and if they aren't
* transient, there are bigger problems than failing to notify the
* router about multicast group membership state changes).
*/
static void
{
/*
* There are only three possibilities for rtype:
* New join, transition from INCLUDE {} to INCLUDE {flist}
* => rtype is ALLOW_NEW_SOURCES
* New join, transition from INCLUDE {} to EXCLUDE {flist}
* => rtype is CHANGE_TO_EXCLUDE
* State change that involves a filter mode change
* => rtype is either CHANGE_TO_INCLUDE or CHANGE_TO_EXCLUDE
*/
rtype == ALLOW_NEW_SOURCES);
switch (rtype) {
case CHANGE_TO_EXCLUDE:
break;
case ALLOW_NEW_SOURCES:
case CHANGE_TO_INCLUDE:
break;
}
}
/*
* The basic strategy here, as extrapolated from RFC 3810 section 6.1 and
* RFC 3376 section 5.1, covers three cases:
* * The current state change is a filter mode change
* Set filter mode retransmit counter; set retransmit allow or
* block list to new source list as appropriate, and clear the
* retransmit list that was not set; send TO_IN or TO_EX with
* new source list.
* * The current state change is a source list change, but the filter
* mode retransmit counter is > 0
* Decrement filter mode retransmit counter; set retransmit
* allow or block list to new source list as appropriate,
* and clear the retransmit list that was not set; send TO_IN
* or TO_EX with new source list.
* * The current state change is a source list change, and the filter
* mode retransmit counter is 0.
* Merge existing rtx allow and block lists with new state:
* rtx_allow = (new allow + rtx_allow) - new block
* rtx_block = (new block + rtx_block) - new allow
* Send ALLOW and BLOCK records for new retransmit lists;
* decrement retransmit counter.
*
* As is the case for mcast_init_rtx(), memory allocation failures are
* acceptable; we just create as much state as we can.
*/
static mrec_t *
{
return (mreclist);
/*
* A filter mode change is indicated by a single mrec, which is
* either TO_IN or TO_EX. In this case, we just need to set new
* retransmit state as if this were an initial join. There is
* no change to the mrec list.
*/
return (mreclist);
}
/*
* Only the source list has changed
*/
if (rtxp->rtx_fmode_cnt > 0) {
/* but we're still sending filter mode change reports */
rtxp->rtx_fmode_cnt--;
} else {
}
/* overwrite first mrec with new info */
/* then free any remaining mrecs */
}
} else {
/*
* Just send the source change reports; but we need to
* recalculate the ALLOW and BLOCK lists based on previous
* state and new changes.
*/
allow_mrec = rp;
else
block_mrec = rp;
}
/*
* Perform calculations:
* new_allow = mrec_allow + (rtx_allow - mrec_block)
* new_block = mrec_block + (rtx_block - mrec_allow)
*
* Each calc requires two steps, for example:
* rtx_allow = rtx_allow - mrec_block;
* new_allow = mrec_allow + rtx_allow;
*
* Store results in mrec lists, and then copy into rtx lists.
* We do it in this order in case the rtx list hasn't been
* alloc'd yet; if it hasn't and our alloc fails, that's okay,
* Overflows are also okay.
*/
if (block_mrec != NULL) {
&block_mrec->mrec_srcs);
}
if (allow_mrec != NULL) {
&allow_mrec->mrec_srcs);
&ovf);
}
if (block_mrec != NULL) {
&ovf);
} else {
}
if (allow_mrec != NULL) {
} else {
}
}
return (rtnmrec);
}