forked from mirrors/linux
		
	ipv6/mcast: init as INCLUDE when join SSM INCLUDE group
This an IPv6 version patch of "ipv4/igmp: init group mode as INCLUDE when join source group". From RFC3810, part 6.1: If no per-interface state existed for that multicast address before the change (i.e., the change consisted of creating a new per-interface record), or if no state exists after the change (i.e., the change consisted of deleting a per-interface record), then the "non-existent" state is considered to have an INCLUDE filter mode and an empty source list. Which means a new multicast group should start with state IN(). Currently, for MLDv2 SSM JOIN_SOURCE_GROUP mode, we first call ipv6_sock_mc_join(), then ip6_mc_source(), which will trigger a TO_IN() message instead of ALLOW(). The issue was exposed by commita052517a8f("net/multicast: should not send source list records when have filter mode change"). Before this change, we sent both ALLOW(A) and TO_IN(A). Now, we only send TO_IN(A). Fix it by adding a new parameter to init group mode. Also add some wrapper functions to avoid changing too much code. v1 -> v2: In the first version I only cleared the group change record. But this is not enough. Because when a new group join, it will init as EXCLUDE and trigger a filter mode change in ip/ip6_mc_add_src(), which will clear all source addresses sf_crcount. This will prevent early joined address sending state change records if multi source addressed joined at the same time. In v2 patch, I fixed it by directly initializing the mode to INCLUDE for SSM JOIN_SOURCE_GROUP. I also split the original patch into two separated patches for IPv4 and IPv6. There is also a difference between v4 and v6 version. For IPv6, when the interface goes down and up, we will send correct state change record with unspecified IPv6 address (::) with function ipv6_mc_up(). But after DAD is completed, we resend the change record TO_IN() in mld_send_initial_cr(). Fix it by sending ALLOW() for INCLUDE mode in mld_send_initial_cr(). Fixes:a052517a8f("net/multicast: should not send source list records when have filter mode change") Reviewed-by: Stefano Brivio <sbrivio@redhat.com> Signed-off-by: Hangbin Liu <liuhangbin@gmail.com> Signed-off-by: David S. Miller <davem@davemloft.net>
This commit is contained in:
		
							parent
							
								
									6e2059b53f
								
							
						
					
					
						commit
						c7ea20c9da
					
				
					 3 changed files with 50 additions and 21 deletions
				
			
		| 
						 | 
					@ -1100,6 +1100,8 @@ void ipv6_sysctl_unregister(void);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
int ipv6_sock_mc_join(struct sock *sk, int ifindex,
 | 
					int ipv6_sock_mc_join(struct sock *sk, int ifindex,
 | 
				
			||||||
		      const struct in6_addr *addr);
 | 
							      const struct in6_addr *addr);
 | 
				
			||||||
 | 
					int ipv6_sock_mc_join_ssm(struct sock *sk, int ifindex,
 | 
				
			||||||
 | 
								  const struct in6_addr *addr, unsigned int mode);
 | 
				
			||||||
int ipv6_sock_mc_drop(struct sock *sk, int ifindex,
 | 
					int ipv6_sock_mc_drop(struct sock *sk, int ifindex,
 | 
				
			||||||
		      const struct in6_addr *addr);
 | 
							      const struct in6_addr *addr);
 | 
				
			||||||
#endif /* _NET_IPV6_H */
 | 
					#endif /* _NET_IPV6_H */
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -729,8 +729,9 @@ static int do_ipv6_setsockopt(struct sock *sk, int level, int optname,
 | 
				
			||||||
			struct sockaddr_in6 *psin6;
 | 
								struct sockaddr_in6 *psin6;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
			psin6 = (struct sockaddr_in6 *)&greqs.gsr_group;
 | 
								psin6 = (struct sockaddr_in6 *)&greqs.gsr_group;
 | 
				
			||||||
			retv = ipv6_sock_mc_join(sk, greqs.gsr_interface,
 | 
								retv = ipv6_sock_mc_join_ssm(sk, greqs.gsr_interface,
 | 
				
			||||||
						 &psin6->sin6_addr);
 | 
											     &psin6->sin6_addr,
 | 
				
			||||||
 | 
											     MCAST_INCLUDE);
 | 
				
			||||||
			/* prior join w/ different source is ok */
 | 
								/* prior join w/ different source is ok */
 | 
				
			||||||
			if (retv && retv != -EADDRINUSE)
 | 
								if (retv && retv != -EADDRINUSE)
 | 
				
			||||||
				break;
 | 
									break;
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -95,6 +95,8 @@ static int ip6_mc_add_src(struct inet6_dev *idev, const struct in6_addr *pmca,
 | 
				
			||||||
			  int delta);
 | 
								  int delta);
 | 
				
			||||||
