| /* |
| * 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 |
| * or http://www.opensolaris.org/os/licensing. |
| * 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 2006 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 <sys/types.h> |
| #include <sys/stream.h> |
| #include <sys/stropts.h> |
| #include <sys/strlog.h> |
| #include <sys/strsun.h> |
| #include <sys/systm.h> |
| #include <sys/ddi.h> |
| #include <sys/sunddi.h> |
| #include <sys/cmn_err.h> |
| #include <sys/atomic.h> |
| #include <sys/zone.h> |
| |
| #include <sys/param.h> |
| #include <sys/socket.h> |
| #include <inet/ipclassifier.h> |
| #include <net/if.h> |
| #include <net/route.h> |
| #include <netinet/in.h> |
| #include <netinet/igmp_var.h> |
| #include <netinet/ip6.h> |
| #include <netinet/icmp6.h> |
| |
| #include <inet/common.h> |
| #include <inet/mi.h> |
| #include <inet/nd.h> |
| #include <inet/ip.h> |
| #include <inet/ip6.h> |
| #include <inet/ip_multi.h> |
| #include <inet/ip_listutils.h> |
| |
| #include <netinet/igmp.h> |
| #include <inet/ip_if.h> |
| #include <net/pfkeyv2.h> |
| #include <inet/ipsec_info.h> |
| |
| static uint_t igmp_query_in(ipha_t *ipha, igmpa_t *igmpa, ill_t *ill); |
| static uint_t igmpv3_query_in(igmp3qa_t *igmp3qa, ill_t *ill, int igmplen); |
| static uint_t mld_query_in(mld_hdr_t *mldh, ill_t *ill); |
| static uint_t mldv2_query_in(mld2q_t *mld2q, ill_t *ill, int mldlen); |
| static void igmp_sendpkt(ilm_t *ilm, uchar_t type, ipaddr_t addr); |
| static void mld_sendpkt(ilm_t *ilm, uchar_t type, const in6_addr_t *v6addr); |
| static void igmpv3_sendrpt(ipif_t *ipif, mrec_t *reclist); |
| static void mldv2_sendrpt(ill_t *ill, mrec_t *reclist); |
| static mrec_t *mcast_bldmrec(mcast_record_t type, in6_addr_t *grp, |
| slist_t *srclist, mrec_t *next); |
| static void mcast_init_rtx(ill_t *ill, rtx_state_t *rtxp, |
| mcast_record_t rtype, slist_t *flist); |
| static mrec_t *mcast_merge_rtx(ilm_t *ilm, mrec_t *rp, slist_t *flist); |
| |
| /* Following protected by igmp_timer_lock */ |
| static int igmp_time_to_next; /* Time since last timeout */ |
| static int igmp_timer_fired_last; |
| uint_t igmp_deferred_next = INFINITY; |
| timeout_id_t igmp_timeout_id = 0; |
| kmutex_t igmp_timer_lock; |
| |
| /* Protected by igmp_slowtimeout_lock */ |
| timeout_id_t igmp_slowtimeout_id = 0; |
| kmutex_t igmp_slowtimeout_lock; |
| |
| /* Following protected by mld_timer_lock */ |
| static int mld_time_to_next; /* Time since last timeout */ |
| static int mld_timer_fired_last; |
| uint_t mld_deferred_next = INFINITY; |
| timeout_id_t mld_timeout_id = 0; |
| kmutex_t mld_timer_lock; |
| |
| /* Protected by mld_slowtimeout_lock */ |
| timeout_id_t mld_slowtimeout_id = 0; |
| kmutex_t mld_slowtimeout_lock; |
| |
| /* |
| * 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". |
| */ |
| #define DSEC_TO_MSEC(dsec) ((dsec) * 100) |
| #define SEC_TO_MSEC(sec) ((sec) * 1000) |
| |
| /* |
| * The first multicast join will trigger the igmp timers / mld timers |
| * The unit for next is milliseconds. |
| */ |
| void |
| igmp_start_timers(unsigned next) |
| { |
| int time_left; |
| /* Protected by igmp_timer_lock */ |
| static boolean_t igmp_timer_setter_active; |
| int ret; |
| |
| ASSERT(next != 0 && next != INFINITY); |
| |
| mutex_enter(&igmp_timer_lock); |
| |
| if (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. |
| */ |
| igmp_time_to_next = MIN(igmp_time_to_next, next); |
| mutex_exit(&igmp_timer_lock); |
| return; |
| } else { |
| igmp_timer_setter_active = B_TRUE; |
| } |
| if (igmp_timeout_id == 0) { |
| /* |
| * The timer is inactive. We need to start a timer |
| */ |
| igmp_time_to_next = next; |
| igmp_timeout_id = timeout(igmp_timeout_handler, NULL, |
| MSEC_TO_TICK(igmp_time_to_next)); |
| igmp_timer_setter_active = B_FALSE; |
| mutex_exit(&igmp_timer_lock); |
| 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 |
| */ |
| time_left = igmp_timer_fired_last + |
| MSEC_TO_TICK(igmp_time_to_next) - ddi_get_lbolt(); |
| if (time_left < MSEC_TO_TICK(next)) { |
| igmp_timer_setter_active = B_FALSE; |
| mutex_exit(&igmp_timer_lock); |
| return; |
| } |
| |
| mutex_exit(&igmp_timer_lock); |
| ret = untimeout(igmp_timeout_id); |
| mutex_enter(&igmp_timer_lock); |
| /* |
| * 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) { |
| ASSERT(igmp_timeout_id == 0); |
| } else { |
| ASSERT(igmp_timeout_id != 0); |
| igmp_timeout_id = 0; |
| } |
| if (igmp_time_to_next != 0) { |
| igmp_time_to_next = MIN(igmp_time_to_next, next); |
| igmp_timeout_id = timeout(igmp_timeout_handler, NULL, |
| MSEC_TO_TICK(igmp_time_to_next)); |
| } |
| igmp_timer_setter_active = B_FALSE; |
| mutex_exit(&igmp_timer_lock); |
| } |
| |
| /* |
| * mld_start_timers: |
| * The unit for next is milliseconds. |
| */ |
| void |
| mld_start_timers(unsigned next) |
| { |
| int time_left; |
| /* Protedted by mld_timer_lock */ |
| static boolean_t mld_timer_setter_active; |
| int ret; |
| |
| ASSERT(next != 0 && next != INFINITY); |
| |
| mutex_enter(&mld_timer_lock); |
| if (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. |
| */ |
| mld_time_to_next = MIN(mld_time_to_next, next); |
| mutex_exit(&mld_timer_lock); |
| return; |
| } else { |
| mld_timer_setter_active = B_TRUE; |
| } |
| if (mld_timeout_id == 0) { |
| /* |
| * The timer is inactive. We need to start a timer |
| */ |
| mld_time_to_next = next; |
| mld_timeout_id = timeout(mld_timeout_handler, NULL, |
| MSEC_TO_TICK(mld_time_to_next)); |
| mld_timer_setter_active = B_FALSE; |
| mutex_exit(&mld_timer_lock); |
| 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 |
| */ |
| time_left = mld_timer_fired_last + |
| MSEC_TO_TICK(mld_time_to_next) - ddi_get_lbolt(); |
| if (time_left < MSEC_TO_TICK(next)) { |
| mld_timer_setter_active = B_FALSE; |
| mutex_exit(&mld_timer_lock); |
| return; |
| } |
| |
| mutex_exit(&mld_timer_lock); |
| ret = untimeout(mld_timeout_id); |
| mutex_enter(&mld_timer_lock); |
| /* |
| * 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) { |
| ASSERT(mld_timeout_id == 0); |
| } else { |
| ASSERT(mld_timeout_id != 0); |
| mld_timeout_id = 0; |
| } |
| if (mld_time_to_next != 0) { |
| mld_time_to_next = MIN(mld_time_to_next, next); |
| mld_timeout_id = timeout(mld_timeout_handler, NULL, |
| MSEC_TO_TICK(mld_time_to_next)); |
| } |
| mld_timer_setter_active = B_FALSE; |
| mutex_exit(&mld_timer_lock); |
| } |
| |
| /* |
| * igmp_input: |
| * Return 0 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 */ |
| int |
| igmp_input(queue_t *q, mblk_t *mp, ill_t *ill) |
| { |
| igmpa_t *igmpa; |
| ipha_t *ipha = (ipha_t *)(mp->b_rptr); |
| int iphlen, igmplen, mblklen; |
| ilm_t *ilm; |
| uint32_t src, dst; |
| uint32_t group; |
| uint_t next; |
| ipif_t *ipif; |
| |
| ASSERT(ill != NULL); |
| ASSERT(!ill->ill_isv6); |
| ++igmpstat.igps_rcv_total; |
| |
| mblklen = MBLKL(mp); |
| if (mblklen < 1 || mblklen < (iphlen = IPH_HDR_LENGTH(ipha))) { |
| ++igmpstat.igps_rcv_tooshort; |
| freemsg(mp); |
| return (-1); |
| } |
| igmplen = ntohs(ipha->ipha_length) - iphlen; |
| /* |
| * Since msg sizes are more variable with v3, just pullup the |
| * whole thing now. |
| */ |
| if (MBLKL(mp) < (igmplen + iphlen)) { |
| mblk_t *mp1; |
| if ((mp1 = msgpullup(mp, -1)) == NULL) { |
| ++igmpstat.igps_rcv_tooshort; |
| freemsg(mp); |
| return (-1); |
| } |
| freemsg(mp); |
| mp = mp1; |
| ipha = (ipha_t *)(mp->b_rptr); |
| } |
| |
| /* |
| * Validate lengths |
| */ |
| if (igmplen < IGMP_MINLEN) { |
| ++igmpstat.igps_rcv_tooshort; |
| freemsg(mp); |
| return (-1); |
| } |
| /* |
| * Validate checksum |
| */ |
| if (IP_CSUM(mp, iphlen, 0)) { |
| ++igmpstat.igps_rcv_badsum; |
| freemsg(mp); |
| return (-1); |
| } |
| |
| igmpa = (igmpa_t *)(&mp->b_rptr[iphlen]); |
| src = ipha->ipha_src; |
| dst = ipha->ipha_dst; |
| if (ip_debug > 1) |
| (void) mi_strlog(ill->ill_rq, 1, SL_TRACE, |
| "igmp_input: src 0x%x, dst 0x%x on %s\n", |
| (int)ntohl(src), (int)ntohl(dst), |
| ill->ill_name); |
| |
| switch (igmpa->igmpa_type) { |
| case IGMP_MEMBERSHIP_QUERY: |
| /* |
| * packet length differentiates between v1/v2 and v3 |
| * v1/v2 should be exactly 8 octets long; v3 is >= 12 |
| */ |
| if (igmplen == IGMP_MINLEN) { |
| next = igmp_query_in(ipha, igmpa, ill); |
| } else if (igmplen >= IGMP_V3_QUERY_MINLEN) { |
| next = igmpv3_query_in((igmp3qa_t *)igmpa, ill, |
| igmplen); |
| } else { |
| ++igmpstat.igps_rcv_tooshort; |
| freemsg(mp); |
| return (-1); |
| } |
| if (next == 0) { |
| freemsg(mp); |
| return (-1); |
| } |
| |
| if (next != INFINITY) |
| igmp_start_timers(next); |
| |
| break; |
| |
| case IGMP_V1_MEMBERSHIP_REPORT: |
| case IGMP_V2_MEMBERSHIP_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. |
| */ |
| mutex_enter(&ill->ill_lock); |
| for (ipif = ill->ill_ipif; ipif != NULL; |
| ipif = ipif->ipif_next) { |
| if (ipif->ipif_lcl_addr == src) { |
| if (ip_debug > 1) { |
| (void) mi_strlog(ill->ill_rq, |
| 1, |
| SL_TRACE, |
| "igmp_input: we are only " |
| "member src 0x%x ipif_local 0x%x", |
| (int)ntohl(src), |
| (int) |
| ntohl(ipif->ipif_lcl_addr)); |
| } |
| mutex_exit(&ill->ill_lock); |
| return (0); |
| } |
| } |
| mutex_exit(&ill->ill_lock); |
| |
| ++igmpstat.igps_rcv_reports; |
| group = igmpa->igmpa_group; |
| if (!CLASSD(group)) { |
| ++igmpstat.igps_rcv_badreports; |
| freemsg(mp); |
| return (-1); |
| } |
| |
| /* |
| * 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. |
| */ |
| if ((src & htonl(0xFF000000U)) == 0) { /* Minimum net mask */ |
| /* Pick the first ipif on this ill */ |
| mutex_enter(&ill->ill_lock); |
| src = ill->ill_ipif->ipif_subnet; |
| mutex_exit(&ill->ill_lock); |
| ip1dbg(("igmp_input: changed src to 0x%x\n", |
| (int)ntohl(src))); |
| ipha->ipha_src = src; |
| } |
| |
| /* |
| * 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. |
| */ |
| mutex_enter(&ill->ill_lock); |
| for (ipif = ill->ill_ipif; ipif != NULL; |
| ipif = ipif->ipif_next) { |
| ilm = ilm_lookup_ipif(ipif, group); |
| if (ilm != NULL) { |
| ++igmpstat.igps_rcv_ourreports; |
| ilm->ilm_timer = INFINITY; |
| ilm->ilm_state = IGMP_OTHERMEMBER; |
| } |
| } /* for */ |
| mutex_exit(&ill->ill_lock); |
| break; |
| |
| case IGMP_V3_MEMBERSHIP_REPORT: |
| /* |
| * 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 (0); |
| } |
| |
| static uint_t |
| igmp_query_in(ipha_t *ipha, igmpa_t *igmpa, ill_t *ill) |
| { |
| ilm_t *ilm; |
| int timer; |
| uint_t next; |
| |
| ++igmpstat.igps_rcv_queries; |
| |
| /* |
| * 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. |
| */ |
| |
| |
| mutex_enter(&ill->ill_lock); |
| ill->ill_mcast_v1_time = 0; |
| ill->ill_mcast_v1_tset = 1; |
| if (ill->ill_mcast_type != IGMP_V1_ROUTER) { |
| ip1dbg(("Received IGMPv1 Query on %s, switching mode " |
| "to IGMP_V1_ROUTER\n", ill->ill_name)); |
| atomic_add_16(&ill->ill_ifptr->illif_mcast_v1, 1); |
| ill->ill_mcast_type = IGMP_V1_ROUTER; |
| } |
| mutex_exit(&ill->ill_lock); |
| |
| timer = SEC_TO_MSEC(IGMP_MAX_HOST_REPORT_DELAY); |
| |
| if (ipha->ipha_dst != htonl(INADDR_ALLHOSTS_GROUP) || |
| igmpa->igmpa_group != 0) { |
| ++igmpstat.igps_rcv_badqueries; |
| return (0); |
| } |
| |
| } else { |
| in_addr_t group; |
| |
| /* |
| * Query from a new router |
| * Simply do a validity check |
| */ |
| group = igmpa->igmpa_group; |
| if (group != 0 && (!CLASSD(group))) { |
| ++igmpstat.igps_rcv_badqueries; |
| 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. |
| */ |
| mutex_enter(&ill->ill_lock); |
| if (ill->ill_mcast_type == IGMP_V3_ROUTER) { |
| ip1dbg(("Received IGMPv2 Query on %s, switching mode " |
| "to IGMP_V2_ROUTER", ill->ill_name)); |
| atomic_add_16(&ill->ill_ifptr->illif_mcast_v2, 1); |
| ill->ill_mcast_type = IGMP_V2_ROUTER; |
| } |
| ill->ill_mcast_v2_time = 0; |
| ill->ill_mcast_v2_tset = 1; |
| mutex_exit(&ill->ill_lock); |
| |
| timer = DSEC_TO_MSEC((int)igmpa->igmpa_code); |
| } |
| |
| if (ip_debug > 1) { |
| mutex_enter(&ill->ill_lock); |
| (void) mi_strlog(ill->ill_rq, 1, SL_TRACE, |
| "igmp_input: TIMER = igmp_code %d igmp_type 0x%x", |
| (int)ntohs(igmpa->igmpa_code), |
| (int)ntohs(igmpa->igmpa_type)); |
| mutex_exit(&ill->ill_lock); |
| } |
| |
| /* |
| * -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. |
| */ |
| next = (unsigned)INFINITY; |
| mutex_enter(&ill->ill_lock); |
| for (ilm = ill->ill_ilm; ilm; ilm = ilm->ilm_next) { |
| |
| /* |
| * 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 |
| */ |
| if (!IN6_IS_ADDR_V4MAPPED(&ilm->ilm_v6addr)) |
| continue; |
| if (ilm->ilm_addr == htonl(INADDR_ANY)) |
| continue; |
| if (ilm->ilm_addr != htonl(INADDR_ALLHOSTS_GROUP) && |
| (igmpa->igmpa_group == 0) || |
| (igmpa->igmpa_group == ilm->ilm_addr)) { |
| if (ilm->ilm_timer > timer) { |
| MCAST_RANDOM_DELAY(ilm->ilm_timer, timer); |
| if (ilm->ilm_timer < next) |
| next = ilm->ilm_timer; |
| } |
| } |
| } |
| mutex_exit(&ill->ill_lock); |
| |
| return (next); |
| } |
| |
| static uint_t |
| igmpv3_query_in(igmp3qa_t *igmp3qa, ill_t *ill, int igmplen) |
| { |
| uint_t i, next, mrd, qqi, timer, delay, numsrc; |
| ilm_t *ilm; |
| ipaddr_t *src_array; |
| uint8_t qrv; |
| |
| /* make sure numsrc matches packet size */ |
| numsrc = ntohs(igmp3qa->igmp3qa_numsrc); |
| if (igmplen < IGMP_V3_QUERY_MINLEN + (numsrc * sizeof (ipaddr_t))) { |
| ++igmpstat.igps_rcv_tooshort; |
| return (0); |
| } |
| src_array = (ipaddr_t *)&igmp3qa[1]; |
| |
| ++igmpstat.igps_rcv_queries; |
| |
| if ((mrd = (uint_t)igmp3qa->igmp3qa_mxrc) >= IGMP_V3_MAXRT_FPMIN) { |
| uint_t hdrval, mant, exp; |
| hdrval = (uint_t)igmp3qa->igmp3qa_mxrc; |
| mant = hdrval & IGMP_V3_MAXRT_MANT_MASK; |
| exp = (hdrval & IGMP_V3_MAXRT_EXP_MASK) >> 4; |
| mrd = (mant | 0x10) << (exp + 3); |
| } |
| if (mrd == 0) |
| mrd = MCAST_DEF_QUERY_RESP_INTERVAL; |
| timer = DSEC_TO_MSEC(mrd); |
| MCAST_RANDOM_DELAY(delay, timer); |
| next = (unsigned)INFINITY; |
| |
| if ((qrv = igmp3qa->igmp3qa_sqrv & IGMP_V3_RV_MASK) == 0) |
| ill->ill_mcast_rv = MCAST_DEF_ROBUSTNESS; |
| else |
| ill->ill_mcast_rv = qrv; |
| |
| if ((qqi = (uint_t)igmp3qa->igmp3qa_qqic) >= IGMP_V3_QQI_FPMIN) { |
| uint_t hdrval, mant, exp; |
| hdrval = (uint_t)igmp3qa->igmp3qa_qqic; |
| mant = hdrval & IGMP_V3_QQI_MANT_MASK; |
| exp = (hdrval & IGMP_V3_QQI_EXP_MASK) >> 4; |
| qqi = (mant | 0x10) << (exp + 3); |
| } |
| ill->ill_mcast_qi = (qqi == 0) ? MCAST_DEF_QUERY_INTERVAL : qqi; |
| |
| /* |
| * 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) |
| */ |
| mutex_enter(&ill->ill_lock); |
| if (ill->ill_global_timer < delay) { |
| mutex_exit(&ill->ill_lock); |
| return (next); |
| } |
| mutex_exit(&ill->ill_lock); |
| |
| /* |
| * Now take action depending upon query type: |
| * general, group specific, or group/source specific. |
| */ |
| if ((numsrc == 0) && (igmp3qa->igmp3qa_group == INADDR_ANY)) { |
| /* |
| * 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]). |
| */ |
| mutex_enter(&ill->ill_lock); |
| ill->ill_global_timer = delay; |
| next = ill->ill_global_timer; |
| mutex_exit(&ill->ill_lock); |
| |
| } else { |
| /* group or group/source specific query */ |
| mutex_enter(&ill->ill_lock); |
| for (ilm = ill->ill_ilm; ilm; ilm = ilm->ilm_next) { |
| if (!IN6_IS_ADDR_V4MAPPED(&ilm->ilm_v6addr) || |
| (ilm->ilm_addr == htonl(INADDR_ANY)) || |
| (ilm->ilm_addr == htonl(INADDR_ALLHOSTS_GROUP)) || |
| (igmp3qa->igmp3qa_group != ilm->ilm_addr)) |
| 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. |
| */ |
| if (numsrc == 0 || (ilm->ilm_timer < INFINITY && |
| SLIST_IS_EMPTY(ilm->ilm_pendsrcs))) { |
| group_query: |
| FREE_SLIST(ilm->ilm_pendsrcs); |
| ilm->ilm_pendsrcs = NULL; |
| } else { |
| boolean_t overflow; |
| slist_t *pktl; |
| if (numsrc > MAX_FILTER_SIZE || |
| (ilm->ilm_pendsrcs == NULL && |
| (ilm->ilm_pendsrcs = l_alloc()) == NULL)) { |
| /* |
| * 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; |
| } |
| if ((pktl = l_alloc()) == NULL) |
| goto group_query; |
| pktl->sl_numsrc = numsrc; |
| for (i = 0; i < numsrc; i++) |
| IN6_IPADDR_TO_V4MAPPED(src_array[i], |
| &(pktl->sl_addr[i])); |
| l_union_in_a(ilm->ilm_pendsrcs, pktl, |
| &overflow); |
| l_free(pktl); |
| if (overflow) |
| goto group_query; |
| } |
| /* choose soonest timer */ |
| ilm->ilm_timer = MIN(ilm->ilm_timer, delay); |
| if (ilm->ilm_timer < next) |
| next = ilm->ilm_timer; |
| } |
| mutex_exit(&ill->ill_lock); |
| } |
| |
| return (next); |
| } |
| |
| void |
| igmp_joingroup(ilm_t *ilm) |
| { |
| ill_t *ill; |
| |
| ill = ilm->ilm_ipif->ipif_ill; |
| |
| ASSERT(IAM_WRITER_ILL(ill)); |
| ASSERT(ilm->ilm_ill == NULL && !ilm->ilm_ipif->ipif_isv6); |
| |
| mutex_enter(&ill->ill_lock); |
| if (ilm->ilm_addr == htonl(INADDR_ALLHOSTS_GROUP)) { |
| ilm->ilm_rtx.rtx_timer = INFINITY; |
| ilm->ilm_state = IGMP_OTHERMEMBER; |
| mutex_exit(&ill->ill_lock); |
| } else { |
| ip1dbg(("Querier mode %d, sending report, group %x\n", |
| ill->ill_mcast_type, htonl(ilm->ilm_addr))); |
| if (ill->ill_mcast_type == IGMP_V1_ROUTER) { |
| mutex_exit(&ill->ill_lock); |
| igmp_sendpkt(ilm, IGMP_V1_MEMBERSHIP_REPORT, 0); |
| mutex_enter(&ill->ill_lock); |
| } else if (ill->ill_mcast_type == IGMP_V2_ROUTER) { |
| mutex_exit(&ill->ill_lock); |
| igmp_sendpkt(ilm, IGMP_V2_MEMBERSHIP_REPORT, 0); |
| mutex_enter(&ill->ill_lock); |
| } else if (ill->ill_mcast_type == IGMP_V3_ROUTER) { |
| mrec_t *rp; |
| mcast_record_t rtype; |
| /* |
| * 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. |
| */ |
| rtype = (ilm->ilm_fmode == MODE_IS_INCLUDE) ? |
| ALLOW_NEW_SOURCES : CHANGE_TO_EXCLUDE; |
| rp = mcast_bldmrec(rtype, &ilm->ilm_v6addr, |
| ilm->ilm_filter, NULL); |
| mutex_exit(&ill->ill_lock); |
| igmpv3_sendrpt(ilm->ilm_ipif, rp); |
| mutex_enter(&ill->ill_lock); |
| /* |
| * Set up retransmission state. Timer is set below, |
| * for both v3 and older versions. |
| */ |
| mcast_init_rtx(ill, &ilm->ilm_rtx, rtype, |
| ilm->ilm_filter); |
| } |
| |
| /* Set the ilm timer value */ |
| MCAST_RANDOM_DELAY(ilm->ilm_rtx.rtx_timer, |
| SEC_TO_MSEC(IGMP_MAX_HOST_REPORT_DELAY)); |
| ilm->ilm_state = IGMP_IREPORTEDLAST; |
| mutex_exit(&ill->ill_lock); |
| |
| /* |
| * 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. |
| */ |
| mutex_enter(&igmp_timer_lock); |
| igmp_deferred_next = MIN(ilm->ilm_rtx.rtx_timer, |
| igmp_deferred_next); |
| mutex_exit(&igmp_timer_lock); |
| } |
| |
| if (ip_debug > 1) { |
| (void) mi_strlog(ilm->ilm_ipif->ipif_ill->ill_rq, 1, SL_TRACE, |
| "igmp_joingroup: multicast_type %d timer %d", |
| (ilm->ilm_ipif->ipif_ill->ill_mcast_type), |
| (int)ntohl(ilm->ilm_rtx.rtx_timer)); |
| } |
| } |
| |
| void |
| mld_joingroup(ilm_t *ilm) |
| { |
| ill_t *ill; |
| |
| ill = ilm->ilm_ill; |
| |
| ASSERT(IAM_WRITER_ILL(ill)); |
| ASSERT(ilm->ilm_ipif == NULL && ill->ill_isv6); |
| |
| mutex_enter(&ill->ill_lock); |
| if (IN6_ARE_ADDR_EQUAL(&ipv6_all_hosts_mcast, &ilm->ilm_v6addr)) { |
| ilm->ilm_rtx.rtx_timer = INFINITY; |
| ilm->ilm_state = IGMP_OTHERMEMBER; |
| mutex_exit(&ill->ill_lock); |
| } else { |
| if (ill->ill_mcast_type == MLD_V1_ROUTER) { |
| mutex_exit(&ill->ill_lock); |
| mld_sendpkt(ilm, MLD_LISTENER_REPORT, NULL); |
| mutex_enter(&ill->ill_lock); |
| } else { |
| mrec_t *rp; |
| mcast_record_t rtype; |
| /* |
| * 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 |
| */ |
| rtype = (ilm->ilm_fmode == MODE_IS_INCLUDE) ? |
| ALLOW_NEW_SOURCES : CHANGE_TO_EXCLUDE; |
| rp = mcast_bldmrec(rtype, &ilm->ilm_v6addr, |
| ilm->ilm_filter, NULL); |
| mutex_exit(&ill->ill_lock); |
| mldv2_sendrpt(ill, rp); |
| mutex_enter(&ill->ill_lock); |
| /* |
| * Set up retransmission state. Timer is set below, |
| * for both v2 and v1. |
| */ |
| mcast_init_rtx(ill, &ilm->ilm_rtx, rtype, |
| ilm->ilm_filter); |
| } |
| |
| /* Set the ilm timer value */ |
| ASSERT(ill->ill_mcast_type != MLD_V2_ROUTER || |
| ilm->ilm_rtx.rtx_cnt > 0); |
| MCAST_RANDOM_DELAY(ilm->ilm_rtx.rtx_timer, |
| SEC_TO_MSEC(ICMP6_MAX_HOST_REPORT_DELAY)); |
| ilm->ilm_state = IGMP_IREPORTEDLAST; |
| mutex_exit(&ill->ill_lock); |
| |
| /* |
| * 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 |
| */ |
| mutex_enter(&mld_timer_lock); |
| mld_deferred_next = MIN(ilm->ilm_rtx.rtx_timer, |
| mld_deferred_next); |
| mutex_exit(&mld_timer_lock); |
| } |
| |
| if (ip_debug > 1) { |
| (void) mi_strlog(ilm->ilm_ill->ill_rq, 1, SL_TRACE, |
| "mld_joingroup: multicast_type %d timer %d", |
| (ilm->ilm_ill->ill_mcast_type), |
| (int)ntohl(ilm->ilm_rtx.rtx_timer)); |
| } |
| } |
| |
| void |
| igmp_leavegroup(ilm_t *ilm) |
| { |
| ill_t *ill = ilm->ilm_ipif->ipif_ill; |
| |
| ASSERT(ilm->ilm_ill == NULL); |
| ASSERT(!ill->ill_isv6); |
| |
| mutex_enter(&ill->ill_lock); |
| if (ilm->ilm_state == IGMP_IREPORTEDLAST && |
| ill->ill_mcast_type == IGMP_V2_ROUTER && |
| (ilm->ilm_addr != htonl(INADDR_ALLHOSTS_GROUP))) { |
| mutex_exit(&ill->ill_lock); |
| igmp_sendpkt(ilm, IGMP_V2_LEAVE_GROUP, |
| (htonl(INADDR_ALLRTRS_GROUP))); |
| return; |
| } else if ((ill->ill_mcast_type == IGMP_V3_ROUTER) && |
| (ilm->ilm_addr != htonl(INADDR_ALLHOSTS_GROUP))) { |
| mrec_t *rp; |
| /* |
| * 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 |
| */ |
| if (ilm->ilm_fmode == MODE_IS_INCLUDE) { |
| rp = mcast_bldmrec(BLOCK_OLD_SOURCES, &ilm->ilm_v6addr, |
| ilm->ilm_filter, NULL); |
| } else { |
| rp = mcast_bldmrec(CHANGE_TO_INCLUDE, &ilm->ilm_v6addr, |
| NULL, NULL); |
| } |
| mutex_exit(&ill->ill_lock); |
| igmpv3_sendrpt(ilm->ilm_ipif, rp); |
| return; |
| } |
| mutex_exit(&ill->ill_lock); |
| } |
| |
| void |
| mld_leavegroup(ilm_t *ilm) |
| { |
| ill_t *ill = ilm->ilm_ill; |
| |
| ASSERT(ilm->ilm_ipif == NULL); |
| ASSERT(ill->ill_isv6); |
| |
| mutex_enter(&ill->ill_lock); |
| if (ilm->ilm_state == IGMP_IREPORTEDLAST && |
| ill->ill_mcast_type == MLD_V1_ROUTER && |
| (!IN6_ARE_ADDR_EQUAL(&ipv6_all_hosts_mcast, &ilm->ilm_v6addr))) { |
| mutex_exit(&ill->ill_lock); |
| mld_sendpkt(ilm, MLD_LISTENER_REDUCTION, &ipv6_all_rtrs_mcast); |
| return; |
| } else if ((ill->ill_mcast_type == MLD_V2_ROUTER) && |
| (!IN6_ARE_ADDR_EQUAL(&ipv6_all_hosts_mcast, &ilm->ilm_v6addr))) { |
| mrec_t *rp; |
| /* |
| * 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 |
| */ |
| if (ilm->ilm_fmode == MODE_IS_INCLUDE) { |
| rp = mcast_bldmrec(BLOCK_OLD_SOURCES, &ilm->ilm_v6addr, |
| ilm->ilm_filter, NULL); |
| } else { |
| rp = mcast_bldmrec(CHANGE_TO_INCLUDE, &ilm->ilm_v6addr, |
| NULL, NULL); |
| } |
| mutex_exit(&ill->ill_lock); |
| mldv2_sendrpt(ill, rp); |
| return; |
| } |
| mutex_exit(&ill->ill_lock); |
| } |
| |
| void |
| igmp_statechange(ilm_t *ilm, mcast_record_t fmode, slist_t *flist) |
| { |
| ill_t *ill; |
| mrec_t *rp; |
| |
| ASSERT(ilm != NULL); |
| |
| /* state change reports should only be sent if the router is v3 */ |
| if (ilm->ilm_ipif->ipif_ill->ill_mcast_type != IGMP_V3_ROUTER) |
| return; |
| |
| if (ilm->ilm_ill == NULL) { |
| ASSERT(ilm->ilm_ipif != NULL); |
| ill = ilm->ilm_ipif->ipif_ill; |
| } else { |
| ill = ilm->ilm_ill; |
| } |
| |
| mutex_enter(&ill->ill_lock); |
| |
| /* |
| * 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) |
| */ |
| |
| if (ilm->ilm_fmode == fmode) { |
| slist_t *a_minus_b = NULL, *b_minus_a = NULL; |
| slist_t *allow, *block; |
| if (((a_minus_b = l_alloc()) == NULL) || |
| ((b_minus_a = l_alloc()) == NULL)) { |
| l_free(a_minus_b); |
| if (ilm->ilm_fmode == MODE_IS_INCLUDE) |
| goto send_to_ex; |
| else |
| goto send_to_in; |
| } |
| l_difference(ilm->ilm_filter, flist, a_minus_b); |
| l_difference(flist, ilm->ilm_filter, b_minus_a); |
| if (ilm->ilm_fmode == MODE_IS_INCLUDE) { |
| allow = b_minus_a; |
| block = a_minus_b; |
| } else { |
| allow = a_minus_b; |
| block = b_minus_a; |
| } |
| rp = NULL; |
| if (!SLIST_IS_EMPTY(allow)) |
| rp = mcast_bldmrec(ALLOW_NEW_SOURCES, &ilm->ilm_v6addr, |
| allow, rp); |
| if (!SLIST_IS_EMPTY(block)) |
| rp = mcast_bldmrec(BLOCK_OLD_SOURCES, &ilm->ilm_v6addr, |
| block, rp); |
| l_free(a_minus_b); |
| l_free(b_minus_a); |
| } else if (ilm->ilm_fmode == MODE_IS_INCLUDE) { |
| send_to_ex: |
| rp = mcast_bldmrec(CHANGE_TO_EXCLUDE, &ilm->ilm_v6addr, flist, |
| NULL); |
| } else { |
| send_to_in: |
| rp = mcast_bldmrec(CHANGE_TO_INCLUDE, &ilm->ilm_v6addr, flist, |
| 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). |
| */ |
| rp = mcast_merge_rtx(ilm, rp, flist); |
| if (ilm->ilm_rtx.rtx_timer == INFINITY) { |
| MCAST_RANDOM_DELAY(ilm->ilm_rtx.rtx_timer, |
| SEC_TO_MSEC(IGMP_MAX_HOST_REPORT_DELAY)); |
| mutex_enter(&igmp_timer_lock); |
| igmp_deferred_next = MIN(igmp_deferred_next, |
| ilm->ilm_rtx.rtx_timer); |
| mutex_exit(&igmp_timer_lock); |
| } |
| |
| mutex_exit(&ill->ill_lock); |
| igmpv3_sendrpt(ilm->ilm_ipif, rp); |
| } |
| |
| void |
| mld_statechange(ilm_t *ilm, mcast_record_t fmode, slist_t *flist) |
| { |
| ill_t *ill; |
| mrec_t *rp = NULL; |
| |
| ASSERT(ilm != NULL); |
| |
| ill = ilm->ilm_ill; |
| |
| /* only need to send if we have an mldv2-capable router */ |
| mutex_enter(&ill->ill_lock); |
| if (ill->ill_mcast_type != MLD_V2_ROUTER) { |
| mutex_exit(&ill->ill_lock); |
| 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) |
| */ |
| if (ilm->ilm_fmode == fmode) { |
| slist_t *a_minus_b = NULL, *b_minus_a = NULL; |
| slist_t *allow, *block; |
| if (((a_minus_b = l_alloc()) == NULL) || |
| ((b_minus_a = l_alloc()) == NULL)) { |
| l_free(a_minus_b); |
| if (ilm->ilm_fmode == MODE_IS_INCLUDE) |
| goto send_to_ex; |
| else |
| goto send_to_in; |
| } |
| l_difference(ilm->ilm_filter, flist, a_minus_b); |
| l_difference(flist, ilm->ilm_filter, b_minus_a); |
| if (ilm->ilm_fmode == MODE_IS_INCLUDE) { |
| allow = b_minus_a; |
| block = a_minus_b; |
| } else { |
| allow = a_minus_b; |
| block = b_minus_a; |
| } |
| if (!SLIST_IS_EMPTY(allow)) |
| rp = mcast_bldmrec(ALLOW_NEW_SOURCES, &ilm->ilm_v6addr, |
| allow, rp); |
| if (!SLIST_IS_EMPTY(block)) |
| rp = mcast_bldmrec(BLOCK_OLD_SOURCES, &ilm->ilm_v6addr, |
| block, rp); |
| l_free(a_minus_b); |
| l_free(b_minus_a); |
| } else if (ilm->ilm_fmode == MODE_IS_INCLUDE) { |
| send_to_ex: |
| rp = mcast_bldmrec(CHANGE_TO_EXCLUDE, &ilm->ilm_v6addr, flist, |
| NULL); |
| } else { |
| send_to_in: |
| rp = mcast_bldmrec(CHANGE_TO_INCLUDE, &ilm->ilm_v6addr, flist, |
| 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). |
| */ |
| rp = mcast_merge_rtx(ilm, rp, flist); |
| ASSERT(ilm->ilm_rtx.rtx_cnt > 0); |
| if (ilm->ilm_rtx.rtx_timer == INFINITY) { |
| MCAST_RANDOM_DELAY(ilm->ilm_rtx.rtx_timer, |
| SEC_TO_MSEC(ICMP6_MAX_HOST_REPORT_DELAY)); |
| mutex_enter(&mld_timer_lock); |
| mld_deferred_next = |
| MIN(mld_deferred_next, ilm->ilm_rtx.rtx_timer); |
| mutex_exit(&mld_timer_lock); |
| } |
| |
| mutex_exit(&ill->ill_lock); |
| mldv2_sendrpt(ill, rp); |
| } |
| |
| uint_t |
| igmp_timeout_handler_per_ill(ill_t *ill, int elapsed) |
| { |
| uint_t next = INFINITY; |
| ilm_t *ilm; |
| ipif_t *ipif; |
| mrec_t *rp = NULL; |
| mrec_t *rtxrp = NULL; |
| rtx_state_t *rtxp; |
| mcast_record_t rtype; |
| |
| ASSERT(IAM_WRITER_ILL(ill)); |
| |
| mutex_enter(&ill->ill_lock); |
| |
| /* First check the global timer on this interface */ |
| if (ill->ill_global_timer == INFINITY) |
| goto per_ilm_timer; |
| if (ill->ill_global_timer <= elapsed) { |
| ill->ill_global_timer = INFINITY; |
| /* |
| * 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. |
| */ |
| for (ilm = ill->ill_ilm; ilm != NULL; ilm = ilm->ilm_next) { |
| if (ilm->ilm_addr == htonl(INADDR_ALLHOSTS_GROUP)) |
| continue; |
| ASSERT(ilm->ilm_ipif != NULL); |
| ilm->ilm_ipif->ipif_igmp_rpt = |
| mcast_bldmrec(ilm->ilm_fmode, &ilm->ilm_v6addr, |
| ilm->ilm_filter, ilm->ilm_ipif->ipif_igmp_rpt); |
| /* |
| * 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. |
| */ |
| ilm->ilm_timer = INFINITY; |
| ilm->ilm_state = IGMP_IREPORTEDLAST; |
| FREE_SLIST(ilm->ilm_pendsrcs); |
| ilm->ilm_pendsrcs = NULL; |
| } |
| /* |
| * 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. |
| */ |
| for (ipif = ill->ill_ipif; ipif != NULL; |
| ipif = ipif->ipif_next) { |
| if (ipif->ipif_igmp_rpt == NULL) |
| continue; |
| mutex_exit(&ill->ill_lock); |
| igmpv3_sendrpt(ipif, ipif->ipif_igmp_rpt); |
| mutex_enter(&ill->ill_lock); |
| /* mrec list was freed by igmpv3_sendrpt() */ |
| ipif->ipif_igmp_rpt = NULL; |
| } |
| } else { |
| ill->ill_global_timer -= elapsed; |
| if (ill->ill_global_timer < next) |
| next = ill->ill_global_timer; |
| } |
| |
| per_ilm_timer: |
| for (ilm = ill->ill_ilm; ilm != NULL; ilm = ilm->ilm_next) { |
| if (ilm->ilm_timer == INFINITY) |
| goto per_ilm_rtxtimer; |
| |
| if (ilm->ilm_timer > elapsed) { |
| ilm->ilm_timer -= elapsed; |
| if (ilm->ilm_timer < next) |
| next = ilm->ilm_timer; |
| |
| if (ip_debug > 1) { |
| (void) mi_strlog(ill->ill_rq, 1, SL_TRACE, |
| "igmp_timo_hlr 2: ilm_timr %d elap %d " |
| "typ %d nxt %d", |
| (int)ntohl(ilm->ilm_timer), elapsed, |
| (ill->ill_mcast_type), next); |
| } |
| |
| goto per_ilm_rtxtimer; |
| } |
| |
| /* the timer has expired, need to take action */ |
| ilm->ilm_timer = INFINITY; |
| ilm->ilm_state = IGMP_IREPORTEDLAST; |
| if (ill->ill_mcast_type == IGMP_V1_ROUTER) { |
| mutex_exit(&ill->ill_lock); |
| igmp_sendpkt(ilm, IGMP_V1_MEMBERSHIP_REPORT, 0); |
| mutex_enter(&ill->ill_lock); |
| } else if (ill->ill_mcast_type == IGMP_V2_ROUTER) { |
| mutex_exit(&ill->ill_lock); |
| igmp_sendpkt(ilm, IGMP_V2_MEMBERSHIP_REPORT, 0); |
| mutex_enter(&ill->ill_lock); |
| } else { |
| slist_t *rsp; |
| if (!SLIST_IS_EMPTY(ilm->ilm_pendsrcs) && |
| (rsp = l_alloc()) != NULL) { |
| /* |
| * Contents of reply depend on pending |
| * requested source list. |
| */ |
| if (ilm->ilm_fmode == MODE_IS_INCLUDE) { |
| l_intersection(ilm->ilm_filter, |
| ilm->ilm_pendsrcs, rsp); |
| } else { |
| l_difference(ilm->ilm_pendsrcs, |
| ilm->ilm_filter, rsp); |
| } |
| FREE_SLIST(ilm->ilm_pendsrcs); |
| ilm->ilm_pendsrcs = NULL; |
| if (!SLIST_IS_EMPTY(rsp)) |
| rp = mcast_bldmrec(MODE_IS_INCLUDE, |
| &ilm->ilm_v6addr, rsp, rp); |
| FREE_SLIST(rsp); |
| } else { |
| /* |
| * Either the pending request is just group- |
| * specific, or we couldn't get the resources |
| * (rsp) to build a source-specific reply. |
| */ |
| rp = mcast_bldmrec(ilm->ilm_fmode, |
| &ilm->ilm_v6addr, ilm->ilm_filter, rp); |
| } |
| mutex_exit(&ill->ill_lock); |
| igmpv3_sendrpt(ill->ill_ipif, rp); |
| mutex_enter(&ill->ill_lock); |
| rp = NULL; |
| } |
| |
| if (ip_debug > 1) { |
| (void) mi_strlog(ill->ill_rq, 1, SL_TRACE, |
| "igmp_timo_hlr 1: ilm_timr %d elap %d " |
| "typ %d nxt %d", |
| (int)ntohl(ilm->ilm_timer), elapsed, |
| (ill->ill_mcast_type), next); |
| } |
| |
| per_ilm_rtxtimer: |
| rtxp = &ilm->ilm_rtx; |
| |
| if (rtxp->rtx_timer == INFINITY) |
| continue; |
| if (rtxp->rtx_timer > elapsed) { |
| rtxp->rtx_timer -= elapsed; |
| if (rtxp->rtx_timer < next) |
| next = rtxp->rtx_timer; |
| continue; |
| } |
| |
| rtxp->rtx_timer = INFINITY; |
| ilm->ilm_state = IGMP_IREPORTEDLAST; |
| if (ill->ill_mcast_type == IGMP_V1_ROUTER) { |
| mutex_exit(&ill->ill_lock); |
| igmp_sendpkt(ilm, IGMP_V1_MEMBERSHIP_REPORT, 0); |
| mutex_enter(&ill->ill_lock); |
| continue; |
| } else if (ill->ill_mcast_type == IGMP_V2_ROUTER) { |
| mutex_exit(&ill->ill_lock); |
| igmp_sendpkt(ilm, IGMP_V2_MEMBERSHIP_REPORT, 0); |
| mutex_enter(&ill->ill_lock); |
| 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. |
| */ |
| ASSERT(rtxp->rtx_cnt > 0); |
| ASSERT(rtxp->rtx_cnt >= rtxp->rtx_fmode_cnt); |
| rtxp->rtx_cnt--; |
| if (rtxp->rtx_fmode_cnt > 0) { |
| rtxp->rtx_fmode_cnt--; |
| rtype = (ilm->ilm_fmode == MODE_IS_INCLUDE) ? |
| CHANGE_TO_INCLUDE : CHANGE_TO_EXCLUDE; |
| rtxrp = mcast_bldmrec(rtype, &ilm->ilm_v6addr, |
| ilm->ilm_filter, rtxrp); |
| } else { |
| rtxrp = mcast_bldmrec(ALLOW_NEW_SOURCES, |
| &ilm->ilm_v6addr, rtxp->rtx_allow, rtxrp); |
| rtxrp = mcast_bldmrec(BLOCK_OLD_SOURCES, |
| &ilm->ilm_v6addr, rtxp->rtx_block, rtxrp); |
| } |
| if (rtxp->rtx_cnt > 0) { |
| MCAST_RANDOM_DELAY(rtxp->rtx_timer, |
| SEC_TO_MSEC(IGMP_MAX_HOST_REPORT_DELAY)); |
| if (rtxp->rtx_timer < next) |
| next = rtxp->rtx_timer; |
| } else { |
| CLEAR_SLIST(rtxp->rtx_allow); |
| CLEAR_SLIST(rtxp->rtx_block); |
| } |
| mutex_exit(&ill->ill_lock); |
| igmpv3_sendrpt(ilm->ilm_ipif, rtxrp); |
| mutex_enter(&ill->ill_lock); |
| rtxrp = NULL; |
| } |
| |
| mutex_exit(&ill->ill_lock); |
| |
| 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 |
| */ |
| |
| /* ARGSUSED */ |
| void |
| igmp_timeout_handler(void *arg) |
| { |
| ill_t *ill; |
| int elapsed; /* Since last call */ |
| uint_t global_next = INFINITY; |
| uint_t next; |
| ill_walk_context_t ctx; |
| boolean_t success; |
| |
| mutex_enter(&igmp_timer_lock); |
| ASSERT(igmp_timeout_id != 0); |
| igmp_timer_fired_last = ddi_get_lbolt(); |
| elapsed = igmp_time_to_next; |
| igmp_time_to_next = 0; |
| mutex_exit(&igmp_timer_lock); |
| |
| rw_enter(&ill_g_lock, RW_READER); |
| ill = ILL_START_WALK_V4(&ctx); |
| for (; ill != NULL; ill = ill_next(&ctx, ill)) { |
| ASSERT(!ill->ill_isv6); |
| /* |
| * We may not be able to refhold the ill if the ill/ipif |
| * 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; |
| rw_exit(&ill_g_lock); |
| success = ipsq_enter(ill, B_TRUE); |
| if (success) { |
| next = igmp_timeout_handler_per_ill(ill, elapsed); |
| if (next < global_next) |
| global_next = next; |
| ipsq_exit(ill->ill_phyint->phyint_ipsq, B_FALSE, |
| B_TRUE); |
| } |
| rw_enter(&ill_g_lock, RW_READER); |
| ill_waiter_dcr(ill); |
| } |
| rw_exit(&ill_g_lock); |
| |
| mutex_enter(&igmp_timer_lock); |
| ASSERT(igmp_timeout_id != 0); |
| igmp_timeout_id = 0; |
| mutex_exit(&igmp_timer_lock); |
| |
| if (global_next != INFINITY) |
| igmp_start_timers(global_next); |
| } |
| |
| /* |
| * mld_timeout_handler: |
| * Called when there are timeout events, every next (tick). |
| * Returns number of ticks to next event (or 0 if none). |
| */ |
| /* ARGSUSED */ |
| uint_t |
| mld_timeout_handler_per_ill(ill_t *ill, int elapsed) |
| { |
| ilm_t *ilm; |
| uint_t next = INFINITY; |
| mrec_t *rp, *rtxrp; |
| rtx_state_t *rtxp; |
| mcast_record_t rtype; |
| |
| ASSERT(IAM_WRITER_ILL(ill)); |
| |
| mutex_enter(&ill->ill_lock); |
| |
| /* |
| * 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. |
| */ |
| if (ill->ill_global_timer == INFINITY) |
| goto per_ilm_timer; |
| if (ill->ill_global_timer <= elapsed) { |
| ill->ill_global_timer = INFINITY; |
| /* |
| * 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. |
| */ |
| rp = NULL; |
| for (ilm = ill->ill_ilm; ilm != NULL; ilm = ilm->ilm_next) { |
| if (IN6_ARE_ADDR_EQUAL(&ilm->ilm_v6addr, |
| &ipv6_all_hosts_mcast)) |
| continue; |
| rp = mcast_bldmrec(ilm->ilm_fmode, &ilm->ilm_v6addr, |
| ilm->ilm_filter, rp); |
| /* |
| * 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. |
| */ |
| ilm->ilm_timer = INFINITY; |
| ilm->ilm_state = IGMP_IREPORTEDLAST; |
| FREE_SLIST(ilm->ilm_pendsrcs); |
| ilm->ilm_pendsrcs = NULL; |
| } |
| mutex_exit(&ill->ill_lock); |
| mldv2_sendrpt(ill, rp); |
| mutex_enter(&ill->ill_lock); |
| } else { |
| ill->ill_global_timer -= elapsed; |
| if (ill->ill_global_timer < next) |
| next = ill->ill_global_timer; |
| } |
| |
| per_ilm_timer: |
| rp = rtxrp = NULL; |
| for (ilm = ill->ill_ilm; ilm != NULL; ilm = ilm->ilm_next) { |
| if (ilm->ilm_timer == INFINITY) |
| goto per_ilm_rtxtimer; |
| |
| if (ilm->ilm_timer > elapsed) { |
| ilm->ilm_timer -= elapsed; |
| if (ilm->ilm_timer < next) |
| next = ilm->ilm_timer; |
| |
| if (ip_debug > 1) { |
| (void) mi_strlog(ill->ill_rq, 1, SL_TRACE, |
| "igmp_timo_hlr 2: ilm_timr" |
| " %d elap %d typ %d nxt %d", |
| (int)ntohl(ilm->ilm_timer), elapsed, |
| (ill->ill_mcast_type), next); |
| } |
| |
| goto per_ilm_rtxtimer; |
| } |
| |
| /* the timer has expired, need to take action */ |
| ilm->ilm_timer = INFINITY; |
| ilm->ilm_state = IGMP_IREPORTEDLAST; |
| if (ill->ill_mcast_type == MLD_V1_ROUTER) { |
| mutex_exit(&ill->ill_lock); |
| mld_sendpkt(ilm, MLD_LISTENER_REPORT, NULL); |
| mutex_enter(&ill->ill_lock); |
| } else { |
| slist_t *rsp; |
| if (!SLIST_IS_EMPTY(ilm->ilm_pendsrcs) && |
| (rsp = l_alloc()) != NULL) { |
| /* |
| * Contents of reply depend on pending |
| * requested source list. |
| */ |
| if (ilm->ilm_fmode == MODE_IS_INCLUDE) { |
| l_intersection(ilm->ilm_filter, |
| ilm->ilm_pendsrcs, rsp); |
| } else { |
| l_difference(ilm->ilm_pendsrcs, |
| ilm->ilm_filter, rsp); |
| } |
| FREE_SLIST(ilm->ilm_pendsrcs); |
| ilm->ilm_pendsrcs = NULL; |
| if (!SLIST_IS_EMPTY(rsp)) |
| rp = mcast_bldmrec(MODE_IS_INCLUDE, |
| &ilm->ilm_v6addr, rsp, rp); |
| FREE_SLIST(rsp); |
| } else { |
| rp = mcast_bldmrec(ilm->ilm_fmode, |
| &ilm->ilm_v6addr, ilm->ilm_filter, rp); |
| } |
| } |
| |
| if (ip_debug > 1) { |
| (void) mi_strlog(ill->ill_rq, 1, SL_TRACE, |
| "igmp_timo_hlr 1: ilm_timr %d elap %d " |
| "typ %d nxt %d", |
| (int)ntohl(ilm->ilm_timer), elapsed, |
| (ill->ill_mcast_type), next); |
| } |
| |
| per_ilm_rtxtimer: |
| rtxp = &ilm->ilm_rtx; |
| |
| if (rtxp->rtx_timer == INFINITY) |
| continue; |
| if (rtxp->rtx_timer > elapsed) { |
| rtxp->rtx_timer -= elapsed; |
| if (rtxp->rtx_timer < next) |
| next = rtxp->rtx_timer; |
| continue; |
| } |
| |
| rtxp->rtx_timer = INFINITY; |
| ilm->ilm_state = IGMP_IREPORTEDLAST; |
| if (ill->ill_mcast_type == MLD_V1_ROUTER) { |
| mutex_exit(&ill->ill_lock); |
| mld_sendpkt(ilm, MLD_LISTENER_REPORT, NULL); |
| mutex_enter(&ill->ill_lock); |
| 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. |
| */ |
| ASSERT(rtxp->rtx_cnt > 0); |
| ASSERT(rtxp->rtx_cnt >= rtxp->rtx_fmode_cnt); |
| rtxp->rtx_cnt--; |
| if (rtxp->rtx_fmode_cnt > 0) { |
| rtxp->rtx_fmode_cnt--; |
| rtype = (ilm->ilm_fmode == MODE_IS_INCLUDE) ? |
| CHANGE_TO_INCLUDE : CHANGE_TO_EXCLUDE; |
| rtxrp = mcast_bldmrec(rtype, &ilm->ilm_v6addr, |
| ilm->ilm_filter, rtxrp); |
| } else { |
| rtxrp = mcast_bldmrec(ALLOW_NEW_SOURCES, |
| &ilm->ilm_v6addr, rtxp->rtx_allow, rtxrp); |
| rtxrp = mcast_bldmrec(BLOCK_OLD_SOURCES, |
| &ilm->ilm_v6addr, rtxp->rtx_block, rtxrp); |
| } |
| if (rtxp->rtx_cnt > 0) { |
| MCAST_RANDOM_DELAY(rtxp->rtx_timer, |
| SEC_TO_MSEC(ICMP6_MAX_HOST_REPORT_DELAY)); |
| if (rtxp->rtx_timer < next) |
| next = rtxp->rtx_timer; |
| } else { |
| CLEAR_SLIST(rtxp->rtx_allow); |
| CLEAR_SLIST(rtxp->rtx_block); |
| } |
| } |
| |
| if (ill->ill_mcast_type == MLD_V2_ROUTER) { |
| mutex_exit(&ill->ill_lock); |
| mldv2_sendrpt(ill, rp); |
| mldv2_sendrpt(ill, rtxrp); |
| return (next); |
| } |
| |
| mutex_exit(&ill->ill_lock); |
| |
| 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 |
| */ |
| /* ARGSUSED */ |
| void |
| mld_timeout_handler(void *arg) |
| { |
| ill_t *ill; |
| int elapsed; /* Since last call */ |
| uint_t global_next = INFINITY; |
| uint_t next; |
| ill_walk_context_t ctx; |
| boolean_t success; |
| |
| mutex_enter(&mld_timer_lock); |
| ASSERT(mld_timeout_id != 0); |
| mld_timer_fired_last = ddi_get_lbolt(); |
| elapsed = mld_time_to_next; |
| mld_time_to_next = 0; |
| mutex_exit(&mld_timer_lock); |
| |
| rw_enter(&ill_g_lock, RW_READER); |
| ill = ILL_START_WALK_V6(&ctx); |
| for (; ill != NULL; ill = ill_next(&ctx, ill)) { |
| ASSERT(ill->ill_isv6); |
| /* |
| * We may not be able to refhold the ill if the ill/ipif |
| * 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; |
| rw_exit(&ill_g_lock); |
| success = ipsq_enter(ill, B_TRUE); |
| if (success) { |
| next = mld_timeout_handler_per_ill(ill, elapsed); |
| if (next < global_next) |
| global_next = next; |
| ipsq_exit(ill->ill_phyint->phyint_ipsq, B_TRUE, |
| B_FALSE); |
| } |
| rw_enter(&ill_g_lock, RW_READER); |
| ill_waiter_dcr(ill); |
| } |
| rw_exit(&ill_g_lock); |
| |
| mutex_enter(&mld_timer_lock); |
| ASSERT(mld_timeout_id != 0); |
| mld_timeout_id = 0; |
| mutex_exit(&mld_timer_lock); |
| |
| if (global_next != INFINITY) |
| mld_start_timers(global_next); |
| } |
| |
| /* |
| * Calculate the Older Version Querier Present timeout value, in number |
| * of slowtimo intervals, for the given ill. |
| */ |
| #define OVQP(ill) \ |
| ((1000 * (((ill)->ill_mcast_rv * (ill)->ill_mcast_qi) \ |
| + MCAST_QUERY_RESP_INTERVAL)) / MCAST_SLOWTIMO_INTERVAL) |
| |
| /* |
| * igmp_slowtimo: |
| * - Resets to new router if we didnt we hear from the router |
| * in IGMP_AGE_THRESHOLD seconds. |
| * - Resets slowtimeout. |
| */ |
| /* ARGSUSED */ |
| void |
| igmp_slowtimo(void *arg) |
| { |
| ill_t *ill; |
| ill_if_t *ifp; |
| avl_tree_t *avl_tree; |
| |
| /* Hold the ill_g_lock so that we can safely walk the ill list */ |
| rw_enter(&ill_g_lock, RW_READER); |
| |
| /* |
| * 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). |
| */ |
| for (ifp = IP_V4_ILL_G_LIST; ifp != (ill_if_t *)&IP_V4_ILL_G_LIST; |
| ifp = ifp->illif_next) { |
| /* |
| * 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. |
| */ |
| if (ifp->illif_mcast_v1 == 0 && ifp->illif_mcast_v2 == 0) |
| continue; |
| |
| avl_tree = &ifp->illif_avl_by_ppa; |
| for (ill = avl_first(avl_tree); ill != NULL; |
| ill = avl_walk(avl_tree, ill, AVL_AFTER)) { |
| mutex_enter(&ill->ill_lock); |
| if (ill->ill_mcast_v1_tset == 1) |
| ill->ill_mcast_v1_time++; |
| if (ill->ill_mcast_v2_tset == 1) |
| ill->ill_mcast_v2_time++; |
| if (ill->ill_mcast_type == IGMP_V1_ROUTER) { |
| if (ill->ill_mcast_v1_time >= OVQP(ill)) { |
| if (ill->ill_mcast_v2_tset > 0) { |
| ip1dbg(("V1 query timer " |
| "expired on %s; switching " |
| "mode to IGMP_V2\n", |
| ill->ill_name)); |
| ill->ill_mcast_type = |
| IGMP_V2_ROUTER; |
| } else { |
| ip1dbg(("V1 query timer " |
| "expired on %s; switching " |
| "mode to IGMP_V3\n", |
| ill->ill_name)); |
| ill->ill_mcast_type = |
| IGMP_V3_ROUTER; |
| } |
| ill->ill_mcast_v1_time = 0; |
| ill->ill_mcast_v1_tset = 0; |
| atomic_add_16(&ifp->illif_mcast_v1, -1); |
| } |
| } |
| if (ill->ill_mcast_type == IGMP_V2_ROUTER) { |
| if (ill->ill_mcast_v2_time >= OVQP(ill)) { |
| ip1dbg(("V2 query timer expired on " |
| "%s; switching mode to IGMP_V3\n", |
| ill->ill_name)); |
| ill->ill_mcast_type = IGMP_V3_ROUTER; |
| ill->ill_mcast_v2_time = 0; |
| ill->ill_mcast_v2_tset = 0; |
| atomic_add_16(&ifp->illif_mcast_v2, -1); |
| } |
| } |
| mutex_exit(&ill->ill_lock); |
| } |
| |
| } |
| rw_exit(&ill_g_lock); |
| mutex_enter(&igmp_slowtimeout_lock); |
| igmp_slowtimeout_id = timeout(igmp_slowtimo, NULL, |
| MSEC_TO_TICK(MCAST_SLOWTIMO_INTERVAL)); |
| mutex_exit(&igmp_slowtimeout_lock); |
| } |
| |
| /* |
| * 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) |
| { |
| ill_t *ill; |
| ill_if_t *ifp; |
| avl_tree_t *avl_tree; |
| |
| /* See comments in igmp_slowtimo() above... */ |
| rw_enter(&ill_g_lock, RW_READER); |
| for (ifp = IP_V6_ILL_G_LIST; ifp != (ill_if_t *)&IP_V6_ILL_G_LIST; |
| ifp = ifp->illif_next) { |
| |
| if (ifp->illif_mcast_v1 == 0) |
| continue; |
| |
| avl_tree = &ifp->illif_avl_by_ppa; |
| for (ill = avl_first(avl_tree); ill != NULL; |
| ill = avl_walk(avl_tree, ill, AVL_AFTER)) { |
| mutex_enter(&ill->ill_lock); |
| if (ill->ill_mcast_v1_tset == 1) |
| ill->ill_mcast_v1_time++; |
| if (ill->ill_mcast_type == MLD_V1_ROUTER) { |
| if (ill->ill_mcast_v1_time >= OVQP(ill)) { |
| ip1dbg(("MLD query timer expired on" |
| " %s; switching mode to MLD_V2\n", |
| ill->ill_name)); |
| ill->ill_mcast_type = MLD_V2_ROUTER; |
| ill->ill_mcast_v1_time = 0; |
| ill->ill_mcast_v1_tset = 0; |
| atomic_add_16(&ifp->illif_mcast_v1, -1); |
| } |
| } |
| mutex_exit(&ill->ill_lock); |
| } |
| } |
| rw_exit(&ill_g_lock); |
| mutex_enter(&mld_slowtimeout_lock); |
| mld_slowtimeout_id = timeout(mld_slowtimo, NULL, |
| MSEC_TO_TICK(MCAST_SLOWTIMO_INTERVAL)); |
| mutex_exit(&mld_slowtimeout_lock); |
| } |
| |
| /* |
| * 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 |
| igmp_sendpkt(ilm_t *ilm, uchar_t type, ipaddr_t addr) |
| { |
| mblk_t *mp; |
| igmpa_t *igmpa; |
| uint8_t *rtralert; |
| ipha_t *ipha; |
| int hdrlen = sizeof (ipha_t) + RTRALERT_LEN; |
| size_t size = hdrlen + sizeof (igmpa_t); |
| ipif_t *ipif = ilm->ilm_ipif; |
| ill_t *ill = ipif->ipif_ill; /* Will be the "lower" ill */ |
| mblk_t *first_mp; |
| ipsec_out_t *io; |
| zoneid_t zoneid; |
| |
| /* |
| * 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. |
| */ |
| first_mp = allocb(sizeof (ipsec_info_t), BPRI_HI); |
| if (first_mp == NULL) |
| return; |
| |
| first_mp->b_datap->db_type = M_CTL; |
| first_mp->b_wptr += sizeof (ipsec_info_t); |
| bzero(first_mp->b_rptr, sizeof (ipsec_info_t)); |
| /* ipsec_out_secure is B_FALSE now */ |
| io = (ipsec_out_t *)first_mp->b_rptr; |
| io->ipsec_out_type = IPSEC_OUT; |
| io->ipsec_out_len = sizeof (ipsec_out_t); |
| io->ipsec_out_use_global_policy = B_TRUE; |
| io->ipsec_out_ill_index = ill->ill_phyint->phyint_ifindex; |
| io->ipsec_out_attach_if = B_TRUE; |
| io->ipsec_out_multicast_loop = B_FALSE; |
| io->ipsec_out_dontroute = B_TRUE; |
| if ((zoneid = ilm->ilm_zoneid) == ALL_ZONES) |
| zoneid = GLOBAL_ZONEID; |
| io->ipsec_out_zoneid = zoneid; |
| |
| mp = allocb(size, BPRI_HI); |
| if (mp == NULL) { |
| freemsg(first_mp); |
| return; |
| } |
| mp->b_wptr = mp->b_rptr + size; |
| first_mp->b_cont = mp; |
| |
| ipha = (ipha_t *)mp->b_rptr; |
| rtralert = (uint8_t *)&(ipha[1]); |
| igmpa = (igmpa_t *)&(rtralert[RTRALERT_LEN]); |
| igmpa->igmpa_type = type; |
| igmpa->igmpa_code = 0; |
| igmpa->igmpa_group = ilm->ilm_addr; |
| igmpa->igmpa_cksum = 0; |
| igmpa->igmpa_cksum = IP_CSUM(mp, hdrlen, 0); |
| if (igmpa->igmpa_cksum == 0) |
| igmpa->igmpa_cksum = 0xffff; |
| |
| rtralert[0] = IPOPT_COPY & IPOPT_RTRALERT; |
| rtralert[1] = RTRALERT_LEN; |
| rtralert[2] = 0; |
| rtralert[3] = 0; |
| |
| ipha->ipha_version_and_hdr_length = (IP_VERSION << 4) |
| | (IP_SIMPLE_HDR_LENGTH_IN_WORDS + RTRALERT_LEN_IN_WORDS); |
| ipha->ipha_type_of_service = 0; |
| ipha->ipha_length = htons(size); |
| ipha->ipha_ident = 0; |
| ipha->ipha_fragment_offset_and_flags = 0; |
| ipha->ipha_ttl = IGMP_TTL; |
| ipha->ipha_protocol = IPPROTO_IGMP; |
| ipha->ipha_hdr_checksum = 0; |
| ipha->ipha_dst = addr ? addr : igmpa->igmpa_group; |
| ipha->ipha_src = ipif->ipif_src_addr; |
| /* |
| * 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. |
| */ |
| ASSERT(ill->ill_rq != NULL); |
| ip_multicast_loopback(ill->ill_rq, ill, first_mp, 0, ilm->ilm_zoneid); |
| |
| ip_wput_multicast(ill->ill_wq, first_mp, ipif); |
| |
| ++igmpstat.igps_snd_reports; |
| } |
| |
| /* |
| * 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 |
| igmpv3_sendrpt(ipif_t *ipif, mrec_t *reclist) |
| { |
| ipsec_out_t *io; |
| igmp3ra_t *igmp3ra; |
| grphdra_t *grphdr; |
| mblk_t *first_mp, *mp; |
| ipha_t *ipha; |
| uint8_t *rtralert; |
| ipaddr_t *src_array; |
| int i, j, numrec, more_src_cnt; |
| size_t hdrsize, size, rsize; |
| ill_t *ill = ipif->ipif_ill; |
| mrec_t *rp, *cur_reclist; |
| mrec_t *next_reclist = reclist; |
| boolean_t morepkts; |
| zoneid_t zoneid; |
| |
| /* if there aren't any records, there's nothing to send */ |
| if (reclist == NULL) |
| return; |
| |
| hdrsize = sizeof (ipha_t) + RTRALERT_LEN; |
| nextpkt: |
| size = hdrsize + sizeof (igmp3ra_t); |
| morepkts = B_FALSE; |
| more_src_cnt = 0; |
| cur_reclist = next_reclist; |
| numrec = 0; |
| for (rp = cur_reclist; rp != NULL; rp = rp->mrec_next) { |
| rsize = sizeof (grphdra_t) + |
| (rp->mrec_srcs.sl_numsrc * sizeof (ipaddr_t)); |
| if (size + rsize > ill->ill_max_frag) { |
| 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; |
| srcspace = ill->ill_max_frag - (size + |
| sizeof (grphdra_t)); |
| srcsperpkt = srcspace / sizeof (ipaddr_t); |
| /* |
| * Increment size and numrec, because we will |
| * be sending a record for the mrec we're |
| * looking at now. |
| */ |
| size += sizeof (grphdra_t) + |
| (srcsperpkt * sizeof (ipaddr_t)); |
| numrec++; |
| if (rp->mrec_type == MODE_IS_EXCLUDE || |
| rp->mrec_type == CHANGE_TO_EXCLUDE) { |
| rp->mrec_srcs.sl_numsrc = srcsperpkt; |
| if (rp->mrec_next == NULL) { |
| /* no more packets to send */ |
| break; |
| } else { |
| /* |
| * more packets, but we're |
| * done with this mrec. |
| */ |
| next_reclist = rp->mrec_next; |
| } |
| } else { |
| more_src_cnt = rp->mrec_srcs.sl_numsrc |
| - srcsperpkt; |
| rp->mrec_srcs.sl_numsrc = 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; |
| } |
| morepkts = B_TRUE; |
| break; |
| } |
| size += rsize; |
| numrec++; |
| } |
| |
| /* |
| * See comments in igmp_sendpkt() about initializing for ipsec and |
| * load balancing requirements. |
| */ |
| first_mp = allocb(sizeof (ipsec_info_t), BPRI_HI); |
| if (first_mp == NULL) |
| goto free_reclist; |
| |
| first_mp->b_datap->db_type = M_CTL; |
| first_mp->b_wptr += sizeof (ipsec_info_t); |
| bzero(first_mp->b_rptr, sizeof (ipsec_info_t)); |
| /* ipsec_out_secure is B_FALSE now */ |
| io = (ipsec_out_t *)first_mp->b_rptr; |
| io->ipsec_out_type = IPSEC_OUT; |
| io->ipsec_out_len = sizeof (ipsec_out_t); |
| io->ipsec_out_use_global_policy = B_TRUE; |
| io->ipsec_out_ill_index = ill->ill_phyint->phyint_ifindex; |
| io->ipsec_out_attach_if = B_TRUE; |
| io->ipsec_out_multicast_loop = B_FALSE; |
| io->ipsec_out_dontroute = B_TRUE; |
| if ((zoneid = ipif->ipif_zoneid) == ALL_ZONES) |
| zoneid = GLOBAL_ZONEID; |
| io->ipsec_out_zoneid = zoneid; |
| |
| mp = allocb(size, BPRI_HI); |
| if (mp == NULL) { |
| freemsg(first_mp); |
| goto free_reclist; |
| } |
| bzero((char *)mp->b_rptr, size); |
| mp->b_wptr = (uchar_t *)(mp->b_rptr + size); |
| first_mp->b_cont = mp; |
| |
| ipha = (ipha_t *)mp->b_rptr; |
| rtralert = (uint8_t *)&(ipha[1]); |
| igmp3ra = (igmp3ra_t *)&(rtralert[RTRALERT_LEN]); |
| grphdr = (grphdra_t *)&(igmp3ra[1]); |
| |
| rp = cur_reclist; |
| for (i = 0; i < numrec; i++) { |
| grphdr->grphdra_type = rp->mrec_type; |
| grphdr->grphdra_numsrc = htons(rp->mrec_srcs.sl_numsrc); |
| grphdr->grphdra_group = V4_PART_OF_V6(rp->mrec_group); |
| src_array = (ipaddr_t *)&(grphdr[1]); |
| |
| for (j = 0; j < rp->mrec_srcs.sl_numsrc; j++) |
| src_array[j] = V4_PART_OF_V6(rp->mrec_srcs.sl_addr[j]); |
| |
| grphdr = (grphdra_t *)&(src_array[j]); |
| rp = rp->mrec_next; |
| } |
| |
| igmp3ra->igmp3ra_type = IGMP_V3_MEMBERSHIP_REPORT; |
| igmp3ra->igmp3ra_numrec = htons(numrec); |
| igmp3ra->igmp3ra_cksum = IP_CSUM(mp, hdrsize, 0); |
| |
| rtralert[0] = IPOPT_COPY & IPOPT_RTRALERT; |
| rtralert[1] = RTRALERT_LEN; |
| rtralert[2] = 0; |
| rtralert[3] = 0; |
| |
| ipha->ipha_version_and_hdr_length = IP_VERSION << 4 |
| | (IP_SIMPLE_HDR_LENGTH_IN_WORDS + RTRALERT_LEN_IN_WORDS); |
| ipha->ipha_type_of_service = IPTOS_PREC_INTERNETCONTROL; |
| ipha->ipha_length = htons(size); |
| ipha->ipha_ttl = IGMP_TTL; |
| ipha->ipha_protocol = IPPROTO_IGMP; |
| ipha->ipha_dst = htonl(INADDR_ALLRPTS_GROUP); |
| ipha->ipha_src = ipif->ipif_src_addr; |
| |
| /* |
| * 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. |
| */ |
| ASSERT(ill->ill_rq != NULL); |
| ip_multicast_loopback(ill->ill_rq, ill, mp, 0, ipif->ipif_zoneid); |
| |
| ip_wput_multicast(ill->ill_wq, first_mp, ipif); |
| |
| ++igmpstat.igps_snd_reports; |
| |
| if (morepkts) { |
| if (more_src_cnt > 0) { |
| int index, mvsize; |
| slist_t *sl = &next_reclist->mrec_srcs; |
| index = sl->sl_numsrc; |
| mvsize = more_src_cnt * sizeof (in6_addr_t); |
| (void) memmove(&sl->sl_addr[0], &sl->sl_addr[index], |
| mvsize); |
| sl->sl_numsrc = more_src_cnt; |
| } |
| goto nextpkt; |
| } |
| |
| free_reclist: |
| while (reclist != NULL) { |
| rp = reclist->mrec_next; |
| mi_free(reclist); |
| reclist = rp; |
| } |
| } |
| |
| /* |
| * mld_input: |
| */ |
| /* ARGSUSED */ |
| void |
| mld_input(queue_t *q, mblk_t *mp, ill_t *ill) |
| { |
| ip6_t *ip6h = (ip6_t *)(mp->b_rptr); |
| mld_hdr_t *mldh; |
| ilm_t *ilm; |
| ipif_t *ipif; |
| uint16_t hdr_length, exthdr_length; |
| in6_addr_t *v6group_ptr, *lcladdr_ptr; |
| uint_t next; |
| int mldlen; |
| |
| BUMP_MIB(ill->ill_icmp6_mib, ipv6IfIcmpInGroupMembTotal); |
| |
| /* Make sure the src address of the packet is link-local */ |
| if (!(IN6_IS_ADDR_LINKLOCAL(&ip6h->ip6_src))) { |
| BUMP_MIB(ill->ill_icmp6_mib, ipv6IfIcmpInErrors); |
| freemsg(mp); |
| return; |
| } |
| |
| if (ip6h->ip6_hlim != 1) { |
| BUMP_MIB(ill->ill_icmp6_mib, ipv6IfIcmpBadHoplimit); |
| freemsg(mp); |
| return; |
| } |
| |
| /* Get to the icmp header part */ |
| if (ip6h->ip6_nxt != IPPROTO_ICMPV6) { |
| hdr_length = ip_hdr_length_v6(mp, ip6h); |
| exthdr_length = hdr_length - IPV6_HDR_LEN; |
| } else { |
| hdr_length = IPV6_HDR_LEN; |
| exthdr_length = 0; |
| } |
| mldlen = ntohs(ip6h->ip6_plen) - exthdr_length; |
| |
| /* An MLD packet must at least be 24 octets to be valid */ |
| if (mldlen < MLD_MINLEN) { |
| BUMP_MIB(ill->ill_icmp6_mib, ipv6IfIcmpInErrors); |
| freemsg(mp); |
| return; |
| } |
| |
| mldh = (mld_hdr_t *)(&mp->b_rptr[hdr_length]); |
| |
| switch (mldh->mld_type) { |
| 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) { |
| next = mld_query_in(mldh, ill); |
| } else if (mldlen >= MLD_V2_QUERY_MINLEN) { |
| next = mldv2_query_in((mld2q_t *)mldh, ill, mldlen); |
| } else { |
| BUMP_MIB(ill->ill_icmp6_mib, ipv6IfIcmpInErrors); |
| freemsg(mp); |
| return; |
| } |
| if (next == 0) { |
| freemsg(mp); |
| return; |
| } |
| |
| if (next != INFINITY) |
| mld_start_timers(next); |
| break; |
| |
| case MLD_LISTENER_REPORT: { |
| |
| ASSERT(ill->ill_ipif != NULL); |
| /* |
| * 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 = &(ill->ill_ipif->ipif_v6subnet); |
| mutex_enter(&ill->ill_lock); |
| for (ipif = ill->ill_ipif; ipif != NULL; |
| ipif = ipif->ipif_next) { |
| if (IN6_ARE_ADDR_EQUAL(&ipif->ipif_v6lcl_addr, |
| lcladdr_ptr)) { |
| if (ip_debug > 1) { |
| char buf1[INET6_ADDRSTRLEN]; |
| char buf2[INET6_ADDRSTRLEN]; |
| |
| (void) mi_strlog(ill->ill_rq, |
| 1, |
| SL_TRACE, |
| "mld_input: we are only " |
| "member src %s ipif_local %s", |
| inet_ntop(AF_INET6, lcladdr_ptr, |
| buf1, sizeof (buf1)), |
| inet_ntop(AF_INET6, |
| &ipif->ipif_v6lcl_addr, |
| buf2, sizeof (buf2))); |
| } |
| mutex_exit(&ill->ill_lock); |
| freemsg(mp); |
| return; |
| } |
| } |
| mutex_exit(&ill->ill_lock); |
| BUMP_MIB(ill->ill_icmp6_mib, ipv6IfIcmpInGroupMembResponses); |
| |
| v6group_ptr = &mldh->mld_addr; |
| if (!IN6_IS_ADDR_MULTICAST(v6group_ptr)) { |
| BUMP_MIB(ill->ill_icmp6_mib, |
| ipv6IfIcmpInGroupMembBadReports); |
| freemsg(mp); |
| 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. |
| */ |
| mutex_enter(&ill->ill_lock); |
| for (ilm = ill->ill_ilm; ilm != NULL; ilm = ilm->ilm_next) { |
| if (!IN6_ARE_ADDR_EQUAL(&ilm->ilm_v6addr, v6group_ptr)) |
| continue; |
| BUMP_MIB(ill->ill_icmp6_mib, |
| ipv6IfIcmpInGroupMembOurReports); |
| |
| ilm->ilm_timer = INFINITY; |
| ilm->ilm_state = IGMP_OTHERMEMBER; |
| } |
| mutex_exit(&ill->ill_lock); |
| break; |
| } |
| case MLD_LISTENER_REDUCTION: |
| BUMP_MIB(ill->ill_icmp6_mib, ipv6IfIcmpInGroupMembReductions); |
| 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 |
| */ |
| freemsg(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 |
| mld_query_in(mld_hdr_t *mldh, ill_t *ill) |
| { |
| ilm_t *ilm; |
| int timer; |
| uint_t next; |
| in6_addr_t *v6group; |
| |
| BUMP_MIB(ill->ill_icmp6_mib, ipv6IfIcmpInGroupMembQueries); |
| |
| /* |
| * 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. |
| */ |
| v6group = &mldh->mld_addr; |
| if (!(IN6_IS_ADDR_UNSPECIFIED(v6group)) && |
| ((!IN6_IS_ADDR_MULTICAST(v6group)))) { |
| BUMP_MIB(ill->ill_icmp6_mib, ipv6IfIcmpInGroupMembBadQueries); |
| return (0); |
| } |
| |
| /* Need to do compatibility mode checking */ |
| mutex_enter(&ill->ill_lock); |
| ill->ill_mcast_v1_time = 0; |
| ill->ill_mcast_v1_tset = 1; |
| if (ill->ill_mcast_type == MLD_V2_ROUTER) { |
| ip1dbg(("Received MLDv1 Query on %s, switching mode to " |
| "MLD_V1_ROUTER\n", ill->ill_name)); |
| atomic_add_16(&ill->ill_ifptr->illif_mcast_v1, 1); |
| ill->ill_mcast_type = MLD_V1_ROUTER; |
| } |
| mutex_exit(&ill->ill_lock); |
| |
| timer = (int)ntohs(mldh->mld_maxdelay); |
| if (ip_debug > 1) { |
| (void) mi_strlog(ill->ill_rq, 1, SL_TRACE, |
| "mld_input: TIMER = mld_maxdelay %d mld_type 0x%x", |
| timer, (int)mldh->mld_type); |
| } |
| |
| /* |
| * -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. |
| */ |
| next = INFINITY; |
| mutex_enter(&ill->ill_lock); |
| for (ilm = ill->ill_ilm; ilm != NULL; ilm = ilm->ilm_next) { |
| ASSERT(!IN6_IS_ADDR_V4MAPPED(&ilm->ilm_v6addr)); |
| |
| if (IN6_IS_ADDR_UNSPECIFIED(&ilm->ilm_v6addr) || |
| IN6_IS_ADDR_MC_NODELOCAL(&ilm->ilm_v6addr) || |
| IN6_IS_ADDR_MC_RESERVED(&ilm->ilm_v6addr)) |
| continue; |
| if ((!IN6_ARE_ADDR_EQUAL(&ilm->ilm_v6addr, |
| &ipv6_all_hosts_mcast)) && |
| (IN6_IS_ADDR_UNSPECIFIED(v6group)) || |
| (IN6_ARE_ADDR_EQUAL(v6group, &ilm->ilm_v6addr))) { |
| if (timer == 0) { |
| /* Respond immediately */ |
| ilm->ilm_timer = INFINITY; |
| ilm->ilm_state = IGMP_IREPORTEDLAST; |
| mutex_exit(&ill->ill_lock); |
| mld_sendpkt(ilm, MLD_LISTENER_REPORT, NULL); |
| mutex_enter(&ill->ill_lock); |
| break; |
| } |
| if (ilm->ilm_timer > timer) { |
| MCAST_RANDOM_DELAY(ilm->ilm_timer, timer); |
| if (ilm->ilm_timer < next) |
| next = ilm->ilm_timer; |
| } |
| break; |
| } |
| } |
| mutex_exit(&ill->ill_lock); |
| |
| 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 |
| mldv2_query_in(mld2q_t *mld2q, ill_t *ill, int mldlen) |
| { |
| ilm_t *ilm; |
| in6_addr_t *v6group, *src_array; |
| uint_t next, numsrc, i, mrd, delay, qqi; |
| uint8_t qrv; |
| |
| v6group = &mld2q->mld2q_addr; |
| numsrc = ntohs(mld2q->mld2q_numsrc); |
| |
| /* make sure numsrc matches packet size */ |
| if (mldlen < MLD_V2_QUERY_MINLEN + (numsrc * sizeof (in6_addr_t))) { |
| BUMP_MIB(ill->ill_icmp6_mib, ipv6IfIcmpInErrors); |
| return (0); |
| } |
| src_array = (in6_addr_t *)&mld2q[1]; |
| |
| BUMP_MIB(ill->ill_icmp6_mib, ipv6IfIcmpInGroupMembQueries); |
| |
| /* extract Maximum Response Delay from code in header */ |
| mrd = ntohs(mld2q->mld2q_mxrc); |
| if (mrd >= MLD_V2_MAXRT_FPMIN) { |
| uint_t hdrval, mant, exp; |
| hdrval = mrd; |
| mant = hdrval & MLD_V2_MAXRT_MANT_MASK; |
| exp = (hdrval & MLD_V2_MAXRT_EXP_MASK) >> 12; |
| mrd = (mant | 0x1000) << (exp + 3); |
| } |
| MCAST_RANDOM_DELAY(delay, mrd); |
| next = (unsigned)INFINITY; |
| |
| if ((qrv = mld2q->mld2q_sqrv & MLD_V2_RV_MASK) == 0) |
| ill->ill_mcast_rv = MCAST_DEF_ROBUSTNESS; |
| else |
| ill->ill_mcast_rv = qrv; |
| |
| if ((qqi = (uint_t)mld2q->mld2q_qqic) >= MLD_V2_QQI_FPMIN) { |
| uint_t mant, exp; |
| mant = qqi & MLD_V2_QQI_MANT_MASK; |
| exp = (qqi & MLD_V2_QQI_EXP_MASK) >> 12; |
| qqi = (mant | 0x10) << (exp + 3); |
| } |
| ill->ill_mcast_qi = (qqi == 0) ? MCAST_DEF_QUERY_INTERVAL : qqi; |
| |
| /* |
| * 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) |
| */ |
| mutex_enter(&ill->ill_lock); |
| if (ill->ill_global_timer < delay) { |
| mutex_exit(&ill->ill_lock); |
| return (next); |
| } |
| mutex_exit(&ill->ill_lock); |
| |
| /* |
| * Now take action depending on query type: general, |
| * group specific, or group/source specific. |
| */ |
| if ((numsrc == 0) && IN6_IS_ADDR_UNSPECIFIED(v6group)) { |
| /* |
| * 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]) |
| */ |
| mutex_enter(&ill->ill_lock); |
| ill->ill_global_timer = delay; |
| next = ill->ill_global_timer; |
| mutex_exit(&ill->ill_lock); |
| |
| } else { |
| /* group or group/source specific query */ |
| mutex_enter(&ill->ill_lock); |
| for (ilm = ill->ill_ilm; ilm != NULL; ilm = ilm->ilm_next) { |
| if (IN6_IS_ADDR_UNSPECIFIED(&ilm->ilm_v6addr) || |
| IN6_IS_ADDR_MC_NODELOCAL(&ilm->ilm_v6addr) || |
| IN6_IS_ADDR_MC_RESERVED(&ilm->ilm_v6addr) || |
| !IN6_ARE_ADDR_EQUAL(v6group, &ilm->ilm_v6addr)) |
| 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. |
| */ |
| if (numsrc == 0 || (ilm->ilm_timer < INFINITY && |
| SLIST_IS_EMPTY(ilm->ilm_pendsrcs))) { |
| group_query: |
| FREE_SLIST(ilm->ilm_pendsrcs); |
| ilm->ilm_pendsrcs = NULL; |
| } else { |
| boolean_t overflow; |
| slist_t *pktl; |
| if (numsrc > MAX_FILTER_SIZE || |
| (ilm->ilm_pendsrcs == NULL && |
| (ilm->ilm_pendsrcs = l_alloc()) == NULL)) { |
| /* |
| * 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; |
| } |
| if ((pktl = l_alloc()) == NULL) |
| goto group_query; |
| pktl->sl_numsrc = numsrc; |
| for (i = 0; i < numsrc; i++) |
| pktl->sl_addr[i] = src_array[i]; |
| l_union_in_a(ilm->ilm_pendsrcs, pktl, |
| &overflow); |
| l_free(pktl); |
| if (overflow) |
| goto group_query; |
| } |
| /* set timer to soonest value */ |
| ilm->ilm_timer = MIN(ilm->ilm_timer, delay); |
| if (ilm->ilm_timer < next) |
| next = ilm->ilm_timer; |
| break; |
| } |
| mutex_exit(&ill->ill_lock); |
| } |
| |
| return (next); |
| } |
| |
| /* |
| * Send MLDv1 response packet with hoplimit 1 |
| */ |
| static void |
| mld_sendpkt(ilm_t *ilm, uchar_t type, const in6_addr_t *v6addr) |
| { |
| mblk_t *mp; |
| mld_hdr_t *mldh; |
| ip6_t *ip6h; |
| ip6_hbh_t *ip6hbh; |
| struct ip6_opt_router *ip6router; |
| size_t size = IPV6_HDR_LEN + sizeof (mld_hdr_t); |
| ill_t *ill = ilm->ilm_ill; /* Will be the "lower" ill */ |
| ipif_t *ipif; |
| ip6i_t *ip6i; |
| |
| /* |
| * 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; |
| |
| ASSERT(ill->ill_isv6); |
| |
| /* |
| * 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. |
| */ |
| size += sizeof (ip6i_t) + router_alert_length; |
| mp = allocb(size, BPRI_HI); |
| if (mp == NULL) |
| return; |
| bzero(mp->b_rptr, size); |
| mp->b_wptr = mp->b_rptr + size; |
| |
| ip6i = (ip6i_t *)mp->b_rptr; |
| ip6i->ip6i_vcf = IPV6_DEFAULT_VERS_AND_FLOW; |
| ip6i->ip6i_nxt = IPPROTO_RAW; |
| ip6i->ip6i_flags = IP6I_ATTACH_IF | IP6I_HOPLIMIT; |
| ip6i->ip6i_ifindex = ill->ill_phyint->phyint_ifindex; |
| |
| ip6h = (ip6_t *)&ip6i[1]; |
| ip6hbh = (struct ip6_hbh *)&ip6h[1]; |
| ip6router = (struct ip6_opt_router *)&ip6hbh[1]; |
| /* |
| * A zero is a pad option of length 1. The bzero of the whole packet |
| * above will pad between ip6router and mld. |
| */ |
| mldh = (mld_hdr_t *)((uint8_t *)ip6hbh + router_alert_length); |
| |
| mldh->mld_type = type; |
| mldh->mld_addr = ilm->ilm_v6addr; |
| |
| ip6router->ip6or_type = IP6OPT_ROUTER_ALERT; |
| ip6router->ip6or_len = 2; |
| ip6router->ip6or_value[0] = 0; |
| ip6router->ip6or_value[1] = IP6_ALERT_MLD; |
| |
| ip6hbh->ip6h_nxt = IPPROTO_ICMPV6; |
| ip6hbh->ip6h_len = 0; |
| |
| ip6h->ip6_vcf = IPV6_DEFAULT_VERS_AND_FLOW; |
| ip6h->ip6_plen = htons(sizeof (*mldh) + router_alert_length); |
| ip6h->ip6_nxt = IPPROTO_HOPOPTS; |
| ip6h->ip6_hops = MLD_HOP_LIMIT; |
| if (v6addr == NULL) |
| ip6h->ip6_dst = ilm->ilm_v6addr; |
| else |
| ip6h->ip6_dst = *v6addr; |
| |
| /* ipif returned by ipif_lookup_zoneid is link-local (if present) */ |
| if (ipif_lookup_zoneid(ill, ilm->ilm_zoneid, IPIF_UP, &ipif)) { |
| ip6h->ip6_src = ipif->ipif_v6src_addr; |
| ipif_refrele(ipif); |
| } else { |
| /* Otherwise, use IPv6 default address selection. */ |
| ip6h->ip6_src = ipv6_all_zeros; |
| } |
| |
| /* |
| * Prepare for checksum by putting icmp length in the icmp |
| * checksum field. The checksum is calculated in ip_wput_v6. |
| */ |
| mldh->mld_cksum = htons(sizeof (*mldh)); |
| |
| /* |
| * 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. |
| */ |
| (void) ip_output_v6(NULL, mp, ill->ill_wq, IP_WPUT); |
| } |
| |
| /* |
| * 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 |
| mldv2_sendrpt(ill_t *ill, mrec_t *reclist) |
| { |
| mblk_t *mp; |
| mld2r_t *mld2r; |
| mld2mar_t *mld2mar; |
| in6_addr_t *srcarray; |
| ip6_t *ip6h; |
| ip6_hbh_t *ip6hbh; |
| ip6i_t *ip6i; |
| struct ip6_opt_router *ip6router; |
| size_t size, optlen, padlen, icmpsize, rsize; |
| ipif_t *ipif; |
| int i, numrec, more_src_cnt; |
| mrec_t *rp, *cur_reclist; |
| mrec_t *next_reclist = reclist; |
| boolean_t morepkts; |
| |
| /* If there aren't any records, there's nothing to send */ |
| if (reclist == NULL) |
| return; |
| |
| ASSERT(ill->ill_isv6); |
| |
| /* |
| * 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. |
| */ |
| optlen = sizeof (ip6_hbh_t) + sizeof (struct ip6_opt_router); |
| ASSERT(optlen <= 8); |
| padlen = 8 - optlen; |
| nextpkt: |
| icmpsize = sizeof (mld2r_t); |
| size = IPV6_HDR_LEN + optlen + padlen + icmpsize; |
| morepkts = B_FALSE; |
| more_src_cnt = 0; |
| for (rp = cur_reclist = next_reclist, numrec = 0; rp != NULL; |
| rp = rp->mrec_next, numrec++) { |
| rsize = sizeof (mld2mar_t) + |
| (rp->mrec_srcs.sl_numsrc * sizeof (in6_addr_t)); |
| if (size + rsize > ill->ill_max_frag) { |
| 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; |
| srcspace = ill->ill_max_frag - |
| (size + sizeof (mld2mar_t)); |
| srcsperpkt = srcspace / sizeof (in6_addr_t); |
| /* |
| * Increment icmpsize and size, because we will |
| * be sending a record for the mrec we're |
| * looking at now. |
| */ |
| rsize = sizeof (mld2mar_t) + |
| (srcsperpkt * sizeof (in6_addr_t)); |
| icmpsize += rsize; |
| size += rsize; |
| if (rp->mrec_type == MODE_IS_EXCLUDE || |
| rp->mrec_type == CHANGE_TO_EXCLUDE) { |
| rp->mrec_srcs.sl_numsrc = srcsperpkt; |
| if (rp->mrec_next == NULL) { |
| /* no more packets to send */ |
| break; |
| } else { |
| /* |
| * more packets, but we're |
| * done with this mrec. |
| */ |
| next_reclist = rp->mrec_next; |
| } |
| } else { |
| more_src_cnt = rp->mrec_srcs.sl_numsrc |
| - srcsperpkt; |
| rp->mrec_srcs.sl_numsrc = 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; |
| } |
| morepkts = B_TRUE; |
| break; |
| } |
| icmpsize += rsize; |
| size += rsize; |
| } |
| |
| /* |
| * 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. |
| */ |
| size += sizeof (ip6i_t); |
| mp = allocb(size, BPRI_HI); |
| if (mp == NULL) |
| goto free_reclist; |
| bzero(mp->b_rptr, size); |
| mp->b_wptr = mp->b_rptr + size; |
| |
| ip6i = (ip6i_t *)mp->b_rptr; |
| ip6i->ip6i_vcf = IPV6_DEFAULT_VERS_AND_FLOW; |
| ip6i->ip6i_nxt = IPPROTO_RAW; |
| ip6i->ip6i_flags = IP6I_ATTACH_IF; |
| ip6i->ip6i_ifindex = ill->ill_phyint->phyint_ifindex; |
|