diff -r -c -w -N linux-2.4.18-10/include/linux/igmp.h linux-2.4.18-10-igmpv3/include/linux/igmp.h *** linux-2.4.18-10/include/linux/igmp.h Thu Apr 12 15:11:39 2001 --- linux-2.4.18-10-igmpv3/include/linux/igmp.h Wed Aug 28 17:34:01 2002 *************** *** 1,11 **** /* * Linux NET3: Internet Group Management Protocol [IGMP] * ! * Authors: ! * Alan Cox ! * ! * Extended to talk the BSD extended IGMP protocol of mrouted 3.6 * * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License --- 1,11 ---- /* * Linux NET3: Internet Group Management Protocol [IGMP] * ! * Stub file. Selects the proper version of IGMP for inclusion based ! * on the CONFIG_IP_IGMPV3 configuration setting. * + * Author: + * Vince Laviano * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License *************** *** 17,119 **** #define _LINUX_IGMP_H /* ! * IGMP protocol structures ! */ ! ! /* ! * Header in on cable format ! */ ! ! struct igmphdr ! { ! __u8 type; ! __u8 code; /* For newer IGMP */ ! __u16 csum; ! __u32 group; ! }; ! ! #define IGMP_HOST_MEMBERSHIP_QUERY 0x11 /* From RFC1112 */ ! #define IGMP_HOST_MEMBERSHIP_REPORT 0x12 /* Ditto */ ! #define IGMP_DVMRP 0x13 /* DVMRP routing */ ! #define IGMP_PIM 0x14 /* PIM routing */ ! #define IGMP_TRACE 0x15 ! #define IGMP_HOST_NEW_MEMBERSHIP_REPORT 0x16 /* New version of 0x11 */ ! #define IGMP_HOST_LEAVE_MESSAGE 0x17 ! ! #define IGMP_MTRACE_RESP 0x1e ! #define IGMP_MTRACE 0x1f ! ! ! /* ! * Use the BSD names for these for compatibility ! */ ! ! #define IGMP_DELAYING_MEMBER 0x01 ! #define IGMP_IDLE_MEMBER 0x02 ! #define IGMP_LAZY_MEMBER 0x03 ! #define IGMP_SLEEPING_MEMBER 0x04 ! #define IGMP_AWAKENING_MEMBER 0x05 ! ! #define IGMP_MINLEN 8 ! ! #define IGMP_MAX_HOST_REPORT_DELAY 10 /* max delay for response to */ ! /* query (in seconds) */ ! ! #define IGMP_TIMER_SCALE 10 /* denotes that the igmphdr->timer field */ ! /* specifies time in 10th of seconds */ ! ! #define IGMP_AGE_THRESHOLD 400 /* If this host don't hear any IGMP V1 */ ! /* message in this period of time, */ ! /* revert to IGMP v2 router. */ ! ! #define IGMP_ALL_HOSTS htonl(0xE0000001L) ! #define IGMP_ALL_ROUTER htonl(0xE0000002L) ! #define IGMP_LOCAL_GROUP htonl(0xE0000000L) ! #define IGMP_LOCAL_GROUP_MASK htonl(0xFFFFFF00L) ! ! /* ! * struct for keeping the multicast list in ! */ ! ! #ifdef __KERNEL__ ! ! /* ip_mc_socklist is real list now. Speed is not argument; ! this list never used in fast path code */ ! ! struct ip_mc_socklist ! { ! struct ip_mc_socklist *next; ! int count; ! struct ip_mreqn multi; ! }; ! ! struct ip_mc_list ! { ! struct in_device *interface; ! unsigned long multiaddr; ! struct ip_mc_list *next; ! struct timer_list timer; ! int users; ! atomic_t refcnt; ! spinlock_t lock; ! char tm_running; ! char reporter; ! char unsolicit_count; ! char loaded; ! }; ! ! extern int ip_check_mc(struct in_device *dev, u32 mc_addr); ! extern int igmp_rcv(struct sk_buff *); ! extern int ip_mc_join_group(struct sock *sk, struct ip_mreqn *imr); ! extern int ip_mc_leave_group(struct sock *sk, struct ip_mreqn *imr); ! extern void ip_mc_drop_socket(struct sock *sk); ! extern void ip_mr_init(void); ! extern void ip_mc_init_dev(struct in_device *); ! extern void ip_mc_destroy_dev(struct in_device *); ! extern void ip_mc_up(struct in_device *); ! extern void ip_mc_down(struct in_device *); ! extern int ip_mc_dec_group(struct in_device *in_dev, u32 addr); ! extern void ip_mc_inc_group(struct in_device *in_dev, u32 addr); ! #endif #endif --- 17,28 ---- #define _LINUX_IGMP_H /* ! * Include defs for appropriate version of IGMP */ ! #ifdef CONFIG_IP_IGMPV3 ! #include ! #else ! #include ! #endif /* CONFIG_IP_IGMPV3 */ #endif + diff -r -c -w -N linux-2.4.18-10/include/linux/igmpv2.h linux-2.4.18-10-igmpv3/include/linux/igmpv2.h *** linux-2.4.18-10/include/linux/igmpv2.h Wed Dec 31 19:00:00 1969 --- linux-2.4.18-10-igmpv3/include/linux/igmpv2.h Wed Aug 28 16:36:44 2002 *************** *** 0 **** --- 1,119 ---- + /* + * Linux NET3: Internet Group Management Protocol [IGMP] + * + * Authors: + * Alan Cox + * + * Extended to talk the BSD extended IGMP protocol of mrouted 3.6 + * + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version + * 2 of the License, or (at your option) any later version. + */ + + #ifndef _LINUX_IGMPV2_H + #define _LINUX_IGMPV2_H + + /* + * IGMP protocol structures + */ + + /* + * Header in on cable format + */ + + struct igmphdr + { + __u8 type; + __u8 code; /* For newer IGMP */ + __u16 csum; + __u32 group; + }; + + #define IGMP_HOST_MEMBERSHIP_QUERY 0x11 /* From RFC1112 */ + #define IGMP_HOST_MEMBERSHIP_REPORT 0x12 /* Ditto */ + #define IGMP_DVMRP 0x13 /* DVMRP routing */ + #define IGMP_PIM 0x14 /* PIM routing */ + #define IGMP_TRACE 0x15 + #define IGMP_HOST_NEW_MEMBERSHIP_REPORT 0x16 /* New version of 0x11 */ + #define IGMP_HOST_LEAVE_MESSAGE 0x17 + + #define IGMP_MTRACE_RESP 0x1e + #define IGMP_MTRACE 0x1f + + + /* + * Use the BSD names for these for compatibility + */ + + #define IGMP_DELAYING_MEMBER 0x01 + #define IGMP_IDLE_MEMBER 0x02 + #define IGMP_LAZY_MEMBER 0x03 + #define IGMP_SLEEPING_MEMBER 0x04 + #define IGMP_AWAKENING_MEMBER 0x05 + + #define IGMP_MINLEN 8 + + #define IGMP_MAX_HOST_REPORT_DELAY 10 /* max delay for response to */ + /* query (in seconds) */ + + #define IGMP_TIMER_SCALE 10 /* denotes that the igmphdr->timer field */ + /* specifies time in 10th of seconds */ + + #define IGMP_AGE_THRESHOLD 400 /* If this host don't hear any IGMP V1 */ + /* message in this period of time, */ + /* revert to IGMP v2 router. */ + + #define IGMP_ALL_HOSTS htonl(0xE0000001L) + #define IGMP_ALL_ROUTER htonl(0xE0000002L) + #define IGMP_LOCAL_GROUP htonl(0xE0000000L) + #define IGMP_LOCAL_GROUP_MASK htonl(0xFFFFFF00L) + + /* + * struct for keeping the multicast list in + */ + + #ifdef __KERNEL__ + + /* ip_mc_socklist is real list now. Speed is not argument; + this list never used in fast path code + */ + + struct ip_mc_socklist + { + struct ip_mc_socklist *next; + int count; + struct ip_mreqn multi; + }; + + struct ip_mc_list + { + struct in_device *interface; + unsigned long multiaddr; + struct ip_mc_list *next; + struct timer_list timer; + int users; + atomic_t refcnt; + spinlock_t lock; + char tm_running; + char reporter; + char unsolicit_count; + char loaded; + }; + + extern int ip_check_mc(struct in_device *dev, u32 mc_addr); + extern int igmp_rcv(struct sk_buff *); + extern int ip_mc_join_group(struct sock *sk, struct ip_mreqn *imr); + extern int ip_mc_leave_group(struct sock *sk, struct ip_mreqn *imr); + extern void ip_mc_drop_socket(struct sock *sk); + extern void ip_mr_init(void); + extern void ip_mc_init_dev(struct in_device *); + extern void ip_mc_destroy_dev(struct in_device *); + extern void ip_mc_up(struct in_device *); + extern void ip_mc_down(struct in_device *); + extern int ip_mc_dec_group(struct in_device *in_dev, u32 addr); + extern void ip_mc_inc_group(struct in_device *in_dev, u32 addr); + #endif + #endif diff -r -c -w -N linux-2.4.18-10/include/linux/igmpv3.h linux-2.4.18-10-igmpv3/include/linux/igmpv3.h *** linux-2.4.18-10/include/linux/igmpv3.h Wed Dec 31 19:00:00 1969 --- linux-2.4.18-10-igmpv3/include/linux/igmpv3.h Wed Aug 28 16:36:44 2002 *************** *** 0 **** --- 1,377 ---- + /* + * Internet Group Management Protocol Version 3 (IGMPv3) + * + * This code implements the IGMPv3 protocol as defined in + * draft-ietf-idmr-igmp-v3-11.txt. IGMPv3 is backwards compatible with + * IGMPv2 as defined in RFC 2236 and with IGMPv1 as defined in RFC 1112. + * To enable IGMPv3, set CONFIG_IP_MULTICAST and CONFIG_IP_IGMPV3. + * + * Version: $Id: igmpv3.h,v 1.46 2001/07/27 09:27:29 davem Exp $ + * + * Authors: + * Wilbert de Graaf (wilbert@kloosterhof.com) (FreeBSD impl) + * Vince Laviano (vlaviano@cisco.com) (Linux port) + * + * The majority of this code is derived from the FreeBSD IGMPv3 + * implementation by Wilbert de Graaf (wilbert@kloosterhof.com). See + * http://www.kloosterhof.com/~wilbert/igmpv3.html for details. + * + * Code was also derived from the earlier Linux IGMPv2 implementation, + * still available in igmpv2.c, by Alan Cox, Chih-Jen Chang, Tsu-Sheng + * Tsao, Christian Daudt, Malcolm Beattie, and Alexey Kuznetsov. + * + * Cisco would like to acknowledge the work of Sprint Labs towards + * IGMPv3 for Linux, which they have made publically available at + * www.sprintlabs.com/Department/IP-Interworking/multicast/linux-igmpv3/ + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version + * 2 of the License, or (at your option) any later version. + * + * Portions of this code are also licensed under the following terms: + * + * "The FreeBSD Copyright + * + * Copyright 1994-2002 FreeBSD, Inc. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE FREEBSD PROJECT ``AS IS'' AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE FREEBSD PROJECT OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + * The views and conclusions contained in the software and documentation + * are those of the authors and should not be interpreted as representing + * official policies, either expressed or implied, of the FreeBSD Project + * or FreeBSD, Inc." + */ + + #ifndef _LINUX_IGMPV3_H + #define _LINUX_IGMPV3_H + + /* + * IGMP protocol structures + */ + + /* + * IGMP packet format + */ + struct igmphdr + { + __u8 type; /* version and type of IGMP message */ + __u8 code; /* subtype for routing messages */ + __u16 csum; /* IP-style checksum */ + __u32 group; /* group address being reported (zero for + queries) */ + }; + + /* + * IGMPv3 query format + */ + struct igmpv3 { + __u8 igmp_type; /* version and type of IGMP message */ + __u8 igmp_code; /* subtype for routing messages */ + __u16 igmp_cksum; /* IP-style checksum */ + __u32 igmp_group; /* group address being reported */ + __u8 igmp_misc; /* reserved, suppress and robustness variable */ + __u8 igmp_qqi; /* querier's query interval */ + __u16 igmp_numsrc; /* number of sources */ + __u32 igmp_sources[1]; /* source addresses */ + }; + + /* Fetch the [Robustness Variable] */ + #define IGMP_QRV(x) ((x)->igmp_misc & (0x07)) + + /* + * IGMPv3 group record + */ + struct igmp_grouprec { + __u8 ig_type; /* record type */ + __u8 ig_datalen; /* amount of aux data */ + __u16 ig_numsrc; /* number of sources */ + __u32 ig_group; /* the group being reported */ + __u32 ig_sources[1]; /* source addresses */ + }; + + /* + * IGMPv3 report + */ + struct igmp_report { + __u8 ir_type; /* record type */ + __u8 ir_rsv1; /* reserved */ + __u16 ir_cksum; /* checksum */ + __u16 ir_rsv2; /* reserved */ + __u16 ir_numgrps; /* number of group records */ + struct igmp_grouprec ir_groups[1]; /* group records */ + }; + + #define IGMP_MINLEN 8 + #define IGMP_HDRLEN 8 + #define IGMP_GRPREC_HDRLEN 8 + #define IGMP_PREPEND 0 /* XXX irrelevant for Linux + impl since mbufs not used? */ + + /* + * Maximum number of sources that can be specified in an igmp query, + * considering its length. + */ + #define IGMP_MAXSOURCES(len) (((len)-12)>>2) + + /* + * Message types, including version number. + * XXX Maintain old names in parallel with new ones for backwards compat? + */ + #define IGMP_MEMBERSHIP_QUERY 0x11 /* membership query */ + #define IGMP_V1_MEMBERSHIP_REPORT 0x12 /* Ver. 1 membership report */ + #define IGMP_V2_MEMBERSHIP_REPORT 0x16 /* Ver. 2 membership report */ + #define IGMP_V3_MEMBERSHIP_REPORT 0x22 /* Ver. 3 membership report */ + + #define IGMP_V2_LEAVE_GROUP 0x17 /* Leave-group message */ + + #define IGMP_DVMRP 0x13 /* DVMRP routing message */ + #define IGMP_PIM 0x14 /* PIM routing message */ + + #define IGMP_TRACE 0x15 + #define IGMP_MTRACE_RESP 0x1e /* traceroute resp. (to sender) */ + #define IGMP_MTRACE 0x1f /* mcast traceroute messages */ + + /* + * The following four definitions are for backwards compatibility. They + * should be removed as soon as all applications are updated to use the new + * constant names. + */ + #define IGMP_HOST_MEMBERSHIP_QUERY IGMP_MEMBERSHIP_QUERY + #define IGMP_HOST_MEMBERSHIP_REPORT IGMP_V1_MEMBERSHIP_REPORT + #define IGMP_HOST_NEW_MEMBERSHIP_REPORT IGMP_V2_MEMBERSHIP_REPORT + #define IGMP_HOST_LEAVE_MESSAGE IGMP_V2_LEAVE_GROUP + + /* + * Use the BSD names for these for compatibility + */ + + #define IGMP_DELAYING_MEMBER 0x01 + #define IGMP_IDLE_MEMBER 0x02 + #define IGMP_LAZY_MEMBER 0x03 + #define IGMP_SLEEPING_MEMBER 0x04 + #define IGMP_AWAKENING_MEMBER 0x05 + + #define IGMP_MAX_HOST_REPORT_DELAY 10 /* max delay for response to */ + /* query (in seconds) */ + #define IGMP_TIMER_SCALE 10 /* denotes that the igmphdr->timer field */ + /* specifies time in 10th of seconds */ + + + #define IGMP_ALL_HOSTS htonl(0xE0000001L) + #define IGMP_ALL_ROUTER htonl(0xE0000002L) + #define IGMP_LOCAL_GROUP htonl(0xE0000000L) + #define IGMP_LOCAL_GROUP_MASK htonl(0xFFFFFF00L) + + struct igmpstat { + __u32 igps_rcv_total; /* total IGMP messages received */ + __u32 igps_rcv_tooshort; /* received with too few bytes */ + __u32 igps_rcv_badsum; /* received with bad checksum */ + __u32 igps_rcv_queries; /* received membership queries */ + __u32 igps_rcv_badqueries; /* received invalid queries */ + __u32 igps_rcv_reports; /* received membership reports */ + __u32 igps_rcv_badreports; /* received invalid reports */ + __u32 igps_rcv_ourreports; /* received reports for our groups */ + __u32 igps_snd_reports; /* sent membership reports */ + __u32 igps_rcv_toolong; /* received with too many bytes */ + }; + + + /* + * struct for keeping the multicast list in + */ + + #ifdef __KERNEL__ + + #define IGMP_RANDOM_DELAY(X) ((net_random() % (X)) + 1) + + /* + * States masks for IGMPv3 + */ + #define IGMP_NONEXISTENT 0x01 + #define IGMP_OTHERMEMBER 0x02 + #define IGMP_IREPORTEDLAST 0x04 + + /* + * We must remember what version the subnet's querier is. + * We conveniently use the IGMP message type for the proper membership report + * to keep this state. + */ + #define IGMP_V1_ROUTER IGMP_V1_MEMBERSHIP_REPORT + #define IGMP_V2_ROUTER IGMP_V2_MEMBERSHIP_REPORT + #define IGMP_V3_ROUTER IGMP_V3_MEMBERSHIP_REPORT + + /* + * Revert to new router if we haven't heard from an old router in this + * amount of time. + * XXX units? Old Linux impl defined to be 400. + */ + #define IGMP_AGE_THRESHOLD 540 + + /* + * IGMPv3 default variables + */ + #define IGMP_INIT_ROBVAR 2 /* Default [Robustness variable] */ + #define IGMP_MAX_ROBVAR 7 /* Max. [Robustness variable] */ + #define IGMP_INIT_QRYINT 125 /* Default [Querier's Query interval] */ + #define IGMP_MAX_QRYINT 255 /* Max. [Querier's Query interval] */ + #define IGMP_INIT_QRYRSP 10 /* Default [Query Response interval] */ + #define IGMP_DEF_QRYMRT 10 /* version 1 [Max. Response Time] */ + #define IGMP_UNSOL_INT 1 /* [Unsolicited Report Interval] */ + + /* + * IGMPv3 report type(s) + */ + #define IGMP_REPORT_MODE_IN 1 /* mode-is-include */ + #define IGMP_REPORT_MODE_EX 2 /* mode-is-exclude */ + #define IGMP_REPORT_TO_IN 3 /* change-to-include */ + #define IGMP_REPORT_TO_EX 4 /* change-to-exclude */ + #define IGMP_REPORT_ALLOW_NEW 5 /* allow-new-sources */ + #define IGMP_REPORT_BLOCK_OLD 6 /* block-old-sources */ + + /* + * Report type as flagged bits in reporttag + */ + #define IGMP_MASK_CUR_STATE 0x01 /* report current-state */ + #define IGMP_MASK_ALLOW_NEW 0x02 /* report source as allow-new */ + #define IGMP_MASK_BLOCK_OLD 0x04 /* report source as block-old */ + #define IGMP_MASK_TO_IN 0x08 /* report source as to_in */ + #define IGMP_MASK_TO_EX 0x10 /* report source as to_ex */ + #define IGMP_MASK_STATE_T1 0x20 /* state at T1 */ + #define IGMP_MASK_STATE_T2 0x40 /* state at T2 */ + #define IGMP_MASK_IF_STATE 0x80 /* report current-state per interface */ + + #define IGMP_MASK_STATE_TX (IGMP_MASK_STATE_T1 | IGMP_MASK_STATE_T2) + #define IGMP_MASK_PENDING (IGMP_MASK_CUR_STATE | IGMP_MASK_ALLOW_NEW | \ + IGMP_MASK_BLOCK_OLD) + + /* + * List identifiers + */ + #define IGMP_EXCLUDE_LIST 1 /* exclude list used to tag report */ + #define IGMP_INCLUDE_LIST 2 /* include list used to tag report */ + #define IGMP_RECORDED_LIST 3 /* recorded list used to tag report */ + + /* + * This information is logically part of the in_device structure. + */ + struct router_info { + struct in_device *rti_ifp; + int rti_type; /* host imgp compat mode */ + int rti_timev1; /* [IGMPv1 querier present] */ + int rti_timev2; /* [IGMPv2 querier present] */ + int rti_timer; /* Report to general query */ + int rti_qrv; /* [Querier Robustness Var] */ + struct router_info *rti_next; + }; + + /* + * Internet multicast source filter list. This list is used to store ip + * source addresses, per interface membership information. + */ + struct in_sfentry { + struct in_addr isf_addr; /* the sourcefilter address */ + unsigned short isf_refcount; /* references by sockets */ + unsigned short isf_reporttag; /* tracks what to report */ + unsigned short isf_rexmit; /* retrans state/count */ + struct in_sfentry *isf_next; /* next filter */ + struct in_sfentry *isf_pnext; /* next in pool (for re-use) */ + }; + + /* + * Internet multicast address structure. There is one of these for each IP + * multicast group to which this host belongs on a given network interface. + * For every entry on the interface's if_multiaddrs list which represents an + * IP multicast group, there is one of these structures. They are also kept on + * a system-wide list to make it easier to keep our legacy IGMP code compatible + * with the rest of the world. + * + * XXX How significant is this last bit w.r.t. Linux? + */ + struct in_multi { + struct { + struct in_multi *le_next; + struct in_multi **le_prev; + } inm_link; + struct in_multi *inm_next; /* interface list */ + struct in_addr inm_addr; + struct in_device *inm_ifp; + struct router_info *inm_rti; + int inm_timer; + int inm_ti_curstate; + int inm_ti_statechg; + unsigned short inm_rpt_statechg; + unsigned short inm_rpt_toxx; + unsigned int inm_state; + unsigned short inm_fmode; + unsigned int inm_num_socks_excl; + struct in_sfentry *inm_sflexcl; + struct in_sfentry *inm_sflincl; + struct in_sfentry *inm_sflrec; + struct in_sfentry *inm_sflpool; + unsigned int inm_num_rec_sources; + unsigned int inm_num_gass_queries; + /* Extra stuff to mimic BSD ifmultiaddr struct */ + unsigned int users; + unsigned int loaded; + }; + + /* + * Structure used by macros below to remember position when stepping through + * all of the in_multi records. + */ + struct in_multistep { + struct in_multi *i_inm; + }; + + struct source_list { + struct source_list *src_next; /* next entry */ + struct in_addr src_addr; /* address */ + }; + + struct sock_mcastsf { + unsigned int smsf_fmode; /* source filter mode */ + unsigned int smsf_numsrc; /* number of sources */ + struct source_list *smsf_slist; /* addresses */ + }; + + extern int ip_check_mc(struct in_device *dev, u32 mc_addr); + extern int igmp_rcv(struct sk_buff *); + extern int ip_mc_join_group(struct sock *sk, struct ip_mreqn *imr); + extern int ip_mc_leave_group(struct sock *sk, struct ip_mreqn *imr); + extern void ip_mc_drop_socket(struct sock *sk); + extern void ip_mr_init(void); + extern void ip_mc_init_dev(struct in_device *); + extern void ip_mc_destroy_dev(struct in_device *); + extern void ip_mc_up(struct in_device *); + extern void ip_mc_down(struct in_device *); + extern int ip_mc_dec_group(struct in_device *in_dev, u32 addr); + extern void ip_mc_inc_group(struct in_device *in_dev, u32 addr); + extern void igmp_init(void); + + int ip_setmoptions(struct sock *sk, int optname, char *optval, int optlen); + int in_setmcast_srcfilter(struct sock *sk, unsigned long arg); + int in_getmcast_srcfilter(struct sock *sk, unsigned long arg); + + #endif /* __KERNEL__ */ + #endif diff -r -c -w -N linux-2.4.18-10/include/linux/in.h linux-2.4.18-10-igmpv3/include/linux/in.h *** linux-2.4.18-10/include/linux/in.h Wed Aug 7 11:41:27 2002 --- linux-2.4.18-10-igmpv3/include/linux/in.h Wed Aug 28 17:34:00 2002 *************** *** 9,14 **** --- 9,15 ---- * * Authors: Original taken from the GNU Project file. * Fred N. van Kempen, + * Vince Laviano -- IGMPv3 definitions. * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License *************** *** 82,99 **** #define IP_ADD_MEMBERSHIP 35 #define IP_DROP_MEMBERSHIP 36 ! /* These need to appear somewhere around here */ ! #define IP_DEFAULT_MULTICAST_TTL 1 ! #define IP_DEFAULT_MULTICAST_LOOP 1 ! /* Request struct for multicast socket ops */ struct ip_mreq { struct in_addr imr_multiaddr; /* IP multicast address of group */ struct in_addr imr_interface; /* local IP address of interface */ }; struct ip_mreqn { struct in_addr imr_multiaddr; /* IP multicast address of group */ --- 83,138 ---- #define IP_ADD_MEMBERSHIP 35 #define IP_DROP_MEMBERSHIP 36 ! /* ! * Protocol-dependent MSF sockoptions for IGMPv3 support ! */ ! #define IP_BLOCK_SOURCE 37 /* block an mcast source */ ! #define IP_UNBLOCK_SOURCE 38 /* unblock an mcast source */ ! #define IP_ADD_SOURCE_MEMBERSHIP 39 /* allow an mcast source */ ! #define IP_DROP_SOURCE_MEMBERSHIP 40 /* unallow an mcast source */ ! ! /* ! * Protocol independent MSF sockoptions for IGMPv3 support ! */ ! #define MCAST_JOIN_GROUP 41 /* join all sources for group */ ! #define MCAST_LEAVE_GROUP 42 /* drop all sources for group */ ! #define MCAST_BLOCK_SOURCE 43 /* block an mcast source */ ! #define MCAST_UNBLOCK_SOURCE 44 /* unblock an mcast source */ ! #define MCAST_JOIN_SOURCE_GROUP 45 /* allow an mcast source */ ! #define MCAST_LEAVE_SOURCE_GROUP 46 /* unallow an mcast source */ ! ! ! /* ! * Source filtermodes for MSF API ! */ ! #define MCAST_INCLUDE 0 ! #define MCAST_EXCLUDE 1 ! ! /* ! * Defaults and limits for multicast ! */ ! #define IP_DEFAULT_MULTICAST_TTL 1 /* normally limit mcasts to 1 hop */ ! #define IP_DEFAULT_MULTICAST_LOOP 1 /* normally hear sends if a member */ ! #define IP_MAX_MEMBERSHIPS 20 /* per socket */ ! #define IP_MAX_SRCFILTERS 1024 /* per socket per group */ ! /* ! * Request structs for multicast socket ops ! */ + /* + * Argument structure for IP_ADD_MEMBERSHIP and IP_DROP_MEMBERSHIP + */ struct ip_mreq { struct in_addr imr_multiaddr; /* IP multicast address of group */ struct in_addr imr_interface; /* local IP address of interface */ }; + /* + * Augmented argument structure for IP_ADD_MEMBERSHIP and IP_DROP_MEMBERSHIP + * that allows interface to be identified by its index if desired. + */ struct ip_mreqn { struct in_addr imr_multiaddr; /* IP multicast address of group */ *************** *** 101,106 **** --- 140,179 ---- int imr_ifindex; /* Interface index */ }; + /* + * Argument structure for IP_BLOCK_SOURCE, IP_UNBLOCK_SOURCE, + * IP_ADD_SOURCE_MEMBERSHIP, IP_DROP_SOURCE_MEMBERSHIP + */ + struct ip_mreq_source { + struct in_addr imr_multiaddr; /* IP multicast address of group */ + struct in_addr imr_sourceaddr; /* IP address of source */ + struct in_addr imr_interface; /* local IP address of interface */ + }; + + /* + * Argument structure for IP_(SET|GET)MULTICAST_FILTER + */ + struct ip_msfilter { + struct in_addr imsf_multiaddr; /* IP multicast address of group */ + struct in_addr imsf_interface; /* local IP address of interface */ + uint32_t imsf_fmode; /* filter mode */ + uint32_t imsf_numsrc; /* number of sources in src list */ + struct in_addr imsf_slist[1]; /* source list */ + }; + + #define IP_MSFILTER_SIZE(numsrc)\ + (sizeof(struct ip_msfilter) - sizeof(struct in_addr) \ + + (numsrc) * sizeof(struct in_addr)) + + /* + * Structures and macros for the protocol independent multicast api + * XXX Omitted for now. --vpl + * struct group_req; + * struct group_source_req; + * struct group_filter; + * GROUP_FILTER_SIZE(numsrc) + */ + struct in_pktinfo { int ipi_ifindex; *************** *** 145,150 **** --- 218,227 ---- #define IN_CLASSC_HOST (0xffffffff & ~IN_CLASSC_NET) #define IN_CLASSD(a) ((((long int) (a)) & 0xf0000000) == 0xe0000000) + #define IN_CLASSD_NET 0xf0000000 + #define IN_CLASSD_NSHIFT 28 + #define IN_CLASSD_HOST 0x0fffffff + #define IN_CLASSD_SSM(a) ((((long int) (a)) & 0xff000000) == 0xe8000000) #define IN_MULTICAST(a) IN_CLASSD(a) #define IN_MULTICAST_NET 0xF0000000 *************** *** 171,176 **** --- 248,254 ---- #define INADDR_UNSPEC_GROUP 0xe0000000U /* 224.0.0.0 */ #define INADDR_ALLHOSTS_GROUP 0xe0000001U /* 224.0.0.1 */ #define INADDR_ALLRTRS_GROUP 0xe0000002U /* 224.0.0.2 */ + #define INADDR_ALLRTRS_IGMPV3_GROUP 0xe0000016U /* 224.0.0.22 */ #define INADDR_MAX_LOCAL_GROUP 0xe00000ffU /* 224.0.0.255 */ diff -r -c -w -N linux-2.4.18-10/include/linux/inetdevice.h linux-2.4.18-10-igmpv3/include/linux/inetdevice.h *** linux-2.4.18-10/include/linux/inetdevice.h Wed Aug 7 09:31:35 2002 --- linux-2.4.18-10-igmpv3/include/linux/inetdevice.h Wed Aug 28 17:34:03 2002 *************** *** 31,37 **** --- 31,43 ---- rwlock_t lock; int dead; struct in_ifaddr *ifa_list; /* IP ifaddr chain */ + + #ifdef CONFIG_IP_IGMPV3 + struct in_multi *mc_list; /* IP multicast filter chain */ + #else struct ip_mc_list *mc_list; /* IP multicast filter chain */ + #endif /* CONFIG_IP_IGMPV3 */ + unsigned long mr_v1_seen; struct neigh_parms *arp_parms; struct ipv4_devconf cnf; diff -r -c -w -N linux-2.4.18-10/include/linux/sockios.h linux-2.4.18-10-igmpv3/include/linux/sockios.h *** linux-2.4.18-10/include/linux/sockios.h Wed Nov 7 17:39:36 2001 --- linux-2.4.18-10-igmpv3/include/linux/sockios.h Wed Aug 28 16:36:44 2002 *************** *** 114,119 **** --- 114,125 ---- #define SIOCBONDINFOQUERY 0x8994 /* rtn info about bond state */ #define SIOCBONDCHANGEACTIVE 0x8995 /* update to a new active slave */ + /* Multicast source filter calls */ + #define SIOCSIPMSFILTER 0x8990 /* set mcast src filter (ipv4) */ + #define SIOCGIPMSFILTER 0x8991 /* get mcast src filter (ipv4) */ + #define SIOCSMSFILTER 0x8992 /* set mcast src filter (proto indep) */ + #define SIOCGMSFILTER 0x8993 /* get mcast src filter (proto indep) */ + /* Device private ioctl calls */ /* diff -r -c -w -N linux-2.4.18-10/include/linux/sysctl.h linux-2.4.18-10-igmpv3/include/linux/sysctl.h *** linux-2.4.18-10/include/linux/sysctl.h Wed Aug 7 11:41:28 2002 --- linux-2.4.18-10-igmpv3/include/linux/sysctl.h Wed Aug 28 18:24:34 2002 *************** *** 293,299 **** NET_IPV4_NONLOCAL_BIND=88, NET_IPV4_ICMP_RATELIMIT=89, NET_IPV4_ICMP_RATEMASK=90, ! NET_TCP_TW_REUSE=91 }; enum { --- 293,302 ---- NET_IPV4_NONLOCAL_BIND=88, NET_IPV4_ICMP_RATELIMIT=89, NET_IPV4_ICMP_RATEMASK=90, ! NET_TCP_TW_REUSE=91, ! NET_IPV4_IGMP_MAX_GASS_QUERIES=92, ! NET_IPV4_IGMP_MAX_REC_SOURCES=93, ! NET_IPV4_IGMP_DEBUG_OUTPUT=94 }; enum { diff -r -c -w -N linux-2.4.18-10/include/net/sock.h linux-2.4.18-10-igmpv3/include/net/sock.h *** linux-2.4.18-10/include/net/sock.h Wed Aug 7 11:41:34 2002 --- linux-2.4.18-10-igmpv3/include/net/sock.h Wed Aug 28 17:34:01 2002 *************** *** 24,29 **** --- 24,31 ---- * Alan Cox : Eliminate low level recv/recvfrom * David S. Miller : New socket lookup architecture. * Steve Whitehouse: Default routines for sock_ops + * Vince Laviano : Added IGMPv3 state to struct inet_opt. + * (See license info in net/ipv4/igmpv3.c) * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License *************** *** 106,111 **** --- 108,116 ---- #include #include + #ifdef CONFIG_IP_IGMPV3 + #include + #endif /* CONFIG_IP_IGMPV3 */ /* The AF_UNIX specific socket options */ struct unix_opt { *************** *** 213,218 **** --- 218,233 ---- int mc_index; /* Multicast device index */ __u32 mc_addr; struct ip_mc_socklist *mc_list; /* Group array */ + + #ifdef CONFIG_IP_IGMPV3 + /* + * IGMPv3 fields. + */ + __u16 mc_num_memberships; + struct in_multi *mc_membership[IP_MAX_MEMBERSHIPS]; + struct sock_mcastsf mc_membershipsf[IP_MAX_MEMBERSHIPS]; + #endif /* CONFIG_IP_IGMPV3 */ + }; #endif diff -r -c -w -N linux-2.4.18-10/net/ipv4/Config.in linux-2.4.18-10-igmpv3/net/ipv4/Config.in *** linux-2.4.18-10/net/ipv4/Config.in Wed Aug 7 09:32:01 2002 --- linux-2.4.18-10-igmpv3/net/ipv4/Config.in Wed Aug 28 16:36:44 2002 *************** *** 2,7 **** --- 2,12 ---- # IP configuration # bool ' IP: multicasting' CONFIG_IP_MULTICAST + if [ "$CONFIG_IP_MULTICAST" = "y" ]; then + if [ "$CONFIG_EXPERIMENTAL" = "y" ]; then + bool ' IP: IGMPv3/MSF support (EXPERIMENTAL)' CONFIG_IP_IGMPV3 + fi + fi bool ' IP: advanced router' CONFIG_IP_ADVANCED_ROUTER if [ "$CONFIG_IP_ADVANCED_ROUTER" = "y" ]; then bool ' IP: policy routing' CONFIG_IP_MULTIPLE_TABLES diff -r -c -w -N linux-2.4.18-10/net/ipv4/Makefile linux-2.4.18-10-igmpv3/net/ipv4/Makefile *** linux-2.4.18-10/net/ipv4/Makefile Fri Dec 21 12:42:05 2001 --- linux-2.4.18-10-igmpv3/net/ipv4/Makefile Wed Aug 28 16:36:44 2002 *************** *** 15,21 **** ip_input.o ip_fragment.o ip_forward.o ip_options.o \ ip_output.o ip_sockglue.o \ tcp.o tcp_input.o tcp_output.o tcp_timer.o tcp_ipv4.o tcp_minisocks.o \ ! tcp_diag.o raw.o udp.o arp.o icmp.o devinet.o af_inet.o igmp.o \ sysctl_net_ipv4.o fib_frontend.o fib_semantics.o fib_hash.o obj-$(CONFIG_IP_MULTIPLE_TABLES) += fib_rules.o --- 15,21 ---- ip_input.o ip_fragment.o ip_forward.o ip_options.o \ ip_output.o ip_sockglue.o \ tcp.o tcp_input.o tcp_output.o tcp_timer.o tcp_ipv4.o tcp_minisocks.o \ ! tcp_diag.o raw.o udp.o arp.o icmp.o devinet.o af_inet.o igmpv2.o igmpv3.o \ sysctl_net_ipv4.o fib_frontend.o fib_semantics.o fib_hash.o obj-$(CONFIG_IP_MULTIPLE_TABLES) += fib_rules.o diff -r -c -w -N linux-2.4.18-10/net/ipv4/af_inet.c linux-2.4.18-10-igmpv3/net/ipv4/af_inet.c *** linux-2.4.18-10/net/ipv4/af_inet.c Wed Aug 7 09:31:35 2002 --- linux-2.4.18-10-igmpv3/net/ipv4/af_inet.c Wed Aug 28 16:36:44 2002 *************** *** 56,61 **** --- 56,62 ---- * Some other random speedups. * Cyrus Durgin : Cleaned up file for kmod hacks. * Andi Kleen : Fix inet_stream_connect TCP race. + * Vince Laviano : IGMPv3 ioctls. * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License *************** *** 924,929 **** --- 925,950 ---- #endif return -ENOPKG; + #ifdef CONFIG_IP_IGMPV3 + + /* + * Multicast source filter ioctls + */ + case SIOCSIPMSFILTER: + return in_setmcast_srcfilter(sk, arg); + + case SIOCGIPMSFILTER: + return in_getmcast_srcfilter(sk, arg); + + case SIOCSMSFILTER: + case SIOCGMSFILTER: + /* + * Protocol independent ioctls not supported, yet. + */ + return -EOPNOTSUPP; + + #endif /* CONFIG_IP_IGMPV3 */ + default: if ((cmd >= SIOCDEVPRIVATE) && (cmd <= (SIOCDEVPRIVATE + 15))) *************** *** 1162,1167 **** --- 1183,1197 ---- icmp_init(&inet_family_ops); + #ifdef CONFIG_IP_IGMPV3 + + /* + * Initialize IGMP + */ + igmp_init(); + + #endif /* CONFIG_IP_IGMPV3 */ + /* I wish inet_add_protocol had no constructor hook... I had to move IPIP from net/ipv4/protocol.c :-( --ANK */ diff -r -c -w -N linux-2.4.18-10/net/ipv4/igmp.c linux-2.4.18-10-igmpv3/net/ipv4/igmp.c *** linux-2.4.18-10/net/ipv4/igmp.c Sat Jul 28 15:12:38 2001 --- linux-2.4.18-10-igmpv3/net/ipv4/igmp.c Wed Dec 31 19:00:00 1969 *************** *** 1,835 **** - /* - * Linux NET3: Internet Group Management Protocol [IGMP] - * - * This code implements the IGMP protocol as defined in RFC1112. There has - * been a further revision of this protocol since which is now supported. - * - * If you have trouble with this module be careful what gcc you have used, - * the older version didn't come out right using gcc 2.5.8, the newer one - * seems to fall out with gcc 2.6.2. - * - * Version: $Id: igmp.c,v 1.46 2001/07/27 09:27:29 davem Exp $ - * - * Authors: - * Alan Cox - * - * This program is free software; you can redistribute it and/or - * modify it under the terms of the GNU General Public License - * as published by the Free Software Foundation; either version - * 2 of the License, or (at your option) any later version. - * - * Fixes: - * - * Alan Cox : Added lots of __inline__ to optimise - * the memory usage of all the tiny little - * functions. - * Alan Cox : Dumped the header building experiment. - * Alan Cox : Minor tweaks ready for multicast routing - * and extended IGMP protocol. - * Alan Cox : Removed a load of inline directives. Gcc 2.5.8 - * writes utterly bogus code otherwise (sigh) - * fixed IGMP loopback to behave in the manner - * desired by mrouted, fixed the fact it has been - * broken since 1.3.6 and cleaned up a few minor - * points. - * - * Chih-Jen Chang : Tried to revise IGMP to Version 2 - * Tsu-Sheng Tsao E-mail: chihjenc@scf.usc.edu and tsusheng@scf.usc.edu - * The enhancements are mainly based on Steve Deering's - * ipmulti-3.5 source code. - * Chih-Jen Chang : Added the igmp_get_mrouter_info and - * Tsu-Sheng Tsao igmp_set_mrouter_info to keep track of - * the mrouted version on that device. - * Chih-Jen Chang : Added the max_resp_time parameter to - * Tsu-Sheng Tsao igmp_heard_query(). Using this parameter - * to identify the multicast router version - * and do what the IGMP version 2 specified. - * Chih-Jen Chang : Added a timer to revert to IGMP V2 router - * Tsu-Sheng Tsao if the specified time expired. - * Alan Cox : Stop IGMP from 0.0.0.0 being accepted. - * Alan Cox : Use GFP_ATOMIC in the right places. - * Christian Daudt : igmp timer wasn't set for local group - * memberships but was being deleted, - * which caused a "del_timer() called - * from %p with timer not initialized\n" - * message (960131). - * Christian Daudt : removed del_timer from - * igmp_timer_expire function (960205). - * Christian Daudt : igmp_heard_report now only calls - * igmp_timer_expire if tm->running is - * true (960216). - * Malcolm Beattie : ttl comparison wrong in igmp_rcv made - * igmp_heard_query never trigger. Expiry - * miscalculation fixed in igmp_heard_query - * and random() made to return unsigned to - * prevent negative expiry times. - * Alexey Kuznetsov: Wrong group leaving behaviour, backport - * fix from pending 2.1.x patches. - * Alan Cox: Forget to enable FDDI support earlier. - * Alexey Kuznetsov: Fixed leaving groups on device down. - * Alexey Kuznetsov: Accordance to igmp-v2-06 draft. - */ - - - #include - #include - #include - #include - #include - #include - #include - #include - #include - #include - #include - #include - #include - #include - #include - #include - #include - #include - #include - #include - #include - #include - #include - #ifdef CONFIG_IP_MROUTE - #include - #endif - - - #define IP_MAX_MEMBERSHIPS 20 - - #ifdef CONFIG_IP_MULTICAST - - - /* Parameter names and values are taken from igmp-v2-06 draft */ - - #define IGMP_V1_Router_Present_Timeout (400*HZ) - #define IGMP_Unsolicited_Report_Interval (10*HZ) - #define IGMP_Query_Response_Interval (10*HZ) - #define IGMP_Unsolicited_Report_Count 2 - - - #define IGMP_Initial_Report_Delay (1) - - /* IGMP_Initial_Report_Delay is not from IGMP specs! - * IGMP specs require to report membership immediately after - * joining a group, but we delay the first report by a - * small interval. It seems more natural and still does not - * contradict to specs provided this delay is small enough. - */ - - #define IGMP_V1_SEEN(in_dev) ((in_dev)->mr_v1_seen && (long)(jiffies - (in_dev)->mr_v1_seen) < 0) - - #endif - - static void ip_ma_put(struct ip_mc_list *im) - { - if (atomic_dec_and_test(&im->refcnt)) { - in_dev_put(im->interface); - kfree(im); - } - } - - #ifdef CONFIG_IP_MULTICAST - - /* - * Timer management - */ - - static __inline__ void igmp_stop_timer(struct ip_mc_list *im) - { - spin_lock_bh(&im->lock); - if (del_timer(&im->timer)) - atomic_dec(&im->refcnt); - im->tm_running=0; - im->reporter = 0; - im->unsolicit_count = 0; - spin_unlock_bh(&im->lock); - } - - /* It must be called with locked im->lock */ - static void igmp_start_timer(struct ip_mc_list *im, int max_delay) - { - int tv=net_random() % max_delay; - - im->tm_running=1; - if (!mod_timer(&im->timer, jiffies+tv+2)) - atomic_inc(&im->refcnt); - } - - static void igmp_mod_timer(struct ip_mc_list *im, int max_delay) - { - spin_lock_bh(&im->lock); - im->unsolicit_count = 0; - if (del_timer(&im->timer)) { - if ((long)(im->timer.expires-jiffies) < max_delay) { - add_timer(&im->timer); - im->tm_running=1; - spin_unlock_bh(&im->lock); - return; - } - atomic_dec(&im->refcnt); - } - igmp_start_timer(im, max_delay); - spin_unlock_bh(&im->lock); - } - - - /* - * Send an IGMP report. - */ - - #define IGMP_SIZE (sizeof(struct igmphdr)+sizeof(struct iphdr)+4) - - /* Don't just hand NF_HOOK skb->dst->output, in case netfilter hook - changes route */ - static inline int - output_maybe_reroute(struct sk_buff *skb) - { - return skb->dst->output(skb); - } - - static int igmp_send_report(struct net_device *dev, u32 group, int type) - { - struct sk_buff *skb; - struct iphdr *iph; - struct igmphdr *ih; - struct rtable *rt; - u32 dst; - - /* According to IGMPv2 specs, LEAVE messages are - * sent to all-routers group. - */ - dst = group; - if (type == IGMP_HOST_LEAVE_MESSAGE) - dst = IGMP_ALL_ROUTER; - - if (ip_route_output(&rt, dst, 0, 0, dev->ifindex)) - return -1; - if (rt->rt_src == 0) { - ip_rt_put(rt); - return -1; - } - - skb=alloc_skb(IGMP_SIZE+dev->hard_header_len+15, GFP_ATOMIC); - if (skb == NULL) { - ip_rt_put(rt); - return -1; - } - - skb->dst = &rt->u.dst; - - skb_reserve(skb, (dev->hard_header_len+15)&~15); - - skb->nh.iph = iph = (struct iphdr *)skb_put(skb, sizeof(struct iphdr)+4); - - iph->version = 4; - iph->ihl = (sizeof(struct iphdr)+4)>>2; - iph->tos = 0; - iph->frag_off = __constant_htons(IP_DF); - iph->ttl = 1; - iph->daddr = dst; - iph->saddr = rt->rt_src; - iph->protocol = IPPROTO_IGMP; - iph->tot_len = htons(IGMP_SIZE); - ip_select_ident(iph, &rt->u.dst, NULL); - ((u8*)&iph[1])[0] = IPOPT_RA; - ((u8*)&iph[1])[1] = 4; - ((u8*)&iph[1])[2] = 0; - ((u8*)&iph[1])[3] = 0; - ip_send_check(iph); - - ih = (struct igmphdr *)skb_put(skb, sizeof(struct igmphdr)); - ih->type=type; - ih->code=0; - ih->csum=0; - ih->group=group; - ih->csum=ip_compute_csum((void *)ih, sizeof(struct igmphdr)); - - return NF_HOOK(PF_INET, NF_IP_LOCAL_OUT, skb, NULL, rt->u.dst.dev, - output_maybe_reroute); - } - - - static void igmp_timer_expire(unsigned long data) - { - struct ip_mc_list *im=(struct ip_mc_list *)data; - struct in_device *in_dev = im->interface; - int err; - - spin_lock(&im->lock); - im->tm_running=0; - - if (IGMP_V1_SEEN(in_dev)) - err = igmp_send_report(in_dev->dev, im->multiaddr, IGMP_HOST_MEMBERSHIP_REPORT); - else - err = igmp_send_report(in_dev->dev, im->multiaddr, IGMP_HOST_NEW_MEMBERSHIP_REPORT); - - /* Failed. Retry later. */ - if (err) { - if (!in_dev->dead) - igmp_start_timer(im, IGMP_Unsolicited_Report_Interval); - goto out; - } - - if (im->unsolicit_count) { - im->unsolicit_count--; - igmp_start_timer(im, IGMP_Unsolicited_Report_Interval); - } - im->reporter = 1; - out: - spin_unlock(&im->lock); - ip_ma_put(im); - } - - static void igmp_heard_report(struct in_device *in_dev, u32 group) - { - struct ip_mc_list *im; - - /* Timers are only set for non-local groups */ - - if (group == IGMP_ALL_HOSTS) - return; - - read_lock(&in_dev->lock); - for (im=in_dev->mc_list; im!=NULL; im=im->next) { - if (im->multiaddr == group) { - igmp_stop_timer(im); - break; - } - } - read_unlock(&in_dev->lock); - } - - static void igmp_heard_query(struct in_device *in_dev, unsigned char max_resp_time, - u32 group) - { - struct ip_mc_list *im; - int max_delay; - - max_delay = max_resp_time*(HZ/IGMP_TIMER_SCALE); - - if (max_resp_time == 0) { - /* Alas, old v1 router presents here. */ - - max_delay = IGMP_Query_Response_Interval; - in_dev->mr_v1_seen = jiffies + IGMP_V1_Router_Present_Timeout; - group = 0; - } - - /* - * - Start the timers in all of our membership records - * that the query applies to for the interface on - * which the query arrived excl. those that belong - * to a "local" group (224.0.0.X) - * - For timers already running check if they need to - * be reset. - * - Use the igmp->igmp_code field as the maximum - * delay possible - */ - read_lock(&in_dev->lock); - for (im=in_dev->mc_list; im!=NULL; im=im->next) { - if (group && group != im->multiaddr) - continue; - if (im->multiaddr == IGMP_ALL_HOSTS) - continue; - igmp_mod_timer(im, max_delay); - } - read_unlock(&in_dev->lock); - } - - int igmp_rcv(struct sk_buff *skb) - { - /* This basically follows the spec line by line -- see RFC1112 */ - struct igmphdr *ih = skb->h.igmph; - struct in_device *in_dev = in_dev_get(skb->dev); - int len = skb->len; - - if (in_dev==NULL) { - kfree_skb(skb); - return 0; - } - - if (skb_is_nonlinear(skb)) { - if (skb_linearize(skb, GFP_ATOMIC) != 0) { - kfree_skb(skb); - return -ENOMEM; - } - ih = skb->h.igmph; - } - - if (len < sizeof(struct igmphdr) || ip_compute_csum((void *)ih, len)) { - in_dev_put(in_dev); - kfree_skb(skb); - return 0; - } - - switch (ih->type) { - case IGMP_HOST_MEMBERSHIP_QUERY: - igmp_heard_query(in_dev, ih->code, ih->group); - break; - case IGMP_HOST_MEMBERSHIP_REPORT: - case IGMP_HOST_NEW_MEMBERSHIP_REPORT: - /* Is it our report looped back? */ - if (((struct rtable*)skb->dst)->key.iif == 0) - break; - igmp_heard_report(in_dev, ih->group); - break; - case IGMP_PIM: - #ifdef CONFIG_IP_PIMSM_V1 - in_dev_put(in_dev); - return pim_rcv_v1(skb); - #endif - case IGMP_DVMRP: - case IGMP_TRACE: - case IGMP_HOST_LEAVE_MESSAGE: - case IGMP_MTRACE: - case IGMP_MTRACE_RESP: - break; - default: - NETDEBUG(printk(KERN_DEBUG "New IGMP type=%d, why we do not know about it?\n", ih->type)); - } - in_dev_put(in_dev); - kfree_skb(skb); - return 0; - } - - #endif - - - /* - * Add a filter to a device - */ - - static void ip_mc_filter_add(struct in_device *in_dev, u32 addr) - { - char buf[MAX_ADDR_LEN]; - struct net_device *dev = in_dev->dev; - - /* Checking for IFF_MULTICAST here is WRONG-WRONG-WRONG. - We will get multicast token leakage, when IFF_MULTICAST - is changed. This check should be done in dev->set_multicast_list - routine. Something sort of: - if (dev->mc_list && dev->flags&IFF_MULTICAST) { do it; } - --ANK - */ - if (arp_mc_map(addr, buf, dev, 0) == 0) - dev_mc_add(dev,buf,dev->addr_len,0); - } - - /* - * Remove a filter from a device - */ - - static void ip_mc_filter_del(struct in_device *in_dev, u32 addr) - { - char buf[MAX_ADDR_LEN]; - struct net_device *dev = in_dev->dev; - - if (arp_mc_map(addr, buf, dev, 0) == 0) - dev_mc_delete(dev,buf,dev->addr_len,0); - } - - static void igmp_group_dropped(struct ip_mc_list *im) - { - #ifdef CONFIG_IP_MULTICAST - int reporter; - #endif - - if (im->loaded) { - im->loaded = 0; - ip_mc_filter_del(im->interface, im->multiaddr); - } - - #ifdef CONFIG_IP_MULTICAST - if (im->multiaddr == IGMP_ALL_HOSTS) - return; - - reporter = im->reporter; - igmp_stop_timer(im); - - if (reporter && !IGMP_V1_SEEN(im->interface)) - igmp_send_report(im->interface->dev, im->multiaddr, IGMP_HOST_LEAVE_MESSAGE); - #endif - } - - static void igmp_group_added(struct ip_mc_list *im) - { - if (im->loaded == 0) { - im->loaded = 1; - ip_mc_filter_add(im->interface, im->multiaddr); - } - - #ifdef CONFIG_IP_MULTICAST - if (im->multiaddr == IGMP_ALL_HOSTS) - return; - - spin_lock_bh(&im->lock); - igmp_start_timer(im, IGMP_Initial_Report_Delay); - spin_unlock_bh(&im->lock); - #endif - } - - - /* - * Multicast list managers - */ - - - /* - * A socket has joined a multicast group on device dev. - */ - - void ip_mc_inc_group(struct in_device *in_dev, u32 addr) - { - struct ip_mc_list *im; - - ASSERT_RTNL(); - - for (im=in_dev->mc_list; im; im=im->next) { - if (im->multiaddr == addr) { - im->users++; - goto out; - } - } - - im = (struct ip_mc_list *)kmalloc(sizeof(*im), GFP_KERNEL); - if (!im) - goto out; - - im->users=1; - im->interface=in_dev; - in_dev_hold(in_dev); - im->multiaddr=addr; - atomic_set(&im->refcnt, 1); - spin_lock_init(&im->lock); - #ifdef CONFIG_IP_MULTICAST - im->tm_running=0; - init_timer(&im->timer); - im->timer.data=(unsigned long)im; - im->timer.function=&igmp_timer_expire; - im->unsolicit_count = IGMP_Unsolicited_Report_Count; - im->reporter = 0; - #endif - im->loaded = 0; - write_lock_bh(&in_dev->lock); - im->next=in_dev->mc_list; - in_dev->mc_list=im; - write_unlock_bh(&in_dev->lock); - igmp_group_added(im); - if (in_dev->dev->flags & IFF_UP) - ip_rt_multicast_event(in_dev); - out: - return; - } - - /* - * A socket has left a multicast group on device dev - */ - - int ip_mc_dec_group(struct in_device *in_dev, u32 addr) - { - int err = -ESRCH; - struct ip_mc_list *i, **ip; - - ASSERT_RTNL(); - - for (ip=&in_dev->mc_list; (i=*ip)!=NULL; ip=&i->next) { - if (i->multiaddr==addr) { - if (--i->users == 0) { - write_lock_bh(&in_dev->lock); - *ip = i->next; - write_unlock_bh(&in_dev->lock); - igmp_group_dropped(i); - - if (in_dev->dev->flags & IFF_UP) - ip_rt_multicast_event(in_dev); - - ip_ma_put(i); - return 0; - } - err = 0; - break; - } - } - return -ESRCH; - } - - /* Device going down */ - - void ip_mc_down(struct in_device *in_dev) - { - struct ip_mc_list *i; - - ASSERT_RTNL(); - - for (i=in_dev->mc_list; i; i=i->next) - igmp_group_dropped(i); - - ip_mc_dec_group(in_dev, IGMP_ALL_HOSTS); - } - - /* Device going up */ - - void ip_mc_up(struct in_device *in_dev) - { - struct ip_mc_list *i; - - ASSERT_RTNL(); - - ip_mc_inc_group(in_dev, IGMP_ALL_HOSTS); - - for (i=in_dev->mc_list; i; i=i->next) - igmp_group_added(i); - } - - /* - * Device is about to be destroyed: clean up. - */ - - void ip_mc_destroy_dev(struct in_device *in_dev) - { - struct ip_mc_list *i; - - ASSERT_RTNL(); - - write_lock_bh(&in_dev->lock); - while ((i = in_dev->mc_list) != NULL) { - in_dev->mc_list = i->next; - write_unlock_bh(&in_dev->lock); - - igmp_group_dropped(i); - ip_ma_put(i); - - write_lock_bh(&in_dev->lock); - } - write_unlock_bh(&in_dev->lock); - } - - static struct in_device * ip_mc_find_dev(struct ip_mreqn *imr) - { - struct rtable *rt; - struct net_device *dev = NULL; - struct in_device *idev = NULL; - - if (imr->imr_address.s_addr) { - dev = ip_dev_find(imr->imr_address.s_addr); - if (!dev) - return NULL; - __dev_put(dev); - } - - if (!dev && !ip_route_output(&rt, imr->imr_multiaddr.s_addr, 0, 0, 0)) { - dev = rt->u.dst.dev; - ip_rt_put(rt); - } - if (dev) { - imr->imr_ifindex = dev->ifindex; - idev = __in_dev_get(dev); - } - return idev; - } - - /* - * Join a socket to a group - */ - int sysctl_igmp_max_memberships = IP_MAX_MEMBERSHIPS; - - int ip_mc_join_group(struct sock *sk , struct ip_mreqn *imr) - { - int err; - u32 addr = imr->imr_multiaddr.s_addr; - struct ip_mc_socklist *iml, *i; - struct in_device *in_dev; - int count = 0; - - if (!MULTICAST(addr)) - return -EINVAL; - - rtnl_shlock(); - - if (!imr->imr_ifindex) - in_dev = ip_mc_find_dev(imr); - else { - in_dev = inetdev_by_index(imr->imr_ifindex); - if (in_dev) - __in_dev_put(in_dev); - } - - if (!in_dev) { - iml = NULL; - err = -ENODEV; - goto done; - } - - iml = (struct ip_mc_socklist *)sock_kmalloc(sk, sizeof(*iml), GFP_KERNEL); - - err = -EADDRINUSE; - for (i=sk->protinfo.af_inet.mc_list; i; i=i->next) { - if (memcmp(&i->multi, imr, sizeof(*imr)) == 0) { - /* New style additions are reference counted */ - if (imr->imr_address.s_addr == 0) { - i->count++; - err = 0; - } - goto done; - } - count++; - } - err = -ENOBUFS; - if (iml == NULL || count >= sysctl_igmp_max_memberships) - goto done; - memcpy(&iml->multi, imr, sizeof(*imr)); - iml->next = sk->protinfo.af_inet.mc_list; - iml->count = 1; - sk->protinfo.af_inet.mc_list = iml; - ip_mc_inc_group(in_dev, addr); - iml = NULL; - err = 0; - - done: - rtnl_shunlock(); - if (iml) - sock_kfree_s(sk, iml, sizeof(*iml)); - return err; - } - - /* - * Ask a socket to leave a group. - */ - - int ip_mc_leave_group(struct sock *sk, struct ip_mreqn *imr) - { - struct ip_mc_socklist *iml, **imlp; - - rtnl_lock(); - for (imlp=&sk->protinfo.af_inet.mc_list; (iml=*imlp)!=NULL; imlp=&iml->next) { - if (iml->multi.imr_multiaddr.s_addr==imr->imr_multiaddr.s_addr && - iml->multi.imr_address.s_addr==imr->imr_address.s_addr && - (!imr->imr_ifindex || iml->multi.imr_ifindex==imr->imr_ifindex)) { - struct in_device *in_dev; - if (--iml->count) { - rtnl_unlock(); - return 0; - } - - *imlp = iml->next; - - in_dev = inetdev_by_index(iml->multi.imr_ifindex); - if (in_dev) { - ip_mc_dec_group(in_dev, imr->imr_multiaddr.s_addr); - in_dev_put(in_dev); - } - rtnl_unlock(); - sock_kfree_s(sk, iml, sizeof(*iml)); - return 0; - } - } - rtnl_unlock(); - return -EADDRNOTAVAIL; - } - - /* - * A socket is closing. - */ - - void ip_mc_drop_socket(struct sock *sk) - { - struct ip_mc_socklist *iml; - - if (sk->protinfo.af_inet.mc_list == NULL) - return; - - rtnl_lock(); - while ((iml=sk->protinfo.af_inet.mc_list) != NULL) { - struct in_device *in_dev; - sk->protinfo.af_inet.mc_list = iml->next; - - if ((in_dev = inetdev_by_index(iml->multi.imr_ifindex)) != NULL) { - ip_mc_dec_group(in_dev, iml->multi.imr_multiaddr.s_addr); - in_dev_put(in_dev); - } - sock_kfree_s(sk, iml, sizeof(*iml)); - - } - rtnl_unlock(); - } - - int ip_check_mc(struct in_device *in_dev, u32 mc_addr) - { - struct ip_mc_list *im; - - read_lock(&in_dev->lock); - for (im=in_dev->mc_list; im; im=im->next) { - if (im->multiaddr == mc_addr) { - read_unlock(&in_dev->lock); - return 1; - } - } - read_unlock(&in_dev->lock); - return 0; - } - - - #ifdef CONFIG_IP_MULTICAST - - int ip_mc_procinfo(char *buffer, char **start, off_t offset, int length) - { - off_t pos=0, begin=0; - struct ip_mc_list *im; - int len=0; - struct net_device *dev; - - len=sprintf(buffer,"Idx\tDevice : Count Querier\tGroup Users Timer\tReporter\n"); - - read_lock(&dev_base_lock); - for(dev = dev_base; dev; dev = dev->next) { - struct in_device *in_dev = in_dev_get(dev); - char *querier = "NONE"; - - if (in_dev == NULL) - continue; - - querier = IGMP_V1_SEEN(in_dev) ? "V1" : "V2"; - - len+=sprintf(buffer+len,"%d\t%-10s: %5d %7s\n", - dev->ifindex, dev->name, dev->mc_count, querier); - - read_lock(&in_dev->lock); - for (im = in_dev->mc_list; im; im = im->next) { - len+=sprintf(buffer+len, - "\t\t\t\t%08lX %5d %d:%08lX\t\t%d\n", - im->multiaddr, im->users, - im->tm_running, im->timer.expires-jiffies, im->reporter); - - pos=begin+len; - if(posoffset+length) { - read_unlock(&in_dev->lock); - in_dev_put(in_dev); - goto done; - } - } - read_unlock(&in_dev->lock); - in_dev_put(in_dev); - } - done: - read_unlock(&dev_base_lock); - - *start=buffer+(offset-begin); - len-=(offset-begin); - if(len>length) - len=length; - if(len<0) - len=0; - return len; - } - #endif - --- 0 ---- diff -r -c -w -N linux-2.4.18-10/net/ipv4/igmpv2.c linux-2.4.18-10-igmpv3/net/ipv4/igmpv2.c *** linux-2.4.18-10/net/ipv4/igmpv2.c Wed Dec 31 19:00:00 1969 --- linux-2.4.18-10-igmpv3/net/ipv4/igmpv2.c Wed Aug 28 16:36:44 2002 *************** *** 0 **** --- 1,846 ---- + /* + * Linux NET3: Internet Group Management Protocol [IGMP] + * + * This code implements the IGMP protocol as defined in RFC1112. There has + * been a further revision of this protocol since which is now supported. + * + * This code is obsoleted by the IGMPv3 implementation available in + * igmpv3.c. To enable IGMPv3, set CONFIG_IP_MULTICAST and + * CONFIG_IP_IGMPV3. + * + * If you have trouble with this module be careful what gcc you have used, + * the older version didn't come out right using gcc 2.5.8, the newer one + * seems to fall out with gcc 2.6.2. + * + * Version: $Id: igmp.c,v 1.46 2001/07/27 09:27:29 davem Exp $ + * + * Authors: + * Alan Cox + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version + * 2 of the License, or (at your option) any later version. + * + * Fixes: + * + * Alan Cox : Added lots of __inline__ to optimise + * the memory usage of all the tiny little + * functions. + * Alan Cox : Dumped the header building experiment. + * Alan Cox : Minor tweaks ready for multicast routing + * and extended IGMP protocol. + * Alan Cox : Removed a load of inline directives. Gcc 2.5.8 + * writes utterly bogus code otherwise (sigh) + * fixed IGMP loopback to behave in the manner + * desired by mrouted, fixed the fact it has been + * broken since 1.3.6 and cleaned up a few minor + * points. + * + * Chih-Jen Chang : Tried to revise IGMP to Version 2 + * Tsu-Sheng Tsao E-mail: chihjenc@scf.usc.edu and tsusheng@scf.usc.edu + * The enhancements are mainly based on Steve Deering's + * ipmulti-3.5 source code. + * Chih-Jen Chang : Added the igmp_get_mrouter_info and + * Tsu-Sheng Tsao igmp_set_mrouter_info to keep track of + * the mrouted version on that device. + * Chih-Jen Chang : Added the max_resp_time parameter to + * Tsu-Sheng Tsao igmp_heard_query(). Using this parameter + * to identify the multicast router version + * and do what the IGMP version 2 specified. + * Chih-Jen Chang : Added a timer to revert to IGMP V2 router + * Tsu-Sheng Tsao if the specified time expired. + * Alan Cox : Stop IGMP from 0.0.0.0 being accepted. + * Alan Cox : Use GFP_ATOMIC in the right places. + * Christian Daudt : igmp timer wasn't set for local group + * memberships but was being deleted, + * which caused a "del_timer() called + * from %p with timer not initialized\n" + * message (960131). + * Christian Daudt : removed del_timer from + * igmp_timer_expire function (960205). + * Christian Daudt : igmp_heard_report now only calls + * igmp_timer_expire if tm->running is + * true (960216). + * Malcolm Beattie : ttl comparison wrong in igmp_rcv made + * igmp_heard_query never trigger. Expiry + * miscalculation fixed in igmp_heard_query + * and random() made to return unsigned to + * prevent negative expiry times. + * Alexey Kuznetsov: Wrong group leaving behaviour, backport + * fix from pending 2.1.x patches. + * Alan Cox: Forget to enable FDDI support earlier. + * Alexey Kuznetsov: Fixed leaving groups on device down. + * Alexey Kuznetsov: Accordance to igmp-v2-06 draft. + * Vince Laviano: IGMPv3 conditional compilation. + */ + + + #include + #include + #include + #include + #include + #include + #include + #include + #include + #include + #include + #include + #include + #include + #include + #include + #include + #include + #include + #include + #include + #include + #include + #ifdef CONFIG_IP_MROUTE + #include + #endif + + /* + * Only compile the contents of this file if IGMPv3 is not configured, in + * which case we're falling back to this original IGMPv2 code. + */ + #ifndef CONFIG_IP_IGMPV3 + + #define IP_MAX_MEMBERSHIPS 20 + + #ifdef CONFIG_IP_MULTICAST + + + /* Parameter names and values are taken from igmp-v2-06 draft */ + + #define IGMP_V1_Router_Present_Timeout (400*HZ) + #define IGMP_Unsolicited_Report_Interval (10*HZ) + #define IGMP_Query_Response_Interval (10*HZ) + #define IGMP_Unsolicited_Report_Count 2 + + + #define IGMP_Initial_Report_Delay (1) + + /* IGMP_Initial_Report_Delay is not from IGMP specs! + * IGMP specs require to report membership immediately after + * joining a group, but we delay the first report by a + * small interval. It seems more natural and still does not + * contradict to specs provided this delay is small enough. + */ + + #define IGMP_V1_SEEN(in_dev) ((in_dev)->mr_v1_seen && (long)(jiffies - (in_dev)->mr_v1_seen) < 0) + + #endif + + static void ip_ma_put(struct ip_mc_list *im) + { + if (atomic_dec_and_test(&im->refcnt)) { + in_dev_put(im->interface); + kfree(im); + } + } + + #ifdef CONFIG_IP_MULTICAST + + /* + * Timer management + */ + + static __inline__ void igmp_stop_timer(struct ip_mc_list *im) + { + spin_lock_bh(&im->lock); + if (del_timer(&im->timer)) + atomic_dec(&im->refcnt); + im->tm_running=0; + im->reporter = 0; + im->unsolicit_count = 0; + spin_unlock_bh(&im->lock); + } + + /* It must be called with locked im->lock */ + static void igmp_start_timer(struct ip_mc_list *im, int max_delay) + { + int tv=net_random() % max_delay; + + im->tm_running=1; + if (!mod_timer(&im->timer, jiffies+tv+2)) + atomic_inc(&im->refcnt); + } + + static void igmp_mod_timer(struct ip_mc_list *im, int max_delay) + { + spin_lock_bh(&im->lock); + im->unsolicit_count = 0; + if (del_timer(&im->timer)) { + if ((long)(im->timer.expires-jiffies) < max_delay) { + add_timer(&im->timer); + im->tm_running=1; + spin_unlock_bh(&im->lock); + return; + } + atomic_dec(&im->refcnt); + } + igmp_start_timer(im, max_delay); + spin_unlock_bh(&im->lock); + } + + + /* + * Send an IGMP report. + */ + + #define IGMP_SIZE (sizeof(struct igmphdr)+sizeof(struct iphdr)+4) + + /* Don't just hand NF_HOOK skb->dst->output, in case netfilter hook + changes route */ + static inline int + output_maybe_reroute(struct sk_buff *skb) + { + return skb->dst->output(skb); + } + + static int igmp_send_report(struct net_device *dev, u32 group, int type) + { + struct sk_buff *skb; + struct iphdr *iph; + struct igmphdr *ih; + struct rtable *rt; + u32 dst; + + /* According to IGMPv2 specs, LEAVE messages are + * sent to all-routers group. + */ + dst = group; + if (type == IGMP_HOST_LEAVE_MESSAGE) + dst = IGMP_ALL_ROUTER; + + if (ip_route_output(&rt, dst, 0, 0, dev->ifindex)) + return -1; + if (rt->rt_src == 0) { + ip_rt_put(rt); + return -1; + } + + skb=alloc_skb(IGMP_SIZE+dev->hard_header_len+15, GFP_ATOMIC); + if (skb == NULL) { + ip_rt_put(rt); + return -1; + } + + skb->dst = &rt->u.dst; + + skb_reserve(skb, (dev->hard_header_len+15)&~15); + + skb->nh.iph = iph = (struct iphdr *)skb_put(skb, sizeof(struct iphdr)+4); + + iph->version = 4; + iph->ihl = (sizeof(struct iphdr)+4)>>2; + iph->tos = 0; + iph->frag_off = __constant_htons(IP_DF); + iph->ttl = 1; + iph->daddr = dst; + iph->saddr = rt->rt_src; + iph->protocol = IPPROTO_IGMP; + iph->tot_len = htons(IGMP_SIZE); + ip_select_ident(iph, &rt->u.dst, NULL); + ((u8*)&iph[1])[0] = IPOPT_RA; + ((u8*)&iph[1])[1] = 4; + ((u8*)&iph[1])[2] = 0; + ((u8*)&iph[1])[3] = 0; + ip_send_check(iph); + + ih = (struct igmphdr *)skb_put(skb, sizeof(struct igmphdr)); + ih->type=type; + ih->code=0; + ih->csum=0; + ih->group=group; + ih->csum=ip_compute_csum((void *)ih, sizeof(struct igmphdr)); + + return NF_HOOK(PF_INET, NF_IP_LOCAL_OUT, skb, NULL, rt->u.dst.dev, + output_maybe_reroute); + } + + + static void igmp_timer_expire(unsigned long data) + { + struct ip_mc_list *im=(struct ip_mc_list *)data; + struct in_device *in_dev = im->interface; + int err; + + spin_lock(&im->lock); + im->tm_running=0; + + if (IGMP_V1_SEEN(in_dev)) + err = igmp_send_report(in_dev->dev, im->multiaddr, IGMP_HOST_MEMBERSHIP_REPORT); + else + err = igmp_send_report(in_dev->dev, im->multiaddr, IGMP_HOST_NEW_MEMBERSHIP_REPORT); + + /* Failed. Retry later. */ + if (err) { + if (!in_dev->dead) + igmp_start_timer(im, IGMP_Unsolicited_Report_Interval); + goto out; + } + + if (im->unsolicit_count) { + im->unsolicit_count--; + igmp_start_timer(im, IGMP_Unsolicited_Report_Interval); + } + im->reporter = 1; + out: + spin_unlock(&im->lock); + ip_ma_put(im); + } + + static void igmp_heard_report(struct in_device *in_dev, u32 group) + { + struct ip_mc_list *im; + + /* Timers are only set for non-local groups */ + + if (group == IGMP_ALL_HOSTS) + return; + + read_lock(&in_dev->lock); + for (im=in_dev->mc_list; im!=NULL; im=im->next) { + if (im->multiaddr == group) { + igmp_stop_timer(im); + break; + } + } + read_unlock(&in_dev->lock); + } + + static void igmp_heard_query(struct in_device *in_dev, unsigned char max_resp_time, + u32 group) + { + struct ip_mc_list *im; + int max_delay; + + max_delay = max_resp_time*(HZ/IGMP_TIMER_SCALE); + + if (max_resp_time == 0) { + /* Alas, old v1 router presents here. */ + + max_delay = IGMP_Query_Response_Interval; + in_dev->mr_v1_seen = jiffies + IGMP_V1_Router_Present_Timeout; + group = 0; + } + + /* + * - Start the timers in all of our membership records + * that the query applies to for the interface on + * which the query arrived excl. those that belong + * to a "local" group (224.0.0.X) + * - For timers already running check if they need to + * be reset. + * - Use the igmp->igmp_code field as the maximum + * delay possible + */ + read_lock(&in_dev->lock); + for (im=in_dev->mc_list; im!=NULL; im=im->next) { + if (group && group != im->multiaddr) + continue; + if (im->multiaddr == IGMP_ALL_HOSTS) + continue; + igmp_mod_timer(im, max_delay); + } + read_unlock(&in_dev->lock); + } + + int igmp_rcv(struct sk_buff *skb) + { + /* This basically follows the spec line by line -- see RFC1112 */ + struct igmphdr *ih = skb->h.igmph; + struct in_device *in_dev = in_dev_get(skb->dev); + int len = skb->len; + + if (in_dev==NULL) { + kfree_skb(skb); + return 0; + } + + if (skb_is_nonlinear(skb)) { + if (skb_linearize(skb, GFP_ATOMIC) != 0) { + kfree_skb(skb); + return -ENOMEM; + } + ih = skb->h.igmph; + } + + if (len < sizeof(struct igmphdr) || ip_compute_csum((void *)ih, len)) { + in_dev_put(in_dev); + kfree_skb(skb); + return 0; + } + + switch (ih->type) { + case IGMP_HOST_MEMBERSHIP_QUERY: + igmp_heard_query(in_dev, ih->code, ih->group); + break; + case IGMP_HOST_MEMBERSHIP_REPORT: + case IGMP_HOST_NEW_MEMBERSHIP_REPORT: + /* Is it our report looped back? */ + if (((struct rtable*)skb->dst)->key.iif == 0) + break; + igmp_heard_report(in_dev, ih->group); + break; + case IGMP_PIM: + #ifdef CONFIG_IP_PIMSM_V1 + in_dev_put(in_dev); + return pim_rcv_v1(skb); + #endif + case IGMP_DVMRP: + case IGMP_TRACE: + case IGMP_HOST_LEAVE_MESSAGE: + case IGMP_MTRACE: + case IGMP_MTRACE_RESP: + break; + default: + NETDEBUG(printk(KERN_DEBUG "New IGMP type=%d, why we do not know about it?\n", ih->type)); + } + in_dev_put(in_dev); + kfree_skb(skb); + return 0; + } + + #endif + + + /* + * Add a filter to a device + */ + + static void ip_mc_filter_add(struct in_device *in_dev, u32 addr) + { + char buf[MAX_ADDR_LEN]; + struct net_device *dev = in_dev->dev; + + /* Checking for IFF_MULTICAST here is WRONG-WRONG-WRONG. + We will get multicast token leakage, when IFF_MULTICAST + is changed. This check should be done in dev->set_multicast_list + routine. Something sort of: + if (dev->mc_list && dev->flags&IFF_MULTICAST) { do it; } + --ANK + */ + if (arp_mc_map(addr, buf, dev, 0) == 0) + dev_mc_add(dev,buf,dev->addr_len,0); + } + + /* + * Remove a filter from a device + */ + + static void ip_mc_filter_del(struct in_device *in_dev, u32 addr) + { + char buf[MAX_ADDR_LEN]; + struct net_device *dev = in_dev->dev; + + if (arp_mc_map(addr, buf, dev, 0) == 0) + dev_mc_delete(dev,buf,dev->addr_len,0); + } + + static void igmp_group_dropped(struct ip_mc_list *im) + { + #ifdef CONFIG_IP_MULTICAST + int reporter; + #endif + + if (im->loaded) { + im->loaded = 0; + ip_mc_filter_del(im->interface, im->multiaddr); + } + + #ifdef CONFIG_IP_MULTICAST + if (im->multiaddr == IGMP_ALL_HOSTS) + return; + + reporter = im->reporter; + igmp_stop_timer(im); + + if (reporter && !IGMP_V1_SEEN(im->interface)) + igmp_send_report(im->interface->dev, im->multiaddr, IGMP_HOST_LEAVE_MESSAGE); + #endif + } + + static void igmp_group_added(struct ip_mc_list *im) + { + if (im->loaded == 0) { + im->loaded = 1; + ip_mc_filter_add(im->interface, im->multiaddr); + } + + #ifdef CONFIG_IP_MULTICAST + if (im->multiaddr == IGMP_ALL_HOSTS) + return; + + spin_lock_bh(&im->lock); + igmp_start_timer(im, IGMP_Initial_Report_Delay); + spin_unlock_bh(&im->lock); + #endif + } + + + /* + * Multicast list managers + */ + + + /* + * A socket has joined a multicast group on device dev. + */ + + void ip_mc_inc_group(struct in_device *in_dev, u32 addr) + { + struct ip_mc_list *im; + + ASSERT_RTNL(); + + for (im=in_dev->mc_list; im; im=im->next) { + if (im->multiaddr == addr) { + im->users++; + goto out; + } + } + + im = (struct ip_mc_list *)kmalloc(sizeof(*im), GFP_KERNEL); + if (!im) + goto out; + + im->users=1; + im->interface=in_dev; + in_dev_hold(in_dev); + im->multiaddr=addr; + atomic_set(&im->refcnt, 1); + spin_lock_init(&im->lock); + #ifdef CONFIG_IP_MULTICAST + im->tm_running=0; + init_timer(&im->timer); + im->timer.data=(unsigned long)im; + im->timer.function=&igmp_timer_expire; + im->unsolicit_count = IGMP_Unsolicited_Report_Count; + im->reporter = 0; + #endif + im->loaded = 0; + write_lock_bh(&in_dev->lock); + im->next=in_dev->mc_list; + in_dev->mc_list=im; + write_unlock_bh(&in_dev->lock); + igmp_group_added(im); + if (in_dev->dev->flags & IFF_UP) + ip_rt_multicast_event(in_dev); + out: + return; + } + + /* + * A socket has left a multicast group on device dev + */ + + int ip_mc_dec_group(struct in_device *in_dev, u32 addr) + { + int err = -ESRCH; + struct ip_mc_list *i, **ip; + + ASSERT_RTNL(); + + for (ip=&in_dev->mc_list; (i=*ip)!=NULL; ip=&i->next) { + if (i->multiaddr==addr) { + if (--i->users == 0) { + write_lock_bh(&in_dev->lock); + *ip = i->next; + write_unlock_bh(&in_dev->lock); + igmp_group_dropped(i); + + if (in_dev->dev->flags & IFF_UP) + ip_rt_multicast_event(in_dev); + + ip_ma_put(i); + return 0; + } + err = 0; + break; + } + } + return -ESRCH; + } + + /* Device going down */ + + void ip_mc_down(struct in_device *in_dev) + { + struct ip_mc_list *i; + + ASSERT_RTNL(); + + for (i=in_dev->mc_list; i; i=i->next) + igmp_group_dropped(i); + + ip_mc_dec_group(in_dev, IGMP_ALL_HOSTS); + } + + /* Device going up */ + + void ip_mc_up(struct in_device *in_dev) + { + struct ip_mc_list *i; + + ASSERT_RTNL(); + + ip_mc_inc_group(in_dev, IGMP_ALL_HOSTS); + + for (i=in_dev->mc_list; i; i=i->next) + igmp_group_added(i); + } + + /* + * Device is about to be destroyed: clean up. + */ + + void ip_mc_destroy_dev(struct in_device *in_dev) + { + struct ip_mc_list *i; + + ASSERT_RTNL(); + + write_lock_bh(&in_dev->lock); + while ((i = in_dev->mc_list) != NULL) { + in_dev->mc_list = i->next; + write_unlock_bh(&in_dev->lock); + + igmp_group_dropped(i); + ip_ma_put(i); + + write_lock_bh(&in_dev->lock); + } + write_unlock_bh(&in_dev->lock); + } + + static struct in_device * ip_mc_find_dev(struct ip_mreqn *imr) + { + struct rtable *rt; + struct net_device *dev = NULL; + struct in_device *idev = NULL; + + if (imr->imr_address.s_addr) { + dev = ip_dev_find(imr->imr_address.s_addr); + if (!dev) + return NULL; + __dev_put(dev); + } + + if (!dev && !ip_route_output(&rt, imr->imr_multiaddr.s_addr, 0, 0, 0)) { + dev = rt->u.dst.dev; + ip_rt_put(rt); + } + if (dev) { + imr->imr_ifindex = dev->ifindex; + idev = __in_dev_get(dev); + } + return idev; + } + + /* + * Join a socket to a group + */ + int sysctl_igmp_max_memberships = IP_MAX_MEMBERSHIPS; + + int ip_mc_join_group(struct sock *sk , struct ip_mreqn *imr) + { + int err; + u32 addr = imr->imr_multiaddr.s_addr; + struct ip_mc_socklist *iml, *i; + struct in_device *in_dev; + int count = 0; + + if (!MULTICAST(addr)) + return -EINVAL; + + rtnl_shlock(); + + if (!imr->imr_ifindex) + in_dev = ip_mc_find_dev(imr); + else { + in_dev = inetdev_by_index(imr->imr_ifindex); + if (in_dev) + __in_dev_put(in_dev); + } + + if (!in_dev) { + iml = NULL; + err = -ENODEV; + goto done; + } + + iml = (struct ip_mc_socklist *)sock_kmalloc(sk, sizeof(*iml), GFP_KERNEL); + + err = -EADDRINUSE; + for (i=sk->protinfo.af_inet.mc_list; i; i=i->next) { + if (memcmp(&i->multi, imr, sizeof(*imr)) == 0) { + /* New style additions are reference counted */ + if (imr->imr_address.s_addr == 0) { + i->count++; + err = 0; + } + goto done; + } + count++; + } + err = -ENOBUFS; + if (iml == NULL || count >= sysctl_igmp_max_memberships) + goto done; + memcpy(&iml->multi, imr, sizeof(*imr)); + iml->next = sk->protinfo.af_inet.mc_list; + iml->count = 1; + sk->protinfo.af_inet.mc_list = iml; + ip_mc_inc_group(in_dev, addr); + iml = NULL; + err = 0; + + done: + rtnl_shunlock(); + if (iml) + sock_kfree_s(sk, iml, sizeof(*iml)); + return err; + } + + /* + * Ask a socket to leave a group. + */ + + int ip_mc_leave_group(struct sock *sk, struct ip_mreqn *imr) + { + struct ip_mc_socklist *iml, **imlp; + + rtnl_lock(); + for (imlp=&sk->protinfo.af_inet.mc_list; (iml=*imlp)!=NULL; imlp=&iml->next) { + if (iml->multi.imr_multiaddr.s_addr==imr->imr_multiaddr.s_addr && + iml->multi.imr_address.s_addr==imr->imr_address.s_addr && + (!imr->imr_ifindex || iml->multi.imr_ifindex==imr->imr_ifindex)) { + struct in_device *in_dev; + if (--iml->count) { + rtnl_unlock(); + return 0; + } + + *imlp = iml->next; + + in_dev = inetdev_by_index(iml->multi.imr_ifindex); + if (in_dev) { + ip_mc_dec_group(in_dev, imr->imr_multiaddr.s_addr); + in_dev_put(in_dev); + } + rtnl_unlock(); + sock_kfree_s(sk, iml, sizeof(*iml)); + return 0; + } + } + rtnl_unlock(); + return -EADDRNOTAVAIL; + } + + /* + * A socket is closing. + */ + + void ip_mc_drop_socket(struct sock *sk) + { + struct ip_mc_socklist *iml; + + if (sk->protinfo.af_inet.mc_list == NULL) + return; + + rtnl_lock(); + while ((iml=sk->protinfo.af_inet.mc_list) != NULL) { + struct in_device *in_dev; + sk->protinfo.af_inet.mc_list = iml->next; + + if ((in_dev = inetdev_by_index(iml->multi.imr_ifindex)) != NULL) { + ip_mc_dec_group(in_dev, iml->multi.imr_multiaddr.s_addr); + in_dev_put(in_dev); + } + sock_kfree_s(sk, iml, sizeof(*iml)); + + } + rtnl_unlock(); + } + + int ip_check_mc(struct in_device *in_dev, u32 mc_addr) + { + struct ip_mc_list *im; + + read_lock(&in_dev->lock); + for (im=in_dev->mc_list; im; im=im->next) { + if (im->multiaddr == mc_addr) { + read_unlock(&in_dev->lock); + return 1; + } + } + read_unlock(&in_dev->lock); + return 0; + } + + + #ifdef CONFIG_IP_MULTICAST + + int ip_mc_procinfo(char *buffer, char **start, off_t offset, int length) + { + off_t pos=0, begin=0; + struct ip_mc_list *im; + int len=0; + struct net_device *dev; + + len=sprintf(buffer,"Idx\tDevice : Count Querier\tGroup Users Timer\tReporter\n"); + + read_lock(&dev_base_lock); + for(dev = dev_base; dev; dev = dev->next) { + struct in_device *in_dev = in_dev_get(dev); + char *querier = "NONE"; + + if (in_dev == NULL) + continue; + + querier = IGMP_V1_SEEN(in_dev) ? "V1" : "V2"; + + len+=sprintf(buffer+len,"%d\t%-10s: %5d %7s\n", + dev->ifindex, dev->name, dev->mc_count, querier); + + read_lock(&in_dev->lock); + for (im = in_dev->mc_list; im; im = im->next) { + len+=sprintf(buffer+len, + "\t\t\t\t%08lX %5d %d:%08lX\t\t%d\n", + im->multiaddr, im->users, + im->tm_running, im->timer.expires-jiffies, im->reporter); + + pos=begin+len; + if(posoffset+length) { + read_unlock(&in_dev->lock); + in_dev_put(in_dev); + goto done; + } + } + read_unlock(&in_dev->lock); + in_dev_put(in_dev); + } + done: + read_unlock(&dev_base_lock); + + *start=buffer+(offset-begin); + len-=(offset-begin); + if(len>length) + len=length; + if(len<0) + len=0; + return len; + } + #endif + #endif /* CONFIG_IP_IGMPV3 */ + diff -r -c -w -N linux-2.4.18-10/net/ipv4/igmpv3.c linux-2.4.18-10-igmpv3/net/ipv4/igmpv3.c *** linux-2.4.18-10/net/ipv4/igmpv3.c Wed Dec 31 19:00:00 1969 --- linux-2.4.18-10-igmpv3/net/ipv4/igmpv3.c Thu Aug 29 16:07:55 2002 *************** *** 0 **** --- 1,3799 ---- + /* + * Internet Group Management Protocol Version 3 (IGMPv3) + * + * This code implements the IGMPv3 protocol as defined in + * draft-ietf-idmr-igmp-v3-11.txt. IGMPv3 is backwards compatible with + * IGMPv2 as defined in RFC 2236 and with IGMPv1 as defined in RFC 1112. + * To enable IGMPv3, set CONFIG_IP_MULTICAST and CONFIG_IP_IGMPV3. + * + * Version: $Id: igmp.c,v 1.46 2001/07/27 09:27:29 davem Exp $ + * + * Authors: + * Wilbert de Graaf (wilbert@kloosterhof.com) (FreeBSD impl) + * Vince Laviano (vlaviano@cisco.com) (Linux port) + * + * The majority of this code is derived from the FreeBSD IGMPv3 + * implementation by Wilbert de Graaf (wilbert@kloosterhof.com). See + * http://www.kloosterhof.com/~wilbert/igmpv3.html for details. + * + * Code was also derived from the earlier Linux IGMPv2 implementation, + * still available in igmpv2.c, by Alan Cox, Chih-Jen Chang, Tsu-Sheng + * Tsao, Christian Daudt, Malcolm Beattie, and Alexey Kuznetsov. + * + * Cisco would like to acknowledge the work of Sprint Labs towards + * IGMPv3 for Linux, which they have made publically available at + * www.sprintlabs.com/Department/IP-Interworking/multicast/linux-igmpv3/ + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version + * 2 of the License, or (at your option) any later version. + * + * Portions of this code are also licensed under the following terms: + * + * "The FreeBSD Copyright + * + * Copyright 1994-2002 FreeBSD, Inc. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE FREEBSD PROJECT ``AS IS'' AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE FREEBSD PROJECT OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + * The views and conclusions contained in the software and documentation + * are those of the authors and should not be interpreted as representing + * official policies, either expressed or implied, of the FreeBSD Project + * or FreeBSD, Inc." + */ + + #include + #include + #include + #include + #include + #include + #include + #include + #include + #include + #include + #include + #include + #include + #include + #include + #include + #include + #include + #include + #include + #include + #include + #ifdef CONFIG_IP_MROUTE + #include + #endif + + /* + * Only compile the contents of this file if IGMPv3 is configured. Otherwise, + * fall back to the original IGMPv2 implementation contained in igmpv2.c + */ + #ifdef CONFIG_IP_IGMPV3 + + /* + * New definitions and macros + */ + #define PR_FASTHZ (5) /* 5 fast timeouts per sec */ + #define PR_SLOWHZ (2) /* 2 slow timeouts per sec */ + #define IGMP_FASTTIMER_INTERVAL (HZ/PR_FASTHZ) /* 200 msecs */ + #define IGMP_SLOWTIMER_INTERVAL (HZ/PR_SLOWHZ) /* 500 msecs */ + + /* + * A DoS protection against successive group-and-source specific (gass) + * queries. Successive means queries that affect the same response. + */ + #define DEFAULT_MAX_MEMBERSHIPS 20 + #define DEFAULT_MAX_GASS_QUERIES 8 + #define DEFAULT_MAX_REC_SOURCES 64 + #define DEFAULT_DEBUG_OUTPUT 0 /* false */ + + /* + * When new source-filters are needed, allocate a few of them at once + */ + #define SF_NUMALLOC 8 + + /* + * Assigning a timer that is handled by the fast_timer procedure + */ + #define ASSIGN_FAST_TIMER(t, v) { (t) = (v); igmp_ft_dirty = 1; } + + /* + * Some address tests + */ + #define IS_IGMP_ALL_HOSTS(x) ((x.s_addr) == IGMP_ALL_HOSTS) + + #define IGMP_VALID_SFADDR(a)\ + (((a).s_addr != INADDR_ANY) && ((a).s_addr != INADDR_NONE) &&\ + (!MULTICAST((a).s_addr))) + + #define IPV4SPACE(x) ((x) << (2)) + + /* + * Make sf the start of the 'fmode' list of 'inm' + */ + #define SF_START(fmode, inm, sf)\ + (sf)=((fmode)==MCAST_EXCLUDE)?(inm)->inm_sflexcl:(inm)->inm_sflincl; + + #define SF_FIND(ip, start, sf)\ + for ((sf)=(start);(sf)!=NULL; (sf)=(sf)->isf_next) {\ + if ((sf)->isf_addr.s_addr == ip.s_addr) break;\ + else if ((sf)->isf_addr.s_addr>ip.s_addr) { (sf)=NULL; break;}} + + /* + * Clear the corresponding bits of 'mask' in all the tags, starting from start + */ + #define SF_CLEARTAG(start, sf, mask)\ + for ((sf)=(start);(sf)!=NULL;(sf)=((sf)->isf_next))\ + (sf)->isf_reporttag &= ~(mask); + + /* + * Of entries from 'start' that have 'mask' set, set the bits of 'smask' and + * clear them of 'cmask', in all the tags. + */ + #define SF_SETCLEARTAG(start, sf, mask, smask, cmask)\ + for ((sf)=(start);(sf)!=NULL;(sf)=((sf)->isf_next)){\ + if (((sf)->isf_reporttag & (mask)) == (mask)) {\ + (sf)->isf_reporttag |= (smask);\ + (sf)->isf_reporttag &= ~(cmask);\ + }\ + } + + /* + * Determine whether a list, starting from 'start' is empty + */ + #define SF_ISEMPTY(start, sf, is)\ + (is)=1;\ + for ((sf)=(start);(sf)!=NULL;(sf)=(sf)->isf_next)\ + if ((sf)->isf_refcount != 0) { (is)=0; break; } + + /* + * Expand LIST_HEAD macro + */ + struct in_multihead { + struct in_multi *lh_first; + } in_multihead; + + /* + * Macro for looking up the in_multi record for a given IP multicast address + * on a given interface. If no matching record is found, "inm" is set null. + * Things are simplified compared to BSD code, because we've eliminated + * struct ifmultiaddr. + */ + #define IN_LOOKUP_MULTI(addr, ifp, inm) \ + /* struct in_addr addr; */ \ + /* struct in_device *ifp; */ \ + /* struct in_multi *inm; */ \ + do { \ + for ((inm) = (ifp)->mc_list; (inm); \ + (inm) = (inm)->inm_next) { \ + if ((inm)->inm_addr.s_addr == (addr).s_addr) { \ + break; \ + } \ + } \ + } while(0) + + /* + * Macro to step through all of the in_multi records, one at a time. + * The current position is remembered in 'step', which the caller must + * provide. IN_FIRST_MULTI(), below, must be called to initialize 'step' + * and get the first record. Both macros return a NULL 'inm' when there + * are no remaining records. + */ + #define IN_NEXT_MULTI(step, inm)\ + /* struct in_multistep step; */\ + /* struct in_multi *inm; */\ + do {\ + if (((inm) = (step).i_inm) != NULL)\ + (step).i_inm = (step).i_inm->inm_link.le_next;\ + } while (0) + + #define IN_FIRST_MULTI(step, inm)\ + /* struct in_multistep step; */\ + /* struct in_multi *inm; */\ + do {\ + (step).i_inm = in_multihead.lh_first;\ + IN_NEXT_MULTI((step), (inm));\ + } while (0) + + /* + * BSD List Macro functions + */ + #define LIST_REMOVE(elm, field) \ + do { \ + if ((elm)->field.le_next != NULL) \ + (elm)->field.le_next->field.le_prev = \ + (elm)->field.le_prev; \ + *(elm)->field.le_prev = (elm)->field.le_next; \ + } while (0) + + #define LIST_INSERT_HEAD(head, elm, field) \ + do { \ + if (((elm)->field.le_next = (head)->lh_first) != NULL) \ + (head)->lh_first->field.le_prev = &(elm)->field.le_next; \ + (head)->lh_first = (elm); \ + (elm)->field.le_prev = &(head)->lh_first; \ + } while (0) + + /* + * function declarations + */ + static int ip_get_sopt_mreq_arg(int optname, char *optval, int optlen, + struct in_addr *gaddr, struct in_addr *saddr, struct in_device **ifpp); + int ip_addmembership(struct sock *sk, struct in_addr *group, + struct in_device *ifp, int *index); /* XXX make static? */ + int igmp_chg_sourcefilter(struct in_multi *inmp, struct source_list *src_rem, + int fmode_from, struct source_list *src_add, int fmode_to); + struct in_multi * in_addmulti(struct in_addr *ap, struct in_device *ifp); + struct in_multi * in_createmulti(struct in_addr *ap, struct in_device *ifp); + void igmp_joingroup(struct in_multi *inm); + void igmp_leavegroup(struct in_multi *inm); + int ip_sock_add_msf(struct sock_mcastsf *msf, struct in_addr src); + int ip_findmembership(struct sock *sk, struct in_addr *group, + struct in_device *ifp, int *index); + int ip_sock_remove_msf(struct sock_mcastsf *msf, struct in_addr src); + int ip_dropmembership(struct sock *sk, struct in_addr *group, + struct in_device *ifp); + void in_delmulti(struct in_multi *inm); + void igmp_fasttimo(unsigned long data); + void igmp_slowtimo(unsigned long data); + static struct router_info * find_rti(struct in_device *ifp); + static void igmp_free_all_sourcefilters(struct in_multi *inmp); + static void igmp_sendpkt(struct in_multi *inm, int type, unsigned long addr); + static int igmp_tag_state(struct in_multi *inmp, int *fmode, struct in_sfentry + **taggedlist, int userec, unsigned short tagmask); + static int igmp_tag_srclist_change(struct in_sfentry *isf, unsigned short + maskreport, unsigned short masktime, int xmit); + static int igmp_sf_add(struct in_multi *inmp, struct in_addr ia, int fmode); + static void igmp_sf_remove(struct in_multi *inmp, struct in_addr ia, int fmode); + static void igmp_sf_unlink_unreferenced(struct in_multi *inmp, int fmode); + static struct in_sfentry* igmp_sf_new(struct in_multi *inmp); + static void igmp_cancel_all_reports(int cancel_statechg_too); + static void igmp_send_if_report(struct in_device *ifp); + static void igmp_current_state_report(struct in_multi *inmp, int userec); + static void igmp_send_report(struct in_multi *inm, struct in_sfentry *slist, + int count, unsigned short mask, unsigned char type, unsigned long addr); + static int igmp_state_change_report(struct in_multi *inmp, int *done); + static void igmp_clean_recorded_sources(struct in_multi *inmp); + static void igmp_send_slc_report(struct in_multi *inm, unsigned long addr); + static int check_router_alert(struct iphdr *ip); + static void igmp_schedule_curstate_reports(struct in_device *ifp, + struct router_info *rti, struct igmpv3 *igmp, int igmplen, int mrt, + struct sk_buff *skb); + static int igmp_random_delay(int mrt, int ver); + static int igmp_record_sources(struct in_multi *inmp, unsigned int numsrc, + struct in_addr *sources); + static struct in_sfentry * igmp_get_sources(struct igmp_grouprec *group, + struct in_sfentry *slist, unsigned short mask, int n, int index, + int rex, int dec, int *cnt); + static inline void igmp_lock(void); + static inline void igmp_unlock(void); + static inline char * rti_version_string(int version); + static inline char * ip_mc_fmode_string(unsigned short fmode); + static inline void igmp_printk(const char *fmt, ...); + + /* + * Static global data + */ + static int igmp_ft_count; /* fasttimer processing */ + static int igmp_ft_dirty; + static int igmp_ft_stamp; + static struct timer_list igmp_fasttimer; + static struct timer_list igmp_slowtimer; + static struct igmpstat igmpstat; + static struct router_info *Head; + static spinlock_t igmp_spinlock = SPIN_LOCK_UNLOCKED; + static pid_t igmp_spinlock_pid = -1; /* -1 for none, else pid */ + static unsigned igmp_spinlock_level = 0; + + /* + * Sysctl variables + */ + int sysctl_igmp_max_memberships = DEFAULT_MAX_MEMBERSHIPS; + int sysctl_igmp_max_gass_queries = DEFAULT_MAX_GASS_QUERIES; + int sysctl_igmp_max_rec_sources = DEFAULT_MAX_REC_SOURCES; + int sysctl_igmp_debug_output = DEFAULT_DEBUG_OUTPUT; + + /* + * Suppress debugging output unless explicitly configured otherwise. + */ + static inline void igmp_printk(const char *fmt, ...) + { + va_list args; + + if (sysctl_igmp_debug_output) { + va_start(args, fmt); + printk(fmt, args); + va_end(args); + } + } + + /* + * IGMPv3 locking functions. The idea is that we want to allow recursive + * calls from same process (e.g., ip_setmoptions() -> ip_addmembership() -> + * ip_route_output() -> ip_route_output_slow() -> ip_check_mc()). So, we + * store the process id after grabbing the lock and clear it before releasing + * the lock. We count levels of recursion and only release the lock after + * the outermost unlock() call. + */ + static inline void igmp_lock() + { + if (igmp_spinlock_pid != current->pid) { + spin_lock_bh(&igmp_spinlock); + igmp_spinlock_pid = current->pid; + } + igmp_spinlock_level++; + } + + static inline void igmp_unlock() + { + if (igmp_spinlock_pid == current->pid) { + if (--igmp_spinlock_level == 0) { + igmp_spinlock_pid = -1; + spin_unlock_bh(&igmp_spinlock); + } + } else { + igmp_printk(KERN_DEBUG "igmp_unlock: unlocking unheld lock!\n"); + BUG(); + } + } + + /* + * Send an IGMP report. + */ + + #define IGMP_SIZE (sizeof(struct igmphdr)+sizeof(struct iphdr)+4) + + /* Don't just hand NF_HOOK skb->dst->output, in case netfilter hook + changes route */ + static inline int + output_maybe_reroute(struct sk_buff *skb) + { + return skb->dst->output(skb); + } + + + static void igmp_send_report(struct in_multi *inm, struct in_sfentry *slist, + int count, unsigned short mask, unsigned char type, unsigned long addr) + { + struct sk_buff *skb; + struct iphdr *ip; + struct rtable *rt; + struct igmp_report *report; + struct igmp_grouprec *group; + int n, nsrcs, hlen, len, did, limit, rex, dec; + struct net_device *dev; + u32 dst; + + + igmp_printk(KERN_DEBUG "igmp_send_report\n"); + + dst = (addr) ? addr : htonl(INADDR_ALLRTRS_IGMPV3_GROUP); + dev = inm->inm_ifp->dev; + + /* + * Determine some useful values for message size + */ + hlen = sizeof(struct iphdr) + 4 + IGMP_HDRLEN + IGMP_GRPREC_HDRLEN + + IGMP_PREPEND; + limit = inm->inm_ifp->dev->mtu; + + /* + * If it's about exclude, report only as many as fit in one message + */ + if ((limit < IPV4SPACE(count) + hlen) && + (type == IGMP_REPORT_MODE_EX || type == IGMP_REPORT_TO_EX)) { + count = (limit - hlen) / sizeof(struct in_addr); + } + + /* + * Update retransmission state in case of to_xx report + */ + rex = 0; + dec = (type == IGMP_REPORT_TO_EX || type == IGMP_REPORT_TO_IN) ? 1 : 0; + + /* + * Send out the report, and, if necessary, use multiple datagrams + */ + did = 0; + do { + if (ip_route_output(&rt, dst, 0, 0, dev->ifindex)) { + igmp_printk(KERN_DEBUG "igmp_send_report: ip_route_output\n"); + return; + } + if (rt->rt_src == 0) { + ip_rt_put(rt); + igmp_printk(KERN_DEBUG "igmp_send_report: rt->rt_src == 0\n"); + return; + } + + /* + * Allocate mtu-sized skbuff + */ + skb = alloc_skb(dev->mtu + dev->hard_header_len+15, GFP_ATOMIC); + if (skb == NULL) { + ip_rt_put(rt); + igmp_printk(KERN_DEBUG "igmp_send_report: NULL skb\n"); + return; + } + skb->dst = &rt->u.dst; + skb_reserve(skb, (dev->hard_header_len + 15) & ~15); + skb->nh.iph = ip = (struct iphdr *) skb_put(skb, + sizeof(struct iphdr) + 4); + report = (struct igmp_report *) skb_put(skb, IGMP_HDRLEN); + + nsrcs = (limit - hlen) >> 2; + + /* + * Find out how many sources we can report in this message and + * its size. + */ + if (nsrcs > count) { + nsrcs = count; + } + len = IGMP_HDRLEN + IGMP_GRPREC_HDRLEN + IPV4SPACE(nsrcs); + + report->ir_type = (u_char) IGMP_V3_MEMBERSHIP_REPORT; + report->ir_rsv1 = 0; + report->ir_numgrps = htons(1); + report->ir_rsv2 = htons(0); + + /* + * Set the igmp group record header and put the sources in the + * buffer + */ + group = &(report->ir_groups[0]); + slist = igmp_get_sources(group, slist, mask, nsrcs, 0, rex, dec, + &n); + report->ir_groups[0].ig_type = (u_char) type; + report->ir_groups[0].ig_datalen = 0; + report->ir_groups[0].ig_numsrc = htons(n); + report->ir_groups[0].ig_group = inm->inm_addr.s_addr; + + /* + * Tell skbuff how much group record data we've stored. We've + * already accounted for the ip header, the router alert option, + * and the report header. + */ + skb_put(skb, IGMP_GRPREC_HDRLEN + n * sizeof(struct in_addr)); + + /* + * Compute igmp checksum and set the ip header + */ + report->ir_cksum = 0; + report->ir_cksum = ip_compute_csum((void *) report, len); + + ip->version = 4; + ip->ihl = (sizeof(struct iphdr) + 4) >> 2; + ip->tos = 0; + ip->frag_off = __constant_htons(IP_DF); + ip->ttl = 1; + ip->saddr = rt->rt_src; + ip->daddr = dst; + ip->protocol = IPPROTO_IGMP; + ip->tot_len = htons(len + sizeof(struct iphdr) + 4); + ip_select_ident(ip, &rt->u.dst, NULL); + /* + * Construct router alert option + */ + ((u8 *) &ip[1])[0] = IPOPT_RA; + ((u8 *) &ip[1])[1] = 4; + ((u8 *) &ip[1])[2] = 0; + ((u8 *) &ip[1])[3] = 0; + ip_send_check(ip); + + /* send the report */ + NF_HOOK(PF_INET, NF_IP_LOCAL_OUT, skb, NULL, rt->u.dst.dev, + output_maybe_reroute); + igmpstat.igps_snd_reports++; + did += nsrcs; + + } while ((did < count) && (nsrcs != 0) && (slist != NULL)); + } + + /* + * Handle an incoming igmp message. + */ + int igmp_rcv(struct sk_buff *skb) + { + struct igmphdr *ih = skb->h.igmph; + struct igmpv3 *igmp = (struct igmpv3 *) skb->h.igmph; + struct iphdr *iph = skb->nh.iph; + struct in_device *in_dev = in_dev_get(skb->dev); + int len = skb->len; + struct router_info *rti; + int mrt, self; + struct in_multi *inm; + struct in_addr in; + + if (in_dev == NULL) { + kfree_skb(skb); + return 0; + } + + if (skb_is_nonlinear(skb)) { + if (skb_linearize(skb, GFP_ATOMIC) != 0) { + kfree_skb(skb); + return -ENOMEM; + } + ih = skb->h.igmph; + } + + igmpstat.igps_rcv_total++; /* XXX should this be placed earlier? */ + + if (len < sizeof(struct igmphdr)) { + igmpstat.igps_rcv_tooshort++; + in_dev_put(in_dev); + kfree_skb(skb); + return 0; + } + + /* + * igmp messages should fit in one ip fragment (mtu) + */ + if (len > in_dev->dev->mtu) { + igmpstat.igps_rcv_toolong++; + in_dev_put(in_dev); + kfree_skb(skb); + return 0; + } + + /* + * Validate checksum + */ + if (ip_compute_csum((void *)ih, len)) { + igmpstat.igps_rcv_badsum++; + in_dev_put(in_dev); + kfree_skb(skb); + return 0; + } + + mrt = (ih->code) ? ih->code : 1; + rti = find_rti(in_dev); + + switch (ih->type) { + case IGMP_MEMBERSHIP_QUERY: + /* + * The query can be a v1, v2 or v3 query. + */ + + igmpstat.igps_rcv_queries++; + + if (ih->code == 0) { + /* + * Version 1 query + */ + igmp_printk(KERN_DEBUG "igmp_rcv: received v1 query\n"); + mrt = IGMP_MAX_HOST_REPORT_DELAY * IGMP_TIMER_SCALE; + if (iph->daddr != IGMP_ALL_HOSTS || ih->group != 0) { + igmpstat.igps_rcv_badqueries++; + in_dev_put(in_dev); + kfree_skb(skb); + return 0; + } + + /* + * Set router variables + */ + rti->rti_qrv = IGMP_INIT_ROBVAR; + rti->rti_timev1 = rti->rti_qrv * IGMP_INIT_QRYINT; + rti->rti_timev1 = (rti->rti_timev1+IGMP_DEF_QRYMRT) + * PR_SLOWHZ; + + } else if (len == 8) { + /* + * Version 2 query + */ + igmp_printk(KERN_DEBUG "igmp_rcv: received v2 query\n"); + + if (ih->group != 0 && !MULTICAST(ih->group)) { + igmpstat.igps_rcv_badqueries++; + in_dev_put(in_dev); + kfree_skb(skb); + return 0; + } + + /* + * Set router variables + */ + rti->rti_qrv = IGMP_INIT_ROBVAR; + rti->rti_timev2 = rti->rti_qrv * IGMP_INIT_QRYINT; + rti->rti_timev2 += ih->code / IGMP_TIMER_SCALE; + rti->rti_timev2 *= PR_SLOWHZ; + + } else if (len >= 12) { + /* + * Version 3 query + */ + igmp_printk(KERN_DEBUG "igmp_rcv: received v3 query\n"); + + /* + * Router alert option is required only for IGMPv3 + * queries. + */ + if (!check_router_alert(iph)) { + igmpstat.igps_rcv_badqueries++; + in_dev_put(in_dev); + kfree_skb(skb); + return 0; + } + + /* + * Drop when address or number of sources cannot be + * valid. + */ + if ((igmp->igmp_group && !MULTICAST(igmp->igmp_group)) + || ntohs(igmp->igmp_numsrc) > IGMP_MAXSOURCES(len)) { + igmpstat.igps_rcv_badqueries++; + in_dev_put(in_dev); + kfree_skb(skb); + return 0; + } + + /* + * Set router variables + */ + rti->rti_qrv = IGMP_QRV(igmp); + + } else { + /* + * Invalid length -- discard. + */ + igmpstat.igps_rcv_badqueries++; + in_dev_put(in_dev); + kfree_skb(skb); + return 0; + } + + /* + * Check whether host compatibility mode should be changed. If + * so, cancel pending responses and retransmission timers. + */ + if (rti->rti_timev1 <= 0) { + if (rti->rti_timev2 <= 0 && + rti->rti_type != IGMP_V3_ROUTER) { + rti->rti_type = IGMP_V3_ROUTER; + igmp_cancel_all_reports(1); + } else if (rti->rti_timev2 > 0 && + rti->rti_type != IGMP_V2_ROUTER) { + rti->rti_type = IGMP_V2_ROUTER; + igmp_cancel_all_reports(1); + } + } else if (rti->rti_timev1 > 0) { + if (rti->rti_type != IGMP_V1_ROUTER) { + rti->rti_type = IGMP_V1_ROUTER; + igmp_cancel_all_reports(1); + } + } + + /* + * Schedule the response(s) to this query + */ + igmp_schedule_curstate_reports(in_dev,rti,igmp,len,mrt,skb); + break; + + case IGMP_V1_MEMBERSHIP_REPORT: + case IGMP_V2_MEMBERSHIP_REPORT: + + if (ih->type == IGMP_V1_MEMBERSHIP_REPORT) + igmp_printk(KERN_DEBUG "igmp_rcv: received v1 report\n"); + else + igmp_printk(KERN_DEBUG "igmp_rcv: received v2 report\n"); + + /* + * 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 + * (inm->inm_timer != 0). In Idle Member state, our timer is + * not running (inm->inm_timer == 0). + * + * The flag is inm->inm_state. It is set to IGMP_OTHERMEMBER + * if we have last heard a report from another member, or to + * IGMP_IREPORTEDLAST if we were the last to send a report. + */ + + /* + * For fast leave to work, we have to know that we are the last + * person to send a report for this group. Reports can be + * looped back if we are a multicast router, so we discard + * reports for which we are the source. + */ + self = 0; + for_ifa(in_dev) { + igmp_printk(KERN_DEBUG "igmp_rcv: ifa->ifa_address is %s\n", + NIPQUAD(ifa->ifa_address)); + igmp_printk(KERN_DEBUG "igmp_rcv: iph->saddr is %s\n", + NIPQUAD(iph->saddr)); + if (ifa->ifa_address == iph->saddr) { + igmp_printk(KERN_DEBUG "igmp_rcv: ignoring own report\n"); + self = 1; + break; + } + } endfor_ifa(in_dev); + if (self) { + break; + } + + igmpstat.igps_rcv_reports++; + + if (in_dev->dev->flags & IFF_LOOPBACK) { + igmp_printk(KERN_DEBUG "igmp_rcv: discarding loopback report\n"); + break; + } + + if (!MULTICAST(ih->group)) { + igmpstat.igps_rcv_badreports++; + break; + } + + /* + * The BSD code rewrites the IP source address of those reports + * with an unspecified (i.e., zero) subnet number so that + * process level daemons can determine which subnet such a + * report arrived from. That wouldn't help here, since + * igmp_rcv() is not responsible for passing igmp msgs to + * process level. + * + * XXX Ought we to do this elsewhere, though? + */ + + /* + * If we belong to the group being reported, stop our timer for + * that group. + */ + in.s_addr = ih->group; + IN_LOOKUP_MULTI(in, in_dev, inm); + if (inm != NULL) { + inm->inm_timer = 0; + igmpstat.igps_rcv_ourreports++; + inm->inm_state |= IGMP_OTHERMEMBER; + } + + break; + + case IGMP_PIM: + igmp_printk(KERN_DEBUG "igmp_rcv: received pim msg\n"); + #ifdef CONFIG_IP_PIMSM_V1 + in_dev_put(in_dev); + return pim_rcv_v1(skb); + #endif + + case IGMP_DVMRP: + case IGMP_TRACE: + case IGMP_V2_LEAVE_GROUP: + case IGMP_MTRACE_RESP: + case IGMP_MTRACE: + case IGMP_V3_MEMBERSHIP_REPORT: + /* + * We do nothing on receipt of these other known message types + */ + igmp_printk(KERN_DEBUG "igmp_rcv: received other msg\n"); + break; + + default: + NETDEBUG(printk(KERN_DEBUG "Unknown IGMP type=%d\n", ih->type)); + } + + /* + * Note: Unlike BSD code, Linux's igmp_rcv() is not responsible for + * passing these messages up to processes listening on raw sockets. + * We can go ahead and release the packet's resources. + */ + + in_dev_put(in_dev); + kfree_skb(skb); + return 0; + } + + /* + * Return whether the IP router alert option has been set, which is required + * for IGMPv3 queries. + */ + static int check_router_alert(struct iphdr *ip) + { + int opt, optlen, cnt; + unsigned char *cp; + + igmp_printk(KERN_DEBUG "check_router_alert\n"); + + cp = (unsigned char *) (ip + 1); + cnt = (ip->ihl << 2) - sizeof(*ip); + for (; cnt > 0; cnt -= optlen, cp += optlen) { + opt = cp[IPOPT_OPTVAL]; + if (opt == IPOPT_EOL) { + break; + } + if (opt == IPOPT_NOP) { + optlen = 1; + } else { + optlen = cp[IPOPT_OLEN]; + if (optlen <= 0 || optlen > cnt) { + break; + } + if (opt == IPOPT_RA) { + return 1; + } + } + } + return 0; + } + + /* + * Determine a random delay given some 'maximum response time'. If version is + * IGMPv3, the IGMPv3 formula will be used to compute the delay. + */ + static int igmp_random_delay(int mrt, int ver) + { + if (ver == IGMP_V3_ROUTER && (mrt & 0x80)) { + /* + * XXX Might be good to give the formula embodied in the + * following line. + */ + mrt = (0x10 | (mrt & 0x0f)) << (((mrt & 0x70) >> 4) + 3); + } + return IGMP_RANDOM_DELAY(mrt); + } + + /* + * Record sources + */ + static int igmp_record_sources(struct in_multi *inmp, unsigned int numsrc, + struct in_addr *sources) + { + struct in_sfentry *prev = NULL, *cur, *isf; + int i, j, count = inmp->inm_num_rec_sources; + struct in_addr *srcs = sources; + + /* + * Don't record anything if the maximum is reached. + */ + if (sysctl_igmp_max_rec_sources > 0 && + sysctl_igmp_max_rec_sources <= count) { + return 0; + } + + cur = NULL; + for (i = j = 0; i < numsrc; i++) { + if (IGMP_VALID_SFADDR(srcs[j])) { + /* + * Insert the address in the (sorted) list of recorded + * sources. Test if the sources are sorted and, if not, + * start looking for a new position from start. + */ + if (cur == NULL || + cur->isf_addr.s_addr > srcs[j].s_addr) { + cur = inmp->inm_sflrec; + prev = NULL; + } + while (cur != NULL) { + if (cur->isf_addr.s_addr >= srcs[j].s_addr) { + break; + } + prev = cur; + cur = cur->isf_next; + } + if (cur == NULL || cur->isf_addr.s_addr != + srcs[j].s_addr) { + if ((isf = igmp_sf_new(inmp)) != NULL) { + isf->isf_addr.s_addr = srcs[j].s_addr; + isf->isf_next = cur; + if (prev != NULL) { + prev->isf_next = isf; + } else { + inmp->inm_sflrec = isf; + } + count++; + cur = isf; + if (sysctl_igmp_max_rec_sources > 0 && + sysctl_igmp_max_rec_sources <= count) { + break; + } + } + } + } + j++; + } + inmp->inm_num_rec_sources += count; + return (inmp->inm_num_rec_sources); + } + + /* + * Schedule reports in response to a single query. The value of 'mrt' is the + * igmp 'maximum response time' that should be used for the response. + */ + static void igmp_schedule_curstate_reports(struct in_device *ifp, + struct router_info *rti, struct igmpv3 *igmp, int igmplen, int mrt, + struct sk_buff *skb) + { + struct in_multi *inm; + struct in_multistep step; + int *ptimer, num_sources, oldtimer, delay, ignore; + + igmp_printk(KERN_DEBUG "igmp_schedule_curstate_reports\n"); + + /* + * Select a random delay + */ + delay = igmp_random_delay(mrt, rti->rti_type); + delay = (delay * PR_FASTHZ) / IGMP_TIMER_SCALE; + + /* + * Ignore query if it's a v3 router and there is a pending response to + * a general query scheduled to be sent sooner than our selected delay. + */ + if (rti->rti_type == IGMP_V3_ROUTER && rti->rti_timer && + rti->rti_timer < delay) { + return; + } + + /* + * Schedule reports by setting a timer in all of our membership records + * to which the query applies, and if the interface is the same. + */ + IN_FIRST_MULTI(step, inm); + while (inm != NULL) { + /* + * Don't schedule responses if the membership's interface + * doesn't match the one that received the query, or if the + * membership is for the all hosts group or applies to a + * loopback interface. + */ + if (inm->inm_ifp == ifp && + inm->inm_addr.s_addr != IGMP_ALL_HOSTS && + (inm->inm_ifp->dev->flags & IFF_LOOPBACK) == 0) { + if (igmp->igmp_group == 0) { + /* + * General query: schedule report if there is + * no pending timer, or pending timer > delay. + * For v3 routers, the action is different (an + * interface timer). + */ + if (rti->rti_type == IGMP_V3_ROUTER) { + if (rti->rti_timer <= 0 || + rti->rti_timer > delay) { + ASSIGN_FAST_TIMER(rti->rti_timer, + delay); + } + break; + } else if (inm->inm_timer == 0 || + inm->inm_timer > delay) { + ASSIGN_FAST_TIMER(inm->inm_timer,delay); + delay = igmp_random_delay(mrt, + rti->rti_type); + delay = delay * PR_FASTHZ / + IGMP_TIMER_SCALE; + } + + } else if (igmp->igmp_group == inm->inm_addr.s_addr) { + /* + * Group-specific, or group-and-source specific + * query + */ + if (igmplen >= 12) { + /* + * Group-specific or group-and-source + * v3 query + */ + num_sources = ntohs(igmp->igmp_numsrc); + ptimer = (num_sources < 1) ? + &inm->inm_timer : + &inm->inm_ti_curstate; + if (inm->inm_timer == 0 && + inm->inm_ti_curstate == 0) { + /* + * No pending response + * Vince: Huh? Didn't we just check + * this? + */ + inm->inm_timer = 0; + inm->inm_ti_curstate = 0; + if (num_sources > 0) { + inm->inm_num_gass_queries = 1; + igmp_record_sources(inm, + num_sources, + (struct in_addr *) + igmp->igmp_sources); + } + ASSIGN_FAST_TIMER(*ptimer, delay); + } else { + /* + * There is a pending response + */ + ignore = 0; + if (num_sources == 0 || + inm->inm_timer != 0) { + /* + * group-and-source-specific + * reports are overruled by + * group-specific reports + */ + ptimer = &inm->inm_timer; + igmp_clean_recorded_sources(inm); + } else { + /* + * Record sources unless the + * number of gass queries + * exceeds a certain threshold + * (accessible through sysctl()) + * or if this protection is + * disabled (<0) + */ + if (sysctl_igmp_max_gass_queries<=0 || + sysctl_igmp_max_gass_queries > + ++inm->inm_num_gass_queries) { + igmp_record_sources(inm, + num_sources, + (struct in_addr *) + igmp->igmp_sources); + } else { + ignore = 1; + } + } + /* + * Select earliest of pending timer + * and new delay + */ + if (!ignore) { + oldtimer = *ptimer; + inm->inm_timer = 0; + inm->inm_ti_curstate = 0; + if (delay < oldtimer) { + ASSIGN_FAST_TIMER(*ptimer, + delay); + } else { + ASSIGN_FAST_TIMER(*ptimer, + oldtimer); + } + } + } + } else if (igmplen == 8) { + /* + * Group-specific v2 query: schedule a + * new report if there is no pending + * report for this group, or if there + * is, if its scheduled time is later + * than the new time. + */ + if (inm->inm_timer == 0 || + inm->inm_timer > delay) { + inm->inm_timer = 0; + igmp_clean_recorded_sources(inm); + ASSIGN_FAST_TIMER(inm->inm_timer, + delay); + } + } + + /* + * No other membership record can match the + * interface and group-address, so stop. + */ + break; + } + + } + IN_NEXT_MULTI(step, inm); + + } /* while (inm) */ + } + + /* + * Initialize igmp. + */ + void igmp_init(void) + { + /* + * Fast timer processing + */ + igmp_ft_count = igmp_ft_dirty = igmp_ft_stamp = 0; + + Head = (struct router_info *) 0; + + /* + * Initialize timers, emulating BSD environment + */ + init_timer(&igmp_fasttimer); + igmp_fasttimer.data = 0; + igmp_fasttimer.function = igmp_fasttimo; + mod_timer(&igmp_fasttimer, jiffies + 1); + init_timer(&igmp_slowtimer); + igmp_slowtimer.data = 0; + igmp_slowtimer.function = igmp_slowtimo; + mod_timer(&igmp_slowtimer, jiffies + 1); + } + + /* + * Fast timer: Check and send scheduled reports. + */ + void igmp_fasttimo(unsigned long data) + { + struct in_multi *inm, *inmdone = NULL; + struct in_multistep step; + struct router_info *rti; + int done, delta, min, dirty; + + /* + * Quick check to see if any work needs to be done, in order to + * minimize the overhead of fisttimo processing. + */ + dirty = igmp_ft_dirty; + igmp_ft_dirty = 0; + if (((igmp_ft_count == 0) || --igmp_ft_count) && dirty == 0) { + mod_timer(&igmp_fasttimer, jiffies + IGMP_FASTTIMER_INTERVAL); + return; + } + + igmp_lock(); + delta = igmp_ft_stamp - igmp_ft_count; + min = INT_MAX; + + /* + * Check all membership records to see if something needs to be + * reported. + */ + IN_FIRST_MULTI(step, inm); + while (inm != NULL) { + done = 0; + /* + * No reports for all hosts + */ + if (inm->inm_addr.s_addr != IGMP_ALL_HOSTS) { + /* + * Handle current-state (solicited) reports + */ + if (inm->inm_timer != 0) { + if ((inm->inm_timer -= delta) <= 0) { + inm->inm_timer = 0; + if (inm->inm_rti->rti_type == + IGMP_V3_ROUTER) { + igmp_current_state_report(inm, 0); + } else { + igmp_sendpkt(inm,inm->inm_rti->rti_type, + 0); + } + inm->inm_state |= IGMP_IREPORTEDLAST; + } else if (inm->inm_timer < min) { + min = inm->inm_timer; + } + } + + /* + * Handle group-and-source-specific (solicited) reports + */ + if (inm->inm_ti_curstate != 0) { + if ((inm->inm_ti_curstate -= delta) <= 0) { + inm->inm_ti_curstate = 0; + igmp_current_state_report(inm, 1); + } else if (inm->inm_ti_curstate < min) { + min = inm->inm_ti_curstate; + } + } + + /* + * Handle state-change reports + */ + if (inm->inm_ti_statechg != 0) { + if ((inm->inm_ti_statechg -= delta) <= 0) { + inm->inm_ti_statechg = 0; + if (igmp_state_change_report(inm, &done)) { + ASSIGN_FAST_TIMER(inm->inm_ti_statechg, + IGMP_RANDOM_DELAY(IGMP_UNSOL_INT * + PR_FASTHZ)); + if (inm->inm_ti_statechg < min) { + min = inm->inm_ti_statechg; + } + } else if (done) { + inmdone = inm; + } + } else if (inm->inm_ti_statechg < min) { + min = inm->inm_ti_statechg; + } + } + } + + /* + * Next membership record and delete current if + * finished. + */ + IN_NEXT_MULTI(step, inm); + if (done) { + LIST_REMOVE(inmdone, inm_link); + igmp_free_all_sourcefilters(inmdone); + kfree(inmdone); + } + } + + /* + * Check interface timers + */ + for (rti = Head; rti != NULL; rti = rti->rti_next) { + if (rti->rti_timer != 0) { + if ((rti->rti_timer -= delta) <= 0) { + rti->rti_timer = 0; + igmp_send_if_report(rti->rti_ifp); + } else if (rti->rti_timer < min) { + min = rti->rti_timer; + } + } + } + + igmp_ft_count = igmp_ft_stamp = (min != INT_MAX) ? min : 0; + + mod_timer(&igmp_fasttimer, jiffies + IGMP_FASTTIMER_INTERVAL); + igmp_unlock(); + } + + /* + * Slow timer: Check router compatibility modes + */ + void igmp_slowtimo(unsigned long data) + { + struct router_info *rti = Head; + int new_mode; + + igmp_lock(); + while (rti) { + new_mode = 0; + + /* + * Decrease router comp. mode timers and determine new mode if + * a timer expired + */ + if (rti->rti_timev2 != 0 && --rti->rti_timev2 == 1) { + new_mode = IGMP_V3_ROUTER; + } + if (rti->rti_timev1 != 0 && --rti->rti_timev1 == 1) { + if (rti->rti_timev2 > 1) { + new_mode = IGMP_V2_ROUTER; + } else { + new_mode = IGMP_V3_ROUTER; + } + } + + /* + * Switch + */ + if (new_mode != 0 && rti->rti_type != new_mode) { + rti->rti_type = new_mode; + igmp_cancel_all_reports(1); + } + + rti = rti->rti_next; + } + + mod_timer(&igmp_slowtimer, jiffies + IGMP_SLOWTIMER_INTERVAL); + igmp_unlock(); + } + + /* + * Should be called whenever the source filter on a socket is changed. This + * function will maintain global membership state and schedule the necessary + * igmp state change reports. The global membership state of the system is + * implemented using reference counting. Sources will only be removed if + * their reference count becomes zero, and a global filter mode change will + * only happen if this was the first or last socket in exclude mode. The + * filter mode of the socket before the change should be passed in 'fmode_from' + * and the new filter mode in 'fmode_to'. + * + * The sources that are removed from the socket's source list in 'fmode_from' + * should be passed in 'src_rem'. Those sources that are added to its list in + * 'fmode_to' are passed in 'src_add'. + */ + int igmp_chg_sourcefilter(struct in_multi *inmp, struct source_list *src_rem, + int fmode_from, struct source_list *src_add, int fmode_to) + { + struct in_sfentry *isf, *list1, *list2; + struct source_list *psrc; + int fmode1, fmode2, cnt1, cnt2, empty, initial; + + igmp_printk(KERN_DEBUG "igmp_chg_sourcefilter\n"); + + /* + * Verify input. In the initial state, a remove is invalid, as is + * inc{0}. + */ + initial = (inmp->inm_state & IGMP_NONEXISTENT); + if (initial && (src_rem != NULL || (src_add == NULL && + fmode_to == MCAST_INCLUDE))) { + return 0; + } + + /* + * Determine current (T1) state unless it's the initial state + */ + if (!initial) { + cnt1 = igmp_tag_state(inmp, &fmode1, &list1, 0, + IGMP_MASK_STATE_T1); + } else { + fmode1 = MCAST_INCLUDE; + cnt1 = 0; + } + + /* + * Remove and/or add sources + */ + for (psrc = src_rem; psrc != NULL; psrc = psrc->src_next) { + igmp_sf_remove(inmp, psrc->src_addr, fmode_from); + } + for (psrc = src_add; psrc != NULL; psrc = psrc->src_next) { + igmp_sf_add(inmp, psrc->src_addr, fmode_to); + } + + /* + * Update reference count of sockets in exclude mode + */ + if (fmode_to != fmode_from) { + inmp->inm_num_socks_excl += ((fmode_to == MCAST_EXCLUDE) + ? 1 : -1); + } + + /* + * Determine new mode + */ + SF_ISEMPTY(inmp->inm_sflexcl, isf, empty); + if (empty) { + SF_ISEMPTY(inmp->inm_sflincl, isf, empty); + if (!empty) { + inmp->inm_fmode = MCAST_INCLUDE; + } else if (initial) { + inmp->inm_fmode = MCAST_EXCLUDE; + } + } else { + inmp->inm_fmode = MCAST_EXCLUDE; + } + + /* + * Whatever happened, now the state is valid. + */ + inmp->inm_state &= ~IGMP_NONEXISTENT; + + /* + * Determine new (T2) state + */ + cnt2 = igmp_tag_state(inmp, &fmode2, &list2, 0, IGMP_MASK_STATE_T2); + + /* + * Don't bother sending reports if there isn't an IGMPv3 router, or if + * it would be in reference to the all_hosts group or the loopback + * interface + */ + if ((inmp->inm_rti->rti_type < IGMP_V3_ROUTER) || + (inmp->inm_addr.s_addr == IGMP_ALL_HOSTS) || + (inmp->inm_ifp->dev->flags & IFF_LOOPBACK)) { + igmp_sf_unlink_unreferenced(inmp, fmode_from); + return 1; + } + + /* + * Determine state-change report + */ + if (fmode1 != fmode2) { + /* + * Generate/tag the filter-mode-changes + */ + if (fmode2 == MCAST_EXCLUDE) { + SF_SETCLEARTAG(list2, isf, IGMP_MASK_STATE_T2, + IGMP_MASK_TO_EX, IGMP_MASK_TO_IN); + } else { + SF_SETCLEARTAG(list2, isf, IGMP_MASK_STATE_T2, + IGMP_MASK_TO_IN, IGMP_MASK_TO_EX); + } + + /* + * Remove sources that were deleted and don't need to be + * reported + */ + if (src_rem != NULL) { + igmp_sf_unlink_unreferenced(inmp, fmode_from); + } + + /* + * Schedule filter-mode-change reports + */ + inmp->inm_rpt_toxx = inmp->inm_rti->rti_qrv; + inmp->inm_rpt_statechg = inmp->inm_rti->rti_qrv; + ASSIGN_FAST_TIMER(inmp->inm_ti_statechg, 1); + } else { + /* + * Source-list changes only affect the list as reported in + * current-state reports, and not the filter-mode itself. + * For that reason, source-list changes can be determined by + * reporting the difference in this list only. + * + * Added means T1=0 and T2=1, while deleted is T1=1 and T2=0 + * BLOCK means either deleted from INCL or added to EXCL + * ALLOW means either deleted from EXCL or added to INCL + */ + cnt1 = 0; + if (fmode2 == MCAST_EXCLUDE) { + cnt1 = igmp_tag_srclist_change(list2, + IGMP_MASK_ALLOW_NEW, IGMP_MASK_STATE_T1, + inmp->inm_rti->rti_qrv); + cnt1 += igmp_tag_srclist_change(list2, + IGMP_MASK_BLOCK_OLD, IGMP_MASK_STATE_T2, + inmp->inm_rti->rti_qrv); + } else { + cnt1 = igmp_tag_srclist_change(list2, + IGMP_MASK_ALLOW_NEW, IGMP_MASK_STATE_T2, + inmp->inm_rti->rti_qrv); + cnt1 += igmp_tag_srclist_change(list2, + IGMP_MASK_BLOCK_OLD, IGMP_MASK_STATE_T1, + inmp->inm_rti->rti_qrv); + } + + /* + * If there are sources to report, activate the state-change + * timer + */ + if (cnt1 != 0) { + inmp->inm_rpt_statechg = inmp->inm_rti->rti_qrv; + ASSIGN_FAST_TIMER(inmp->inm_ti_statechg, 1); + } + } + + return 1; + } + + /* + * Join a group by sending a membership report + */ + void igmp_joingroup(struct in_multi *inm) + { + igmp_printk(KERN_DEBUG "igmp_joingroup\n"); + + /* + * With IGMPv3, all membership records need router info. + */ + inm->inm_rti = find_rti(inm->inm_ifp); + if (inm->inm_rti == NULL) { + /* + * See comments in find_rti(). If the malloc fails, we + * log an error and bail. Anything better to do? + */ + igmp_printk(KERN_DEBUG "igmp_joingroup: couldn't obtain rti\n"); + return; + } + + if ((inm->inm_addr.s_addr == IGMP_ALL_HOSTS) || + (inm->inm_ifp->dev->flags & IFF_LOOPBACK)) { + inm->inm_timer = 0; + inm->inm_state |= IGMP_OTHERMEMBER; + } else { + /* + * In IGMPv3 mode, the change will be through a state-change + */ + if (inm->inm_rti->rti_type != IGMP_V3_ROUTER) { + inm->inm_state |= IGMP_IREPORTEDLAST; + inm->inm_rpt_statechg = inm->inm_rti->rti_qrv; + ASSIGN_FAST_TIMER(inm->inm_timer, 1); + } + } + } + + /* + * Leave a group. Send the leave message and clean up the membership record. + */ + void igmp_leavegroup(struct in_multi *inmp) + { + igmp_printk(KERN_DEBUG "igmp_leavegroup\n"); + + /* + * Send the 'right' leave message if this membership/interface requires + * reports. + */ + if (inmp->inm_addr.s_addr != IGMP_ALL_HOSTS && + !(inmp->inm_ifp->dev->flags & IFF_LOOPBACK) && + inmp->inm_rti && inmp->inm_rti->rti_type != IGMP_V1_ROUTER) { + if (inmp->inm_rti->rti_type == IGMP_V3_ROUTER) { + /* + * Schedule leave reports. If current mode is include, + * the scheduled change will be ok (block old), but if + * it's exclude, a to_in{} report must be enforced. + * After final leave has been sent, the membership + * record will be deleted. + */ + inmp->inm_timer = inmp->inm_ti_curstate = + inmp->inm_ti_statechg = 0; + if (inmp->inm_fmode == MCAST_EXCLUDE) { + inmp->inm_sflincl = NULL; + inmp->inm_sflexcl = NULL; + inmp->inm_sflrec = NULL; + inmp->inm_fmode = MCAST_INCLUDE; + inmp->inm_rpt_toxx = inmp->inm_rti->rti_qrv; + } + inmp->inm_rpt_statechg = inmp->inm_rti->rti_qrv; + ASSIGN_FAST_TIMER(inmp->inm_ti_statechg, 1); + + } else if (inmp->inm_state & IGMP_IREPORTEDLAST) { + /* + * With a v2 router, send out a v2 leave report + */ + igmp_sendpkt(inmp,IGMP_V2_LEAVE_GROUP,IGMP_ALL_ROUTER); + } + } + /* + * Cleanup membership record unless the router is v3, since + * then the record will be deleted after the final state-change + * report. + */ + if (inmp->inm_rti->rti_type != IGMP_V3_ROUTER) { + igmp_free_all_sourcefilters(inmp); + LIST_REMOVE(inmp, inm_link); + kfree(inmp); + } + } + + /* + * Find or build a router information record corresponding to an interface + * address. + */ + static struct router_info * find_rti(struct in_device *ifp) + { + struct router_info *rti; + + for (rti = Head; rti; rti = rti->rti_next) + if (rti->rti_ifp == ifp) + return rti; + + /* + * No information yet, so create a new router information record and + * initialize + */ + rti = (struct router_info *) kmalloc(sizeof(*rti), GFP_ATOMIC); + if (rti == NULL) { + /* + * BSD code doesn't seem to allow for this malloc to fail. It + * uses the MALLOC() macro and then immediately dereferences + * rti. Is the memory somehow guaranteed to be available? + */ + igmp_printk(KERN_DEBUG "igmp.c: find_rti: kmalloc failed for rti\n"); + return NULL; + } + + rti->rti_ifp = ifp; + in_dev_hold(ifp); + rti->rti_timer = rti->rti_timev1 = rti->rti_timev2 = 0; + rti->rti_type = IGMP_V3_ROUTER; + rti->rti_qrv = IGMP_INIT_ROBVAR; + rti->rti_next = Head; + Head = rti; + return rti; + } + + /* + * Free all the sourcelists and empty the pool of some membership + */ + static void + igmp_free_all_sourcefilters(struct in_multi *inmp) + { + struct in_sfentry *isf, *isf2; + + inmp->inm_sflexcl = NULL; + inmp->inm_sflincl = NULL; + inmp->inm_sflrec = NULL; + + isf = inmp->inm_sflpool; + inmp->inm_sflpool = NULL; + while (isf != NULL) { + isf2 = isf; + isf = isf->isf_pnext; + kfree(isf2); + } + } + + /* + * Send out an igmp (v1, v2) report for membership 'inm' + */ + static void + igmp_sendpkt(struct in_multi *inm, int type, unsigned long addr) + { + struct net_device *dev; + struct sk_buff *skb; + struct iphdr *ip; + struct igmphdr *igmp; + struct rtable *rt; + u32 dst; + + igmp_printk(KERN_DEBUG "igmp_sendpkt\n"); + + dst = (addr != 0) ? addr : inm->inm_addr.s_addr; + dev = inm->inm_ifp->dev; + + if (ip_route_output(&rt, dst, 0, 0, dev->ifindex)) { + igmp_printk(KERN_DEBUG "igmp_sendpkt: ip_route_output\n"); + return; + } + if (rt->rt_src == 0) { + ip_rt_put(rt); + igmp_printk(KERN_DEBUG "igmp_sendpkt: rt->rt_src == 0\n"); + return; + } + + skb = alloc_skb(sizeof(struct iphdr) + 4 + IGMP_MINLEN + + dev->hard_header_len + 15, GFP_ATOMIC); + if (skb == NULL) { + ip_rt_put(rt); + igmp_printk(KERN_DEBUG "igmp_sendpkt: NULL skb\n"); + return; + } + skb->dst = &rt->u.dst; + skb_reserve(skb, (dev->hard_header_len + 15) & ~15); + + skb->nh.iph = ip = (struct iphdr *) skb_put(skb, + sizeof(struct iphdr) + 4); + + igmp = (struct igmphdr *) skb_put(skb, IGMP_MINLEN); + igmp->type = type; + igmp->code = 0; + igmp->group = inm->inm_addr.s_addr; + igmp->csum = 0; + igmp->csum = ip_compute_csum((void *) igmp, IGMP_MINLEN); + + ip->version = 4; + ip->ihl = (sizeof(struct iphdr) + 4) >> 2; + ip->tos = 0; + ip->frag_off = __constant_htons(IP_DF); + ip->ttl = 1; + ip->saddr = rt->rt_src; + ip->daddr = dst; + ip->protocol = IPPROTO_IGMP; + ip->tot_len = htons(sizeof(struct iphdr) + 4 + IGMP_MINLEN); + ip_select_ident(ip, &rt->u.dst, NULL); + /* + * Construct router alert option + */ + ((u8 *) &ip[1])[0] = IPOPT_RA; + ((u8 *) &ip[1])[1] = 4; + ((u8 *) &ip[1])[2] = 0; + ((u8 *) &ip[1])[3] = 0; + ip_send_check(ip); + + /* + * Send the report + */ + NF_HOOK(PF_INET, NF_IP_LOCAL_OUT, skb, NULL, rt->u.dst.dev, + output_maybe_reroute); + + igmpstat.igps_snd_reports++; + } + + /* + * Send out a report for a specific interface, where all memberships on this + * interface should be reported. + */ + static void igmp_send_if_report(struct in_device *ifp) + { + struct in_multi *inm; + struct in_multistep step; + struct igmp_report *report; + struct igmp_grouprec *group; + struct iphdr *ip; + int n, left, done, bytesavail, xmit, tothlen, size; + int ngroups, fmode, igmplen, grouplen; + struct in_sfentry *slist; + struct rtable *rt; + struct net_device *dev; + struct sk_buff *skb; + u32 dst; + + igmp_printk(KERN_DEBUG "igmp_send_if_report\n"); + + /* + * Find the first membership that needs to be reported + */ + IN_FIRST_MULTI(step, inm); + while (inm && (IS_IGMP_ALL_HOSTS(inm->inm_addr) || + (ifp!=inm->inm_ifp))) { + IN_NEXT_MULTI(step, inm); + } + if (inm == NULL) { + return; + } + left = igmp_tag_state(inm, &fmode, &slist, 0, IGMP_MASK_IF_STATE); + dst = htonl(INADDR_ALLRTRS_IGMPV3_GROUP); + dev = ifp->dev; + + /* + * Loop initialization + */ + size = dev->mtu; + tothlen = sizeof(*ip) + 4 + IGMP_HDRLEN + IGMP_PREPEND; + done = bytesavail = ngroups = igmplen = 0; + group = NULL; + report = NULL; + ip = NULL; + skb = NULL; + + /* + * Report all memberships on this interface + */ + while (!done) { + + xmit = 0; + + /* + * If there is no more space to report available, get and + * prepare a new buffer + */ + if (bytesavail < (IGMP_GRPREC_HDRLEN + IPV4SPACE(left))) { + + /* + * Allocate skbuff + */ + skb = alloc_skb(dev->mtu + dev->hard_header_len + 15, + GFP_ATOMIC); + if (skb == NULL) { + igmp_printk(KERN_DEBUG "igmp_send_if_report: " + "NULL skb\n"); + return; + } + + bytesavail = size - tothlen; + igmplen = IGMP_HDRLEN; + + skb_reserve(skb, (dev->hard_header_len + 15) & ~15); + skb->nh.iph = ip = (struct iphdr *) skb_put(skb, + sizeof(struct iphdr) + 4); + report = (struct igmp_report *) skb_put(skb, + IGMP_HDRLEN); + group = &(report->ir_groups[0]); + ngroups = 0; + } + + /* + * Construct the group record + */ + n = (bytesavail - IGMP_GRPREC_HDRLEN) / sizeof(struct in_addr); + if (n > left) { + n = left; + } + slist = igmp_get_sources(group, slist, IGMP_MASK_IF_STATE, n, 0, + 0, 0, &n); + left -= n; + ngroups++; + if (fmode == MCAST_INCLUDE) { + group->ig_type = IGMP_REPORT_MODE_IN; + } else { + group->ig_type = IGMP_REPORT_MODE_EX; + } + group->ig_datalen = 0; + group->ig_numsrc = htons(n); + group->ig_group = inm->inm_addr.