3288 lines · 78.7 KB · C
RawDownload
// SPDX-License-Identifier: GPL-2.0-or-later/* *	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. * *	Authors: *		Alan Cox <alan@lxorguk.ukuu.org.uk> * *	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. *		David L Stevens:	IGMPv3 support, with help from *					Vinay Kulkarni */ #include <linux/module.h>#include <linux/slab.h>#include <linux/uaccess.h>#include <linux/types.h>#include <linux/kernel.h>#include <linux/jiffies.h>#include <linux/string.h>#include <linux/socket.h>#include <linux/sockios.h>#include <linux/in.h>#include <linux/inet.h>#include <linux/netdevice.h>#include <linux/skbuff.h>#include <linux/inetdevice.h>#include <linux/igmp.h>#include "igmp_internal.h"#include <linux/if_arp.h>#include <linux/rtnetlink.h>#include <linux/times.h>#include <linux/pkt_sched.h>#include <linux/byteorder/generic.h> #include <net/net_namespace.h>#include <net/netlink.h>#include <net/addrconf.h>#include <net/arp.h>#include <net/ip.h>#include <net/protocol.h>#include <net/route.h>#include <net/sock.h>#include <net/checksum.h>#include <net/inet_common.h>#include <linux/netfilter_ipv4.h>#ifdef CONFIG_IP_MROUTE#include <linux/mroute.h>#endif#ifdef CONFIG_PROC_FS#include <linux/proc_fs.h>#include <linux/seq_file.h>#endif #ifdef CONFIG_IP_MULTICAST/* Parameter names and values are taken from igmp-v2-06 draft */ #define IGMP_QUERY_INTERVAL			(125*HZ)#define IGMP_QUERY_RESPONSE_INTERVAL		(10*HZ) #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. */ static bool IGMP_V1_SEEN(const struct in_device *in_dev){	unsigned long seen; 	if (IPV4_DEVCONF_ALL_RO(dev_net(in_dev->dev), FORCE_IGMP_VERSION) == 1)		return true;	if (IN_DEV_CONF_GET((in_dev), FORCE_IGMP_VERSION) == 1)		return true;	seen = READ_ONCE(in_dev->mr_v1_seen);	return seen && time_before(jiffies, seen);} static bool IGMP_V2_SEEN(const struct in_device *in_dev){	unsigned long seen; 	if (IPV4_DEVCONF_ALL_RO(dev_net(in_dev->dev), FORCE_IGMP_VERSION) == 2)		return true;	if (IN_DEV_CONF_GET((in_dev), FORCE_IGMP_VERSION) == 2)		return true;	seen = READ_ONCE(in_dev->mr_v2_seen);	return seen && time_before(jiffies, seen);} static int unsolicited_report_interval(struct in_device *in_dev){	int interval_ms, interval_jiffies; 	if (IGMP_V1_SEEN(in_dev) || IGMP_V2_SEEN(in_dev))		interval_ms = IN_DEV_CONF_GET(			in_dev,			IGMPV2_UNSOLICITED_REPORT_INTERVAL);	else /* v3 */		interval_ms = IN_DEV_CONF_GET(			in_dev,			IGMPV3_UNSOLICITED_REPORT_INTERVAL); 	interval_jiffies = msecs_to_jiffies(interval_ms); 	/* _timer functions can't handle a delay of 0 jiffies so ensure	 *  we always return a positive value.	 */	if (interval_jiffies <= 0)		interval_jiffies = 1;	return interval_jiffies;} static void igmpv3_add_delrec(struct in_device *in_dev, struct ip_mc_list *im,			      gfp_t gfp);static void igmpv3_del_delrec(struct in_device *in_dev, struct ip_mc_list *im);static void igmpv3_clear_delrec(struct in_device *in_dev);static int sf_setstate(struct ip_mc_list *pmc);static void sf_markstate(struct ip_mc_list *pmc);#endifstatic void ip_mc_clear_src(struct ip_mc_list *pmc);static int ip_mc_add_src(struct in_device *in_dev, __be32 *pmca, int sfmode,			 int sfcount, __be32 *psfsrc, int delta); static void ip_ma_put(struct ip_mc_list *im){	if (refcount_dec_and_test(&im->refcnt)) {		in_dev_put(im->interface);		kfree_rcu(im, rcu);	}} #define pmc_dereference(e, pmc) \	rcu_dereference_protected(e, lockdep_is_held(&(pmc)->lock) || \				     lockdep_is_held(&(pmc)->interface->mc_tomb_lock)) #define for_each_pmc_rcu(in_dev, pmc)				\	for (pmc = rcu_dereference(in_dev->mc_list);		\	     pmc != NULL;					\	     pmc = rcu_dereference(pmc->next_rcu)) #define for_each_pmc_rtnl(in_dev, pmc)				\	for (pmc = rtnl_dereference(in_dev->mc_list);		\	     pmc != NULL;					\	     pmc = rtnl_dereference(pmc->next_rcu)) #define for_each_psf_mclock(pmc, psf)				\	for (psf = pmc_dereference((pmc)->sources, pmc);	\	     psf;						\	     psf = pmc_dereference(psf->sf_next, pmc)) #define for_each_psf_rcu(im, psf)				\	for (psf = rcu_dereference((im)->sources);		\	     psf;						\	     psf = rcu_dereference(psf->sf_next)) #define for_each_psf_tomb(pmc, psf)				\	for (psf = pmc_dereference((pmc)->tomb, pmc);		\	     psf;						\	     psf = pmc_dereference(psf->sf_next, pmc)) static void ip_sf_list_clear_all(struct ip_sf_list *psf){	struct ip_sf_list *next; 	while (psf) {		next = rcu_dereference_protected(psf->sf_next, 1);		kfree_rcu(psf, rcu);		psf = next;	}} #ifdef CONFIG_IP_MULTICAST /* *	Timer management */ static void igmp_stop_timer(struct ip_mc_list *im){	bool put = false; 	spin_lock_bh(&im->lock);	if (timer_delete(&im->timer))		put = true;	WRITE_ONCE(im->tm_running, 0);	WRITE_ONCE(im->reporter, 0);	im->unsolicit_count = 0;	spin_unlock_bh(&im->lock); 	if (put)		ip_ma_put(im);} /* It must be called with locked im->lock */static void igmp_start_timer(struct ip_mc_list *im, int max_delay){	int tv = get_random_u32_below(max_delay); 	WRITE_ONCE(im->tm_running, 1);	if (refcount_inc_not_zero(&im->refcnt)) {		if (mod_timer(&im->timer, jiffies + tv + 2))			ip_ma_put(im);	}} static void igmp_gq_start_timer(struct in_device *in_dev){	int tv = get_random_u32_below(READ_ONCE(in_dev->mr_maxdelay));	unsigned long exp = jiffies + tv + 2; 	if (in_dev->mr_gq_running &&	    time_after_eq(exp, (in_dev->mr_gq_timer).expires))		return; 	in_dev->mr_gq_running = 1;	if (in_dev_hold_safe(in_dev)) {		if (mod_timer(&in_dev->mr_gq_timer, exp))			in_dev_put(in_dev);	}} static void igmp_ifc_start_timer(struct in_device *in_dev, int delay){	if (in_dev_hold_safe(in_dev)) {		int tv = get_random_u32_below(delay); 		if (mod_timer(&in_dev->mr_ifc_timer, jiffies + tv + 2))			in_dev_put(in_dev);	}} static void igmp_mod_timer(struct ip_mc_list *im, int max_delay){	bool put = false; 	spin_lock_bh(&im->lock);	im->unsolicit_count = 0;	if (timer_delete(&im->timer)) {		if ((long)(im->timer.expires-jiffies) < max_delay) {			add_timer(&im->timer);			WRITE_ONCE(im->tm_running, 1);			spin_unlock_bh(&im->lock);			return;		}		put = true;	}	igmp_start_timer(im, max_delay);	spin_unlock_bh(&im->lock); 	if (put)		ip_ma_put(im);}  /* *	Send an IGMP report. */ #define IGMP_SIZE (sizeof(struct igmphdr)+sizeof(struct iphdr)+4)  static int is_in(struct ip_mc_list *pmc, struct ip_sf_list *psf, int type,	int gdeleted, int sdeleted){	switch (type) {	case IGMPV3_MODE_IS_INCLUDE:	case IGMPV3_MODE_IS_EXCLUDE:		if (gdeleted || sdeleted)			return 0;		if (!(pmc->gsquery && !psf->sf_gsresp)) {			if (pmc->sfmode == MCAST_INCLUDE)				return 1;			/* don't include if this source is excluded			 * in all filters			 */			if (psf->sf_count[MCAST_INCLUDE])				return type == IGMPV3_MODE_IS_INCLUDE;			return pmc->sfcount[MCAST_EXCLUDE] ==				psf->sf_count[MCAST_EXCLUDE];		}		return 0;	case IGMPV3_CHANGE_TO_INCLUDE:		if (gdeleted || sdeleted)			return 0;		return psf->sf_count[MCAST_INCLUDE] != 0;	case IGMPV3_CHANGE_TO_EXCLUDE:		if (gdeleted || sdeleted)			return 0;		if (pmc->sfcount[MCAST_EXCLUDE] == 0 ||		    psf->sf_count[MCAST_INCLUDE])			return 0;		return pmc->sfcount[MCAST_EXCLUDE] ==			psf->sf_count[MCAST_EXCLUDE];	case IGMPV3_ALLOW_NEW_SOURCES:		if (gdeleted || !psf->sf_crcount)			return 0;		return (pmc->sfmode == MCAST_INCLUDE) ^ sdeleted;	case IGMPV3_BLOCK_OLD_SOURCES:		if (pmc->sfmode == MCAST_INCLUDE)			return gdeleted || (psf->sf_crcount && sdeleted);		return psf->sf_crcount && !gdeleted && !sdeleted;	}	return 0;} static intigmp_scount(struct ip_mc_list *pmc, int type, int gdeleted, int sdeleted){	struct ip_sf_list *psf;	int scount = 0; 	for_each_psf_mclock(pmc, psf) {		if (!is_in(pmc, psf, type, gdeleted, sdeleted))			continue;		scount++;	}	return scount;} /* source address selection per RFC 3376 section 4.2.13 */static __be32 igmpv3_get_srcaddr(struct net_device *dev,				 const struct flowi4 *fl4){	struct in_device *in_dev = __in_dev_get_rcu(dev);	const struct in_ifaddr *ifa; 	if (!in_dev)		return htonl(INADDR_ANY); 	in_dev_for_each_ifa_rcu(ifa, in_dev) {		if (fl4->saddr == ifa->ifa_local)			return fl4->saddr;	} 	return htonl(INADDR_ANY);} static struct sk_buff *igmpv3_newpack(struct net_device *dev, unsigned int mtu){	struct sk_buff *skb;	struct rtable *rt;	struct iphdr *pip;	struct igmpv3_report *pig;	struct net *net = dev_net(dev);	struct flowi4 fl4;	int hlen = LL_RESERVED_SPACE(dev);	int tlen = dev->needed_tailroom;	unsigned int size; 	size = min(mtu, IP_MAX_MTU);	while (1) {		skb = alloc_skb(size + hlen + tlen,				GFP_ATOMIC | __GFP_NOWARN);		if (skb)			break;		size >>= 1;		if (size < 256)			return NULL;	}	skb->priority = TC_PRIO_CONTROL; 	rt = ip_route_output_ports(net, &fl4, NULL, IGMPV3_ALL_MCR, 0,				   0, 0,				   IPPROTO_IGMP, 0, dev->ifindex);	if (IS_ERR(rt)) {		kfree_skb(skb);		return NULL;	} 	skb_dst_set(skb, &rt->dst);	skb->dev = dev; 	skb_reserve(skb, hlen);	skb_tailroom_reserve(skb, mtu, tlen); 	skb_reset_network_header(skb);	pip = ip_hdr(skb);	skb_put(skb, sizeof(struct iphdr) + 4); 	pip->version  = 4;	pip->ihl      = (sizeof(struct iphdr)+4)>>2;	pip->tos      = 0xc0;	pip->frag_off = htons(IP_DF);	pip->ttl      = 1;	pip->daddr    = fl4.daddr; 	rcu_read_lock();	pip->saddr    = igmpv3_get_srcaddr(dev, &fl4);	rcu_read_unlock(); 	pip->protocol = IPPROTO_IGMP;	pip->tot_len  = 0;	/* filled in later */	ip_select_ident(net, skb, NULL);	((u8 *)&pip[1])[0] = IPOPT_RA;	((u8 *)&pip[1])[1] = 4;	((u8 *)&pip[1])[2] = 0;	((u8 *)&pip[1])[3] = 0; 	skb->transport_header = skb->network_header + sizeof(struct iphdr) + 4;	skb_put(skb, sizeof(*pig));	pig = igmpv3_report_hdr(skb);	pig->type = IGMPV3_HOST_MEMBERSHIP_REPORT;	pig->resv1 = 0;	pig->csum = 0;	pig->resv2 = 0;	pig->ngrec = 0;	return skb;} static int igmpv3_sendpack(struct sk_buff *skb){	struct igmphdr *pig = igmp_hdr(skb);	const int igmplen = skb_tail_pointer(skb) - skb_transport_header(skb); 	pig->csum = ip_compute_csum(igmp_hdr(skb), igmplen); 	return ip_local_out(skb_dst_dev_net(skb), skb->sk, skb);} static int grec_size(struct ip_mc_list *pmc, int type, int gdel, int sdel){	return sizeof(struct igmpv3_grec) + 4*igmp_scount(pmc, type, gdel, sdel);} static struct sk_buff *add_grhead(struct sk_buff *skb, struct ip_mc_list *pmc,	int type, struct igmpv3_grec **ppgr, unsigned int mtu){	struct net_device *dev = pmc->interface->dev;	struct igmpv3_report *pih;	struct igmpv3_grec *pgr; 	if (!skb) {		skb = igmpv3_newpack(dev, mtu);		if (!skb)			return NULL;	}	pgr = skb_put(skb, sizeof(struct igmpv3_grec));	pgr->grec_type = type;	pgr->grec_auxwords = 0;	pgr->grec_nsrcs = 0;	pgr->grec_mca = pmc->multiaddr;

Lines 1–500 of 3288.