diff options
Diffstat (limited to 'net/ipv4/igmp.c')
| -rw-r--r-- | net/ipv4/igmp.c | 44 | 
1 files changed, 34 insertions, 10 deletions
| diff --git a/net/ipv4/igmp.c b/net/ipv4/igmp.c index d1f8f302dbf3..726f6b608274 100644 --- a/net/ipv4/igmp.c +++ b/net/ipv4/igmp.c @@ -89,6 +89,7 @@  #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/arp.h> @@ -321,6 +322,23 @@ igmp_scount(struct ip_mc_list *pmc, int type, int gdeleted, int sdeleted)  	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); + +	if (!in_dev) +		return htonl(INADDR_ANY); + +	for_ifa(in_dev) { +		if (inet_ifa_match(fl4->saddr, ifa)) +			return fl4->saddr; +	} endfor_ifa(in_dev); + +	return htonl(INADDR_ANY); +} +  static struct sk_buff *igmpv3_newpack(struct net_device *dev, unsigned int mtu)  {  	struct sk_buff *skb; @@ -368,7 +386,7 @@ static struct sk_buff *igmpv3_newpack(struct net_device *dev, unsigned int mtu)  	pip->frag_off = htons(IP_DF);  	pip->ttl      = 1;  	pip->daddr    = fl4.daddr; -	pip->saddr    = fl4.saddr; +	pip->saddr    = igmpv3_get_srcaddr(dev, &fl4);  	pip->protocol = IPPROTO_IGMP;  	pip->tot_len  = 0;	/* filled in later */  	ip_select_ident(net, skb, NULL); @@ -404,16 +422,17 @@ static int grec_size(struct ip_mc_list *pmc, int type, int gdel, int sdel)  }  static struct sk_buff *add_grhead(struct sk_buff *skb, struct ip_mc_list *pmc, -	int type, struct igmpv3_grec **ppgr) +	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, dev->mtu); -	if (!skb) -		return NULL; +	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; @@ -436,12 +455,17 @@ static struct sk_buff *add_grec(struct sk_buff *skb, struct ip_mc_list *pmc,  	struct igmpv3_grec *pgr = NULL;  	struct ip_sf_list *psf, *psf_next, *psf_prev, **psf_list;  	int scount, stotal, first, isquery, truncate; +	unsigned int mtu;  	if (pmc->multiaddr == IGMP_ALL_HOSTS)  		return skb;  	if (ipv4_is_local_multicast(pmc->multiaddr) && !net->ipv4.sysctl_igmp_llm_reports)  		return skb; +	mtu = READ_ONCE(dev->mtu); +	if (mtu < IPV4_MIN_MTU) +		return skb; +  	isquery = type == IGMPV3_MODE_IS_INCLUDE ||  		  type == IGMPV3_MODE_IS_EXCLUDE;  	truncate = type == IGMPV3_MODE_IS_EXCLUDE || @@ -462,7 +486,7 @@ static struct sk_buff *add_grec(struct sk_buff *skb, struct ip_mc_list *pmc,  		    AVAILABLE(skb) < grec_size(pmc, type, gdeleted, sdeleted)) {  			if (skb)  				igmpv3_sendpack(skb); -			skb = igmpv3_newpack(dev, dev->mtu); +			skb = igmpv3_newpack(dev, mtu);  		}  	}  	first = 1; @@ -498,12 +522,12 @@ static struct sk_buff *add_grec(struct sk_buff *skb, struct ip_mc_list *pmc,  				pgr->grec_nsrcs = htons(scount);  			if (skb)  				igmpv3_sendpack(skb); -			skb = igmpv3_newpack(dev, dev->mtu); +			skb = igmpv3_newpack(dev, mtu);  			first = 1;  			scount = 0;  		}  		if (first) { -			skb = add_grhead(skb, pmc, type, &pgr); +			skb = add_grhead(skb, pmc, type, &pgr, mtu);  			first = 0;  		}  		if (!skb) @@ -538,7 +562,7 @@ empty_source:  				igmpv3_sendpack(skb);  				skb = NULL; /* add_grhead will get a new one */  			} -			skb = add_grhead(skb, pmc, type, &pgr); +			skb = add_grhead(skb, pmc, type, &pgr, mtu);  		}  	}  	if (pgr) |