igmp.c revision f5db8fb084e8d3d9f551ce34defa3c80d56edebc
/*
* 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. */
/*
* 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/ipsec_impl.h>
#include <inet/tunables.h>
#include <inet/ip_multi.h>
#include <inet/ip_listutils.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".
*/
/*
* A running timer (scheduled thru timeout) can be cancelled if another
* timer with a shorter timeout value is scheduled before it has timed
* out. When the shorter timer expires, the original timer is updated
* to account for the time elapsed while the shorter timer ran; but this
* does not take into account the amount of time already spent in timeout
* state before being preempted by the shorter timer, that is the time
* interval between time scheduled to time cancelled. This can cause
* delays in sending out multicast membership reports. To resolve this
* problem, wallclock time (absolute time) is used instead of deltas
* (relative time) to track timers.
*
* The MACRO below gets the lbolt value, used for proper timer scheduling
* and firing. Therefore multicast membership reports are sent on time.
* The timer does not exactly fire at the time it was scehduled to fire,
* there is a difference of a few milliseconds observed. An offset is used
* to take care of the difference.
*/
#define CURRENT_OFFSET (999)
/*
* 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 if we haven't
* been asked to quiesce.
*/
}
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, if we
* haven't been asked to quiesce.
*/
(void *)ipst,
}
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().
*/
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;
}
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\n",
}
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 our ill has ILMs that 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.
*/
continue;
} /* 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) ||
}
}
}
/*
* No packets have been sent above - no
* ill_mcast_send_queued is needed.
*/
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 */
}
}
/*
* No packets have been sent above - no
* ill_mcast_send_queued is needed.
*/
return (next);
}
/*
* Caller holds ill_mcast_lock. We queue the packet using ill_mcast_queue
* and it gets sent after the lock is dropped.
*/
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 */
/*
* We are holding ill_mcast_lock here and the timeout
* handler (igmp_timeout_handler_per_ill) acquires that
* lock. Hence we can't call igmp_start_timers since it could
* deadlock in untimeout().
* Instead the thread which drops ill_mcast_lock will have
* to call ill_mcast_timer_start().
*/
}
if (ip_debug > 1) {
"igmp_joingroup: multicast_type %d timer %d",
}
}
/*
* Caller holds ill_mcast_lock. We queue the packet using ill_mcast_queue
* and it gets sent after the lock is dropped.
*/
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 */
/*
* We are holding ill_mcast_lock here and the timeout
* handler (mld_timeout_handler_per_ill) acquires that
* lock. Hence we can't call mld_start_timers since it could
* deadlock in untimeout().
* Instead the thread which drops ill_mcast_lock will have
* to call ill_mcast_timer_start().
*/
}
if (ip_debug > 1) {
"mld_joingroup: multicast_type %d timer %d",
}
}
/*
* Caller holds ill_mcast_lock. We queue the packet using ill_mcast_queue
* and it gets sent after the lock is dropped.
*/
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;
}
}
/*
* Caller holds ill_mcast_lock. We queue the packet using ill_mcast_queue
* and it gets sent after the lock is dropped.
*/
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;
}
}
/*
* Caller holds ill_mcast_lock. We queue the packet using ill_mcast_queue
* and it gets sent after the lock is dropped.
*/
void
{
/* state change reports should only be sent if the router is v3 */
return;
/*
* 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, the caller will start it when dropping ill_mcast_lock.
*/
}
}
/*
* Caller holds ill_mcast_lock. We queue the packet using ill_mcast_queue
* and it gets sent after the lock is dropped.
*/
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, the caller will start it when dropping ill_mcast_lock.
*/
}
}
{
/* 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.
*/
}
} else {
}
goto per_ilm_rtxtimer;
if (ip_debug > 1) {
"igmp_timo_hlr 2: ilm_timr %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.
*/
}
}
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 {
}
}
/* Defer ill_mcast_timer_start() until the caller is done */
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_mcast_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 acquiring ill_mcast_lock.
*
* The igmp_slowtimeo() function is called thru another timer.
* igmp_slowtimeout_lock protects the igmp_slowtimeout_id
*/
void
igmp_timeout_handler(void *arg)
{
ipst->ips_igmp_timeout_id = 0;
ipst->ips_igmp_time_to_next = 0;
/* Make sure the ill isn't going away. */
if (!ill_check_and_refhold(ill))
continue;
if (next < global_next)
global_next = next;
}
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).
*/
{
/*
* 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 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 {
}
}
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 {
}
}
}
/* Defer ill_mcast_timer_start() until the caller is done */
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)
{
ipst->ips_mld_timeout_id = 0;
ipst->ips_mld_time_to_next = 0;
/* Make sure the ill isn't going away. */
if (!ill_check_and_refhold(ill))
continue;
if (next < global_next)
global_next = next;
}
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.
* Check for ips_igmp_max_version ensures that we don't revert to a higher
* IGMP version than configured.
*/
void
igmp_slowtimo(void *arg)
{
/*
* 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;
/* Make sure the ill isn't going away. */
if (!ill_check_and_refhold(ill))
continue;
ill->ill_mcast_v1_time++;
ill->ill_mcast_v2_time++;
if ((ill->ill_mcast_v2_tset > 0) ||
(ipst->ips_igmp_max_version ==
IGMP_V2_ROUTER)) {
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;
}
}
}
} else {
ipst->ips_igmp_slowtimeout_id = 0;
}
}
/*
* mld_slowtimo:
* - Resets to newer version if we didn't hear from the older version router
* in MLD_AGE_THRESHOLD seconds.
* - Restarts slowtimeout.
* Check for ips_mld_max_version ensures that we don't revert to a higher
* IGMP version than configured.
*/
void
mld_slowtimo(void *arg)
{
/* See comments in igmp_slowtimo() above... */
if (ifp->illif_mcast_v1 == 0)
continue;
/* Make sure the ill isn't going away. */
if (!ill_check_and_refhold(ill))
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;
}
}
}
} else {
ipst->ips_mld_slowtimeout_id = 0;
}
}
/*
* igmp_sendpkt:
* This will send to ip_output_simple just like icmp_inbound.
*/
static void
{
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;
}
/*
* Sends an IGMP_V3_MEMBERSHIP_REPORT message out the ill.
* The report will contain one group record
* for each element of reclist. If this causes packet length to
* exceed ill->ill_mc_mtu, 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));
/*
* Skip if there's not even enough room in
* a single packet to send something useful.
*/
continue;
/*
* 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++;
}
goto free_reclist;
}
rp = cur_reclist;
for (i = 0; i < numrec; i++) {
}
rtralert[2] = 0;
rtralert[3] = 0;
if (morepkts) {
if (more_src_cnt > 0) {
mvsize);
}
goto nextpkt;
}
}
}
/*
* mld_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 mld_input() may need to reinitialize variables that were copied
* from the mblk as this calls pullupmsg().
*/
mblk_t *
{
int mldlen;
/* Make sure the src address of the packet is link-local */
return (NULL);
}
return (NULL);
}
/* Get to the icmp header part */
/* An MLD packet must at least be 24 octets to be valid */
if (mldlen < MLD_MINLEN) {
return (NULL);
}
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 (NULL);
}
if (next == 0) {
return (mp);
}
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.
*/
if (ip_debug > 1) {
char buf1[INET6_ADDRSTRLEN];
1,
"mld_input: we are only "
"member src %s\n",
}
return (mp);
}
}
if (!IN6_IS_ADDR_MULTICAST(v6group_ptr)) {
return (NULL);
}
/*
* 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;
}
/*
* No packets have been sent above - no
* ill_mcast_send_queued is needed.
*/
break;
case MLD_LISTENER_REDUCTION:
break;
}
return (mp);
}
/*
* 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) {
}
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 (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;
}
}
/*
* No packets have been sent above - no
* ill_mcast_send_queued is needed.
*/
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;
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
/*
* Prepare for checksum by putting icmp length in the icmp
* checksum field. The checksum is calculated in ip_output.
*/
}
/*
* 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_mc_mtu,
* 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;
/*
* Skip if there's not even enough room in
* a single packet to send something useful.
*/
if (srcspace <= sizeof (in6_addr_t))
continue;
/*
* 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;
}
}
goto free_reclist;
/*
* 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_output_simple.
*/
mld2mar->mld2mar_auxlen = 0;
}
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);
}