igmp.c revision 14622
11541Srgrimes/* 21541Srgrimes * Copyright (c) 1988 Stephen Deering. 31541Srgrimes * Copyright (c) 1992, 1993 41541Srgrimes * The Regents of the University of California. All rights reserved. 51541Srgrimes * 61541Srgrimes * This code is derived from software contributed to Berkeley by 71541Srgrimes * Stephen Deering of Stanford University. 81541Srgrimes * 91541Srgrimes * Redistribution and use in source and binary forms, with or without 101541Srgrimes * modification, are permitted provided that the following conditions 111541Srgrimes * are met: 121541Srgrimes * 1. Redistributions of source code must retain the above copyright 131541Srgrimes * notice, this list of conditions and the following disclaimer. 141541Srgrimes * 2. Redistributions in binary form must reproduce the above copyright 151541Srgrimes * notice, this list of conditions and the following disclaimer in the 161541Srgrimes * documentation and/or other materials provided with the distribution. 171541Srgrimes * 3. All advertising materials mentioning features or use of this software 181541Srgrimes * must display the following acknowledgement: 191541Srgrimes * This product includes software developed by the University of 201541Srgrimes * California, Berkeley and its contributors. 211541Srgrimes * 4. Neither the name of the University nor the names of its contributors 221541Srgrimes * may be used to endorse or promote products derived from this software 231541Srgrimes * without specific prior written permission. 241541Srgrimes * 251541Srgrimes * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND 261541Srgrimes * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 271541Srgrimes * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 281541Srgrimes * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE 291541Srgrimes * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 301541Srgrimes * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS 311541Srgrimes * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) 321541Srgrimes * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT 331541Srgrimes * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY 341541Srgrimes * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF 351541Srgrimes * SUCH DAMAGE. 361541Srgrimes * 371541Srgrimes * @(#)igmp.c 8.1 (Berkeley) 7/19/93 3814622Sfenner * $Id: igmp.c,v 1.15 1995/12/09 20:43:51 phk Exp $ 391541Srgrimes */ 401541Srgrimes 412531Swollman/* 422531Swollman * Internet Group Management Protocol (IGMP) routines. 432531Swollman * 442531Swollman * Written by Steve Deering, Stanford, May 1988. 452531Swollman * Modified by Rosen Sharma, Stanford, Aug 1994. 469209Swollman * Modified by Bill Fenner, Xerox PARC, Feb 1995. 4714622Sfenner * Modified to fully comply to IGMPv2 by Bill Fenner, Oct 1995. 482531Swollman * 4914622Sfenner * MULTICAST Revision: 3.5.1.4 502531Swollman */ 511541Srgrimes 521541Srgrimes#include <sys/param.h> 531549Srgrimes#include <sys/systm.h> 541541Srgrimes#include <sys/mbuf.h> 551541Srgrimes#include <sys/socket.h> 561541Srgrimes#include <sys/protosw.h> 5712296Sphk#include <sys/kernel.h> 586472Swollman#include <sys/sysctl.h> 591541Srgrimes 601541Srgrimes#include <net/if.h> 611541Srgrimes#include <net/route.h> 621541Srgrimes 631541Srgrimes#include <netinet/in.h> 641541Srgrimes#include <netinet/in_var.h> 651541Srgrimes#include <netinet/in_systm.h> 661541Srgrimes#include <netinet/ip.h> 671541Srgrimes#include <netinet/ip_var.h> 681541Srgrimes#include <netinet/igmp.h> 691541Srgrimes#include <netinet/igmp_var.h> 701541Srgrimes 7112704Sphkstatic int fill_rti __P((struct in_multi *inm)); 7212704Sphkstatic struct router_info * 7312579Sbde find_rti __P((struct ifnet *ifp)); 7412579Sbde 7512704Sphkstatic struct igmpstat igmpstat; 762531Swollman 7712296SphkSYSCTL_STRUCT(_net_inet_igmp, IGMPCTL_STATS, stats, CTLFLAG_RD, 7812296Sphk &igmpstat, igmpstat, ""); 7912296Sphk 809209Swollmanstatic int igmp_timers_are_running; 811541Srgrimesstatic u_long igmp_all_hosts_group; 8214622Sfennerstatic u_long igmp_all_rtrs_group; 8314622Sfennerstatic struct mbuf *router_alert; 849209Swollmanstatic struct router_info *Head; 851541Srgrimes 8614622Sfennerstatic void igmp_sendpkt(struct in_multi *, int, unsigned long); 871541Srgrimes 881541Srgrimesvoid 891541Srgrimesigmp_init() 901541Srgrimes{ 9114622Sfenner struct ipoption *ra; 9214622Sfenner 931541Srgrimes /* 941541Srgrimes * To avoid byte-swapping the same value over and over again. 951541Srgrimes */ 961541Srgrimes igmp_all_hosts_group = htonl(INADDR_ALLHOSTS_GROUP); 9714622Sfenner igmp_all_rtrs_group = htonl(INADDR_ALLRTRS_GROUP); 989209Swollman 999209Swollman igmp_timers_are_running = 0; 1009209Swollman 10114622Sfenner /* 10214622Sfenner * Construct a Router Alert option to use in outgoing packets 10314622Sfenner */ 10414622Sfenner MGET(router_alert, M_DONTWAIT, MT_DATA); 10514622Sfenner ra = mtod(router_alert, struct ipoption *); 10614622Sfenner ra->ipopt_dst.s_addr = 0; 10714622Sfenner ra->ipopt_list[0] = IPOPT_RA; /* Router Alert Option */ 10814622Sfenner ra->ipopt_list[1] = 0x04; /* 4 bytes long */ 10914622Sfenner ra->ipopt_list[2] = 0x00; 11014622Sfenner ra->ipopt_list[3] = 0x00; 11114622Sfenner router_alert->m_len = sizeof(ra->ipopt_dst) + ra->ipopt_list[1]; 11214622Sfenner 1132531Swollman Head = (struct router_info *) 0; 1141541Srgrimes} 1151541Srgrimes 11612704Sphkstatic struct router_info * 1172531Swollmanfind_rti(ifp) 1182531Swollman struct ifnet *ifp; 1192531Swollman{ 1202531Swollman register struct router_info *rti = Head; 1212531Swollman 1222531Swollman#ifdef IGMP_DEBUG 1232531Swollman printf("[igmp.c, _find_rti] --> entering \n"); 1242531Swollman#endif 1252531Swollman while (rti) { 12614622Sfenner if (rti->rti_ifp == ifp) { 1272531Swollman#ifdef IGMP_DEBUG 1282531Swollman printf("[igmp.c, _find_rti] --> found old entry \n"); 1292531Swollman#endif 1302531Swollman return rti; 1312531Swollman } 13214622Sfenner rti = rti->rti_next; 1332531Swollman } 1342531Swollman MALLOC(rti, struct router_info *, sizeof *rti, M_MRTABLE, M_NOWAIT); 13514622Sfenner rti->rti_ifp = ifp; 13614622Sfenner rti->rti_type = IGMP_V2_ROUTER; 13714622Sfenner rti->rti_time = 0; 13814622Sfenner rti->rti_next = Head; 1392531Swollman Head = rti; 1402531Swollman#ifdef IGMP_DEBUG 1412531Swollman printf("[igmp.c, _find_rti] --> created an entry \n"); 1422531Swollman#endif 1432531Swollman return rti; 1442531Swollman} 1452531Swollman 1461541Srgrimesvoid 1471541Srgrimesigmp_input(m, iphlen) 1481541Srgrimes register struct mbuf *m; 1491541Srgrimes register int iphlen; 1501541Srgrimes{ 1511541Srgrimes register struct igmp *igmp; 1521541Srgrimes register struct ip *ip; 1531541Srgrimes register int igmplen; 1541541Srgrimes register struct ifnet *ifp = m->m_pkthdr.rcvif; 1551541Srgrimes register int minlen; 1561541Srgrimes register struct in_multi *inm; 1571541Srgrimes register struct in_ifaddr *ia; 1581541Srgrimes struct in_multistep step; 1592531Swollman struct router_info *rti; 1609209Swollman 1618546Sdg int timer; /** timer value in the igmp query header **/ 1621541Srgrimes 1631541Srgrimes ++igmpstat.igps_rcv_total; 1641541Srgrimes 1651541Srgrimes ip = mtod(m, struct ip *); 1661541Srgrimes igmplen = ip->ip_len; 1671541Srgrimes 1681541Srgrimes /* 1691541Srgrimes * Validate lengths 1701541Srgrimes */ 1711541Srgrimes if (igmplen < IGMP_MINLEN) { 1721541Srgrimes ++igmpstat.igps_rcv_tooshort; 1731541Srgrimes m_freem(m); 1741541Srgrimes return; 1751541Srgrimes } 1761541Srgrimes minlen = iphlen + IGMP_MINLEN; 1771541Srgrimes if ((m->m_flags & M_EXT || m->m_len < minlen) && 1781541Srgrimes (m = m_pullup(m, minlen)) == 0) { 1791541Srgrimes ++igmpstat.igps_rcv_tooshort; 1801541Srgrimes return; 1811541Srgrimes } 1821541Srgrimes 1831541Srgrimes /* 1841541Srgrimes * Validate checksum 1851541Srgrimes */ 1861541Srgrimes m->m_data += iphlen; 1871541Srgrimes m->m_len -= iphlen; 1881541Srgrimes igmp = mtod(m, struct igmp *); 1891541Srgrimes if (in_cksum(m, igmplen)) { 1901541Srgrimes ++igmpstat.igps_rcv_badsum; 1911541Srgrimes m_freem(m); 1921541Srgrimes return; 1931541Srgrimes } 1941541Srgrimes m->m_data -= iphlen; 1951541Srgrimes m->m_len += iphlen; 1962531Swollman 1971541Srgrimes ip = mtod(m, struct ip *); 1988546Sdg timer = igmp->igmp_code * PR_FASTHZ / IGMP_TIMER_SCALE; 1992531Swollman rti = find_rti(ifp); 2001541Srgrimes 20114622Sfenner /* 20214622Sfenner * In the IGMPv2 specification, there are 3 states and a flag. 20314622Sfenner * 20414622Sfenner * In Non-Member state, we simply don't have a membership record. 20514622Sfenner * In Delaying Member state, our timer is running (inm->inm_timer) 20614622Sfenner * In Idle Member state, our timer is not running (inm->inm_timer==0) 20714622Sfenner * 20814622Sfenner * The flag is inm->inm_state, it is set to IGMP_OTHERMEMBER if 20914622Sfenner * we have heard a report from another member, or IGMP_IREPORTEDLAST 21014622Sfenner * if I sent the last report. 21114622Sfenner */ 2121541Srgrimes switch (igmp->igmp_type) { 2131541Srgrimes 21414622Sfenner case IGMP_MEMBERSHIP_QUERY: 2151541Srgrimes ++igmpstat.igps_rcv_queries; 2161541Srgrimes 2178090Spst if (ifp->if_flags & IFF_LOOPBACK) 2181541Srgrimes break; 2191541Srgrimes 2202531Swollman if (igmp->igmp_code == 0) { 22114622Sfenner /* 22214622Sfenner * Old router. Remember that the querier on this 22314622Sfenner * interface is old, and set the timer to the 22414622Sfenner * value in RFC 1112. 22514622Sfenner */ 2264028Spst 22714622Sfenner rti->rti_type = IGMP_V1_ROUTER; 22814622Sfenner rti->rti_time = 0; 2294028Spst 23014622Sfenner timer = IGMP_MAX_HOST_REPORT_DELAY * PR_FASTHZ; 2314028Spst 23214622Sfenner if (ip->ip_dst.s_addr != igmp_all_hosts_group || 23314622Sfenner igmp->igmp_group.s_addr != 0) { 2342531Swollman ++igmpstat.igps_rcv_badqueries; 2352531Swollman m_freem(m); 2362531Swollman return; 2372531Swollman } 23814622Sfenner } else { 2392531Swollman /* 24014622Sfenner * New router. Simply do the new validity check. 2412531Swollman */ 24214622Sfenner 24314622Sfenner if (igmp->igmp_group.s_addr != 0 && 24414622Sfenner !IN_MULTICAST(ntohl(igmp->igmp_group.s_addr))) { 24514622Sfenner ++igmpstat.igps_rcv_badqueries; 24614622Sfenner m_freem(m); 24714622Sfenner return; 24814622Sfenner } 24914622Sfenner } 2502531Swollman 25114622Sfenner /* 25214622Sfenner * - Start the timers in all of our membership records 25314622Sfenner * that the query applies to for the interface on 25414622Sfenner * which the query arrived excl. those that belong 25514622Sfenner * to the "all-hosts" group (224.0.0.1). 25614622Sfenner * - Restart any timer that is already running but has 25714622Sfenner * a value longer than the requested timeout. 25814622Sfenner * - Use the value specified in the query message as 25914622Sfenner * the maximum timeout. 26014622Sfenner */ 26114622Sfenner IN_FIRST_MULTI(step, inm); 26214622Sfenner while (inm != NULL) { 26314622Sfenner if (inm->inm_ifp == ifp && 26414622Sfenner inm->inm_addr.s_addr != igmp_all_hosts_group && 26514622Sfenner (igmp->igmp_group.s_addr == 0 || 26614622Sfenner igmp->igmp_group.s_addr == inm->inm_addr.s_addr)) { 26714622Sfenner if (inm->inm_timer == 0 || 26814622Sfenner inm->inm_timer > timer) { 26914622Sfenner inm->inm_timer = 27014622Sfenner IGMP_RANDOM_DELAY(timer); 2712531Swollman igmp_timers_are_running = 1; 2722531Swollman } 2731541Srgrimes } 2741541Srgrimes IN_NEXT_MULTI(step, inm); 2751541Srgrimes } 2769209Swollman 2771541Srgrimes break; 2781541Srgrimes 27914622Sfenner case IGMP_V1_MEMBERSHIP_REPORT: 28014622Sfenner case IGMP_V2_MEMBERSHIP_REPORT: 2819209Swollman /* 28214622Sfenner * For fast leave to work, we have to know that we are the 28314622Sfenner * last person to send a report for this group. Reports 28414622Sfenner * can potentially get looped back if we are a multicast 28514622Sfenner * router, so discard reports sourced by me. 2869209Swollman */ 28714622Sfenner IFP_TO_IA(ifp, ia); 28814622Sfenner if (ia && ip->ip_src.s_addr == IA_SIN(ia)->sin_addr.s_addr) 28914622Sfenner break; 29014622Sfenner 2911541Srgrimes ++igmpstat.igps_rcv_reports; 2921541Srgrimes 2938090Spst if (ifp->if_flags & IFF_LOOPBACK) 2941541Srgrimes break; 2951541Srgrimes 29614622Sfenner if (!IN_MULTICAST(ntohl(igmp->igmp_group.s_addr))) { 2971541Srgrimes ++igmpstat.igps_rcv_badreports; 2981541Srgrimes m_freem(m); 2991541Srgrimes return; 3001541Srgrimes } 3011541Srgrimes 3021541Srgrimes /* 3031541Srgrimes * KLUDGE: if the IP source address of the report has an 3041541Srgrimes * unspecified (i.e., zero) subnet number, as is allowed for 3051541Srgrimes * a booting host, replace it with the correct subnet number 3061541Srgrimes * so that a process-level multicast routing demon can 3071541Srgrimes * determine which subnet it arrived from. This is necessary 3081541Srgrimes * to compensate for the lack of any way for a process to 3091541Srgrimes * determine the arrival interface of an incoming packet. 3101541Srgrimes */ 31114622Sfenner if ((ntohl(ip->ip_src.s_addr) & IN_CLASSA_NET) == 0) 3121541Srgrimes if (ia) ip->ip_src.s_addr = htonl(ia->ia_subnet); 3131541Srgrimes 3141541Srgrimes /* 3151541Srgrimes * If we belong to the group being reported, stop 3161541Srgrimes * our timer for that group. 3171541Srgrimes */ 3181541Srgrimes IN_LOOKUP_MULTI(igmp->igmp_group, ifp, inm); 3191541Srgrimes 3202531Swollman if (inm != NULL) { 32114622Sfenner inm->inm_timer = 0; 32214622Sfenner ++igmpstat.igps_rcv_ourreports; 32314622Sfenner 32414622Sfenner inm->inm_state = IGMP_OTHERMEMBER; 3252531Swollman } 32614622Sfenner 3271541Srgrimes break; 3281541Srgrimes } 3291541Srgrimes 3301541Srgrimes /* 3311541Srgrimes * Pass all valid IGMP packets up to any process(es) listening 3321541Srgrimes * on a raw IGMP socket. 3331541Srgrimes */ 3341541Srgrimes rip_input(m); 3351541Srgrimes} 3361541Srgrimes 3371541Srgrimesvoid 3381541Srgrimesigmp_joingroup(inm) 3391541Srgrimes struct in_multi *inm; 3401541Srgrimes{ 3419209Swollman int s = splnet(); 3421541Srgrimes 34314622Sfenner if (inm->inm_addr.s_addr == igmp_all_hosts_group 34414622Sfenner || inm->inm_ifp->if_flags & IFF_LOOPBACK) { 3451541Srgrimes inm->inm_timer = 0; 34614622Sfenner inm->inm_state = IGMP_OTHERMEMBER; 34714622Sfenner } else { 34814622Sfenner inm->inm_rti = find_rti(inm->inm_ifp); 34914622Sfenner igmp_sendpkt(inm, inm->inm_rti->rti_type, 0); 3502531Swollman inm->inm_timer = IGMP_RANDOM_DELAY( 3512531Swollman IGMP_MAX_HOST_REPORT_DELAY*PR_FASTHZ); 35214622Sfenner inm->inm_state = IGMP_IREPORTEDLAST; 3531541Srgrimes igmp_timers_are_running = 1; 3541541Srgrimes } 3551541Srgrimes splx(s); 3561541Srgrimes} 3571541Srgrimes 3581541Srgrimesvoid 3591541Srgrimesigmp_leavegroup(inm) 3601541Srgrimes struct in_multi *inm; 3611541Srgrimes{ 36214622Sfenner if (inm->inm_state == IGMP_IREPORTEDLAST && 36314622Sfenner inm->inm_addr.s_addr != igmp_all_hosts_group && 36414622Sfenner !(inm->inm_ifp->if_flags & IFF_LOOPBACK) && 36514622Sfenner inm->inm_rti->rti_type != IGMP_V1_ROUTER) 36614622Sfenner igmp_sendpkt(inm, IGMP_V2_LEAVE_GROUP, igmp_all_rtrs_group); 3671541Srgrimes} 3681541Srgrimes 3691541Srgrimesvoid 3701541Srgrimesigmp_fasttimo() 3711541Srgrimes{ 3721541Srgrimes register struct in_multi *inm; 3731541Srgrimes struct in_multistep step; 3749209Swollman int s; 3751541Srgrimes 3761541Srgrimes /* 3771541Srgrimes * Quick check to see if any work needs to be done, in order 3781541Srgrimes * to minimize the overhead of fasttimo processing. 3791541Srgrimes */ 3809209Swollman 3811541Srgrimes if (!igmp_timers_are_running) 3821541Srgrimes return; 3831541Srgrimes 3841541Srgrimes s = splnet(); 3851541Srgrimes igmp_timers_are_running = 0; 3861541Srgrimes IN_FIRST_MULTI(step, inm); 3871541Srgrimes while (inm != NULL) { 3881541Srgrimes if (inm->inm_timer == 0) { 3891541Srgrimes /* do nothing */ 3901541Srgrimes } else if (--inm->inm_timer == 0) { 39114622Sfenner igmp_sendpkt(inm, inm->inm_rti->rti_type, 0); 39214622Sfenner inm->inm_state = IGMP_IREPORTEDLAST; 3931541Srgrimes } else { 3941541Srgrimes igmp_timers_are_running = 1; 3951541Srgrimes } 3961541Srgrimes IN_NEXT_MULTI(step, inm); 3971541Srgrimes } 3981541Srgrimes splx(s); 3991541Srgrimes} 4001541Srgrimes 4012531Swollmanvoid 4022531Swollmanigmp_slowtimo() 4032531Swollman{ 4042531Swollman int s = splnet(); 4052531Swollman register struct router_info *rti = Head; 4062531Swollman 4072531Swollman#ifdef IGMP_DEBUG 4082531Swollman printf("[igmp.c,_slowtimo] -- > entering \n"); 4092531Swollman#endif 4102531Swollman while (rti) { 41114622Sfenner if (rti->rti_type == IGMP_V1_ROUTER) { 41214622Sfenner rti->rti_time++; 41314622Sfenner if (rti->rti_time >= IGMP_AGE_THRESHOLD) { 41414622Sfenner rti->rti_type = IGMP_V2_ROUTER; 4152531Swollman } 41614622Sfenner } 41714622Sfenner rti = rti->rti_next; 4182531Swollman } 4199209Swollman#ifdef IGMP_DEBUG 4202531Swollman printf("[igmp.c,_slowtimo] -- > exiting \n"); 4212531Swollman#endif 4222531Swollman splx(s); 4232531Swollman} 4242531Swollman 4251541Srgrimesstatic void 42614622Sfennerigmp_sendpkt(inm, type, addr) 4272531Swollman struct in_multi *inm; 4282531Swollman int type; 42914622Sfenner unsigned long addr; 4301541Srgrimes{ 4312531Swollman struct mbuf *m; 4322531Swollman struct igmp *igmp; 4332531Swollman struct ip *ip; 4342531Swollman struct ip_moptions *imo; 4351541Srgrimes 4362531Swollman MGETHDR(m, M_DONTWAIT, MT_HEADER); 4372531Swollman if (m == NULL) 4382531Swollman return; 4392531Swollman 4402531Swollman MALLOC(imo, struct ip_moptions *, sizeof *imo, M_IPMOPTS, M_DONTWAIT); 4412531Swollman if (!imo) { 4422531Swollman m_free(m); 4431541Srgrimes return; 4442531Swollman } 4452531Swollman 4468090Spst m->m_pkthdr.rcvif = loif; 4471541Srgrimes m->m_pkthdr.len = sizeof(struct ip) + IGMP_MINLEN; 4482531Swollman MH_ALIGN(m, IGMP_MINLEN + sizeof(struct ip)); 4492531Swollman m->m_data += sizeof(struct ip); 4502531Swollman m->m_len = IGMP_MINLEN; 4512531Swollman igmp = mtod(m, struct igmp *); 4522531Swollman igmp->igmp_type = type; 4532531Swollman igmp->igmp_code = 0; 4542531Swollman igmp->igmp_group = inm->inm_addr; 4552531Swollman igmp->igmp_cksum = 0; 4562531Swollman igmp->igmp_cksum = in_cksum(m, IGMP_MINLEN); 4571541Srgrimes 4582531Swollman m->m_data -= sizeof(struct ip); 4592531Swollman m->m_len += sizeof(struct ip); 4602531Swollman ip = mtod(m, struct ip *); 4612531Swollman ip->ip_tos = 0; 4622531Swollman ip->ip_len = sizeof(struct ip) + IGMP_MINLEN; 4632531Swollman ip->ip_off = 0; 4642531Swollman ip->ip_p = IPPROTO_IGMP; 4652531Swollman ip->ip_src.s_addr = INADDR_ANY; 46614622Sfenner ip->ip_dst.s_addr = addr ? addr : igmp->igmp_group.s_addr; 4671541Srgrimes 4682531Swollman imo->imo_multicast_ifp = inm->inm_ifp; 4692531Swollman imo->imo_multicast_ttl = 1; 4709209Swollman imo->imo_multicast_vif = -1; 4712531Swollman /* 4722531Swollman * Request loopback of the report if we are acting as a multicast 4732531Swollman * router, so that the process-level routing demon can hear it. 4742531Swollman */ 4752531Swollman imo->imo_multicast_loop = (ip_mrouter != NULL); 4761541Srgrimes 47714622Sfenner ip_output(m, router_alert, (struct route *)0, 0, imo); 4782531Swollman 4792531Swollman FREE(imo, M_IPMOPTS); 4802531Swollman ++igmpstat.igps_snd_reports; 4811541Srgrimes} 482