static int ip6_mc_leave_src(struct sock *sk, struct ipv6_mc_socklist *iml,
 | 
					static int ip6_mc_leave_src(struct sock *sk, struct ipv6_mc_socklist *iml,
 | 
				
			||||||
			    struct inet6_dev *idev);
 | 
								    struct inet6_dev *idev);
 | 
				
			||||||
 | 
					static int __ipv6_dev_mc_inc(struct net_device *dev,
 | 
				
			||||||
 | 
								     const struct in6_addr *addr, unsigned int mode);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
#define MLD_QRV_DEFAULT		2
 | 
					#define MLD_QRV_DEFAULT		2
 | 
				
			||||||
/* RFC3810, 9.2. Query Interval */
 | 
					/* RFC3810, 9.2. Query Interval */
 | 
				
			||||||
| 
						 | 
					@ -132,7 +134,8 @@ static int unsolicited_report_interval(struct inet6_dev *idev)
 | 
				
			||||||
	return iv > 0 ? iv : 1;
 | 
						return iv > 0 ? iv : 1;
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
int ipv6_sock_mc_join(struct sock *sk, int ifindex, const struct in6_addr *addr)
 | 
					static int __ipv6_sock_mc_join(struct sock *sk, int ifindex,
 | 
				
			||||||
 | 
								       const struct in6_addr *addr, unsigned int mode)
 | 
				
			||||||
{
 | 
					{
 | 
				
			||||||
	struct net_device *dev = NULL;
 | 
						struct net_device *dev = NULL;
 | 
				
			||||||
	struct ipv6_mc_socklist *mc_lst;
 | 
						struct ipv6_mc_socklist *mc_lst;
 | 
				
			||||||
| 
						 | 
					@ -179,7 +182,7 @@ int ipv6_sock_mc_join(struct sock *sk, int ifindex, const struct in6_addr *addr)
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	mc_lst->ifindex = dev->ifindex;
 | 
						mc_lst->ifindex = dev->ifindex;
 | 
				
			||||||
	mc_lst->sfmode = MCAST_EXCLUDE;
 | 
						mc_lst->sfmode = mode;
 | 
				
			||||||
	rwlock_init(&mc_lst->sflock);
 | 
						rwlock_init(&mc_lst->sflock);
 | 
				
			||||||
	mc_lst->sflist = NULL;
 | 
						mc_lst->sflist = NULL;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					@ -187,7 +190,7 @@ int ipv6_sock_mc_join(struct sock *sk, int ifindex, const struct in6_addr *addr)
 | 
				
			||||||
	 *	now add/increase the group membership on the device
 | 
						 *	now add/increase the group membership on the device
 | 
				
			||||||
	 */
 | 
						 */
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	err = ipv6_dev_mc_inc(dev, addr);
 | 
						err = __ipv6_dev_mc_inc(dev, addr, mode);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	if (err) {
 | 
						if (err) {
 | 
				
			||||||
		sock_kfree_s(sk, mc_lst, sizeof(*mc_lst));
 | 
							sock_kfree_s(sk, mc_lst, sizeof(*mc_lst));
 | 
				
			||||||
| 
						 | 
					@ -199,8 +202,19 @@ int ipv6_sock_mc_join(struct sock *sk, int ifindex, const struct in6_addr *addr)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	return 0;
 | 
						return 0;
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					int ipv6_sock_mc_join(struct sock *sk, int ifindex, const struct in6_addr *addr)
 | 
				
			||||||
 | 
					{
 | 
				
			||||||
 | 
						return __ipv6_sock_mc_join(sk, ifindex, addr, MCAST_EXCLUDE);
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
EXPORT_SYMBOL(ipv6_sock_mc_join);
 | 
					EXPORT_SYMBOL(ipv6_sock_mc_join);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					int ipv6_sock_mc_join_ssm(struct sock *sk, int ifindex,
 | 
				
			||||||
 | 
								  const struct in6_addr *addr, unsigned int mode)
 | 
				
			||||||
 | 
					{
 | 
				
			||||||
 | 
						return __ipv6_sock_mc_join(sk, ifindex, addr, mode);
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
/*
 | 
					/*
 | 
				
			||||||
 *	socket leave on multicast group
 | 
					 *	socket leave on multicast group
 | 
				
			||||||
 */
 | 
					 */
 | 
				
			||||||
| 
						 | 
					@ -646,7 +660,7 @@ bool inet6_mc_check(struct sock *sk, const struct in6_addr *mc_addr,
 | 
				
			||||||
	return rv;
 | 
						return rv;
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
static void igmp6_group_added(struct ifmcaddr6 *mc)
 | 
					static void igmp6_group_added(struct ifmcaddr6 *mc, unsigned int mode)
 | 
				
			||||||
{
 | 
					{
 | 
				
			||||||
	struct net_device *dev = mc->idev->dev;
 | 
						struct net_device *dev = mc->idev->dev;
 | 
				
			||||||
	char buf[MAX_ADDR_LEN];
 | 
						char buf[MAX_ADDR_LEN];
 | 
				
			||||||
| 
						 | 
					@ -672,7 +686,13 @@ static void igmp6_group_added(struct ifmcaddr6 *mc)
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
	/* else v2 */
 | 
						/* else v2 */
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	mc->mca_crcount = mc->idev->mc_qrv;
 | 
						/* Based on RFC3810 6.1, for newly added INCLUDE SSM, we
 | 
				
			||||||
 | 
						 * should not send filter-mode change record as the mode
 | 
				
			||||||
 | 
						 * should be from IN() to IN(A).
 | 
				
			||||||
 | 
						 */
 | 
				
			||||||
 | 
						if (mode == MCAST_EXCLUDE)
 | 
				
			||||||
 | 
							mc->mca_crcount = mc->idev->mc_qrv;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	mld_ifc_event(mc->idev);
 | 
						mld_ifc_event(mc->idev);
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					@ -770,13 +790,14 @@ static void mld_del_delrec(struct inet6_dev *idev, struct ifmcaddr6 *im)
 | 
				
			||||||
	spin_lock_bh(&im->mca_lock);
 | 
						spin_lock_bh(&im->mca_lock);
 | 
				
			||||||
	if (pmc) {
 | 
						if (pmc) {
 | 
				
			||||||
		im->idev = pmc->idev;
 | 
							im->idev = pmc->idev;
 | 
				
			||||||
		im->mca_crcount = idev->mc_qrv;
 | 
					 | 
				
			||||||
		im->mca_sfmode = pmc->mca_sfmode;
 | 
							im->mca_sfmode = pmc->mca_sfmode;
 | 
				
			||||||
		if (pmc->mca_sfmode == MCAST_INCLUDE) {
 | 
							if (pmc->mca_sfmode == MCAST_INCLUDE) {
 | 
				
			||||||
			im->mca_tomb = pmc->mca_tomb;
 | 
								im->mca_tomb = pmc->mca_tomb;
 | 
				
			||||||
			im->mca_sources = pmc->mca_sources;
 | 
								im->mca_sources = pmc->mca_sources;
 | 
				
			||||||
			for (psf = im->mca_sources; psf; psf = psf->sf_next)
 | 
								for (psf = im->mca_sources; psf; psf = psf->sf_next)
 | 
				
			||||||
				psf->sf_crcount = im->mca_crcount;
 | 
									psf->sf_crcount = idev->mc_qrv;
 | 
				
			||||||
 | 
							} else {
 | 
				
			||||||
 | 
								im->mca_crcount = idev->mc_qrv;
 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
		in6_dev_put(pmc->idev);
 | 
							in6_dev_put(pmc->idev);
 | 
				
			||||||
		kfree(pmc);
 | 
							kfree(pmc);
 | 
				
			||||||
| 
						 | 
					@ -831,7 +852,8 @@ static void ma_put(struct ifmcaddr6 *mc)
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
static struct ifmcaddr6 *mca_alloc(struct inet6_dev *idev,
 | 
					static struct ifmcaddr6 *mca_alloc(struct inet6_dev *idev,
 | 
				
			||||||
				   const struct in6_addr *addr)
 | 
									   const struct in6_addr *addr,
 | 
				
			||||||
 | 
									   unsigned int mode)
 | 
				
			||||||
{
 | 
					{
 | 
				
			||||||
	struct ifmcaddr6 *mc;
 | 
						struct ifmcaddr6 *mc;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					@ -849,9 +871,8 @@ static struct ifmcaddr6 *mca_alloc(struct inet6_dev *idev,
 | 
				
			||||||
	refcount_set(&mc->mca_refcnt, 1);
 | 
						refcount_set(&mc->mca_refcnt, 1);
 | 
				
			||||||
	spin_lock_init(&mc->mca_lock);
 | 
						spin_lock_init(&mc->mca_lock);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	/* initial mode is (EX, empty) */
 | 
						mc->mca_sfmode = mode;
 | 
				
			||||||
	mc->mca_sfmode = MCAST_EXCLUDE;
 | 
						mc->mca_sfcount[mode] = 1;
 | 
				
			||||||
	mc->mca_sfcount[MCAST_EXCLUDE] = 1;
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
	if (ipv6_addr_is_ll_all_nodes(&mc->mca_addr) ||
 | 
						if (ipv6_addr_is_ll_all_nodes(&mc->mca_addr) ||
 | 
				
			||||||
	    IPV6_ADDR_MC_SCOPE(&mc->mca_addr) < IPV6_ADDR_SCOPE_LINKLOCAL)
 | 
						    IPV6_ADDR_MC_SCOPE(&mc->mca_addr) < IPV6_ADDR_SCOPE_LINKLOCAL)
 | 
				
			||||||
| 
						 | 
					@ -863,7 +884,8 @@ static struct ifmcaddr6 *mca_alloc(struct inet6_dev *idev,
 | 
				
			||||||
/*
 | 
					/*
 | 
				
			||||||
 *	device multicast group inc (add if not found)
 | 
					 *	device multicast group inc (add if not found)
 | 
				
			||||||
 */
 | 
					 */
 | 
				
			||||||
int ipv6_dev_mc_inc(struct net_device *dev, const struct in6_addr *addr)
 | 
					static int __ipv6_dev_mc_inc(struct net_device *dev,
 | 
				
			||||||
 | 
								     const struct in6_addr *addr, unsigned int mode)
 | 
				
			||||||
{
 | 
					{
 | 
				
			||||||
	struct ifmcaddr6 *mc;
 | 
						struct ifmcaddr6 *mc;
 | 
				
			||||||
	struct inet6_dev *idev;
 | 
						struct inet6_dev *idev;
 | 
				
			||||||
| 
						 | 
					@ -887,14 +909,13 @@ int ipv6_dev_mc_inc(struct net_device *dev, const struct in6_addr *addr)
 | 
				
			||||||
		if (ipv6_addr_equal(&mc->mca_addr, addr)) {
 | 
							if (ipv6_addr_equal(&mc->mca_addr, addr)) {
 | 
				
			||||||
			mc->mca_users++;
 | 
								mc->mca_users++;
 | 
				
			||||||
			write_unlock_bh(&idev->lock);
 | 
								write_unlock_bh(&idev->lock);
 | 
				
			||||||
			ip6_mc_add_src(idev, &mc->mca_addr, MCAST_EXCLUDE, 0,
 | 
								ip6_mc_add_src(idev, &mc->mca_addr, mode, 0, NULL, 0);
 | 
				
			||||||
				NULL, 0);
 | 
					 | 
				
			||||||
			in6_dev_put(idev);
 | 
								in6_dev_put(idev);
 | 
				
			||||||
			return 0;
 | 
								return 0;
 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	mc = mca_alloc(idev, addr);
 | 
						mc = mca_alloc(idev, addr, mode);
 | 
				
			||||||
	if (!mc) {
 | 
						if (!mc) {
 | 
				
			||||||
		write_unlock_bh(&idev->lock);
 | 
							write_unlock_bh(&idev->lock);
 | 
				
			||||||
		in6_dev_put(idev);
 | 
							in6_dev_put(idev);
 | 
				
			||||||
| 
						 | 
					@ -911,11 +932,16 @@ int ipv6_dev_mc_inc(struct net_device *dev, const struct in6_addr *addr)
 | 
				
			||||||
	write_unlock_bh(&idev->lock);
 | 
						write_unlock_bh(&idev->lock);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	mld_del_delrec(idev, mc);
 | 
						mld_del_delrec(idev, mc);
 | 
				
			||||||
	igmp6_group_added(mc);
 | 
						igmp6_group_added(mc, mode);
 | 
				
			||||||
	ma_put(mc);
 | 
						ma_put(mc);
 | 
				
			||||||
	return 0;
 | 
						return 0;
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					int ipv6_dev_mc_inc(struct net_device *dev, const struct in6_addr *addr)
 | 
				
			||||||
 | 
					{
 | 
				
			||||||
 | 
						return __ipv6_dev_mc_inc(dev, addr, MCAST_EXCLUDE);
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
/*
 | 
					/*
 | 
				
			||||||
 *	device multicast group del
 | 
					 *	device multicast group del
 | 
				
			||||||
 */
 | 
					 */
 | 
				
			||||||
| 
						 | 
					@ -1751,7 +1777,7 @@ static struct sk_buff *add_grec(struct sk_buff *skb, struct ifmcaddr6 *pmc,
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		psf_next = psf->sf_next;
 | 
							psf_next = psf->sf_next;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		if (!is_in(pmc, psf, type, gdeleted, sdeleted)) {
 | 
							if (!is_in(pmc, psf, type, gdeleted, sdeleted) && !crsend) {
 | 
				
			||||||
			psf_prev = psf;
 | 
								psf_prev = psf;
 | 
				
			||||||
			continue;
 | 
								continue;
 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
| 
						 | 
					@ -2066,7 +2092,7 @@ static void mld_send_initial_cr(struct inet6_dev *idev)
 | 
				
			||||||
		if (pmc->mca_sfcount[MCAST_EXCLUDE])
 | 
							if (pmc->mca_sfcount[MCAST_EXCLUDE])
 | 
				
			||||||
			type = MLD2_CHANGE_TO_EXCLUDE;
 | 
								type = MLD2_CHANGE_TO_EXCLUDE;
 | 
				
			||||||
		else
 | 
							else
 | 
				
			||||||
			type = MLD2_CHANGE_TO_INCLUDE;
 | 
								type = MLD2_ALLOW_NEW_SOURCES;
 | 
				
			||||||
		skb = add_grec(skb, pmc, type, 0, 0, 1);
 | 
							skb = add_grec(skb, pmc, type, 0, 0, 1);
 | 
				
			||||||
		spin_unlock_bh(&pmc->mca_lock);
 | 
							spin_unlock_bh(&pmc->mca_lock);
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
| 
						 | 
					@ -2546,7 +2572,7 @@ void ipv6_mc_up(struct inet6_dev *idev)
 | 
				
			||||||
	ipv6_mc_reset(idev);
 | 
						ipv6_mc_reset(idev);
 | 
				
			||||||
	for (i = idev->mc_list; i; i = i->next) {
 | 
						for (i = idev->mc_list; i; i = i->next) {
 | 
				
			||||||
		mld_del_delrec(idev, i);
 | 
							mld_del_delrec(idev, i);
 | 
				
			||||||
		igmp6_group_added(i);
 | 
							igmp6_group_added(i, i->mca_sfmode);
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
	read_unlock_bh(&idev->lock);
 | 
						read_unlock_bh(&idev->lock);
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
		Loading…
	
		Reference in a new issue