mirror of
				https://github.com/torvalds/linux.git
				synced 2025-11-04 10:40:15 +02:00 
			
		
		
		
	igmp: hash a hash table to speedup ip_check_mc_rcu()
After IP route cache removal, multicast applications using a lot of multicast addresses hit a O(N) behavior in ip_check_mc_rcu() Add a per in_device hash table to get faster lookup. This hash table is created only if the number of items in mc_list is above 4. Reported-by: Shawn Bohrer <sbohrer@rgmadvisors.com> Signed-off-by: Eric Dumazet <edumazet@google.com> Tested-by: Shawn Bohrer <sbohrer@rgmadvisors.com> Reviewed-by: Cong Wang <xiyou.wangcong@gmail.com> Signed-off-by: David S. Miller <davem@davemloft.net>
This commit is contained in:
		
							parent
							
								
									64153ce0a7
								
							
						
					
					
						commit
						e989707135
					
				
					 4 changed files with 77 additions and 3 deletions
				
			
		| 
						 | 
				
			
			@ -84,6 +84,7 @@ struct ip_mc_list {
 | 
			
		|||
		struct ip_mc_list *next;
 | 
			
		||||
		struct ip_mc_list __rcu *next_rcu;
 | 
			
		||||
	};
 | 
			
		||||
	struct ip_mc_list __rcu *next_hash;
 | 
			
		||||
	struct timer_list	timer;
 | 
			
		||||
	int			users;
 | 
			
		||||
	atomic_t		refcnt;
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -50,12 +50,17 @@ struct ipv4_devconf {
 | 
			
		|||
	DECLARE_BITMAP(state, IPV4_DEVCONF_MAX);
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
#define MC_HASH_SZ_LOG 9
 | 
			
		||||
 | 
			
		||||
struct in_device {
 | 
			
		||||
	struct net_device	*dev;
 | 
			
		||||
	atomic_t		refcnt;
 | 
			
		||||
	int			dead;
 | 
			
		||||
	struct in_ifaddr	*ifa_list;	/* IP ifaddr chain		*/
 | 
			
		||||
 | 
			
		||||
	struct ip_mc_list __rcu	*mc_list;	/* IP multicast filter chain    */
 | 
			
		||||
	struct ip_mc_list __rcu	* __rcu *mc_hash;
 | 
			
		||||
 | 
			
		||||
	int			mc_count;	/* Number of installed mcasts	*/
 | 
			
		||||
	spinlock_t		mc_tomb_lock;
 | 
			
		||||
	struct ip_mc_list	*mc_tomb;
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -215,6 +215,7 @@ void in_dev_finish_destroy(struct in_device *idev)
 | 
			
		|||
 | 
			
		||||
	WARN_ON(idev->ifa_list);
 | 
			
		||||
	WARN_ON(idev->mc_list);
 | 
			
		||||
	kfree(rcu_dereference_protected(idev->mc_hash, 1));
 | 
			
		||||
#ifdef NET_REFCNT_DEBUG
 | 
			
		||||
	pr_debug("%s: %p=%s\n", __func__, idev, dev ? dev->name : "NIL");
 | 
			
		||||
#endif
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -1217,6 +1217,57 @@ static void igmp_group_added(struct ip_mc_list *im)
 | 
			
		|||
 *	Multicast list managers
 | 
			
		||||
 */
 | 
			
		||||
 | 
			
		||||
static u32 ip_mc_hash(const struct ip_mc_list *im)
 | 
			
		||||
{
 | 
			
		||||
	return hash_32((u32)im->multiaddr, MC_HASH_SZ_LOG);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
static void ip_mc_hash_add(struct in_device *in_dev,
 | 
			
		||||
			   struct ip_mc_list *im)
 | 
			
		||||
{
 | 
			
		||||
	struct ip_mc_list __rcu **mc_hash;
 | 
			
		||||
	u32 hash;
 | 
			
		||||
 | 
			
		||||
	mc_hash = rtnl_dereference(in_dev->mc_hash);
 | 
			
		||||
	if (mc_hash) {
 | 
			
		||||
		hash = ip_mc_hash(im);
 | 
			
		||||
		im->next_hash = rtnl_dereference(mc_hash[hash]);
 | 
			
		||||
		rcu_assign_pointer(mc_hash[hash], im);
 | 
			
		||||
		return;
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	/* do not use a hash table for small number of items */
 | 
			
		||||
	if (in_dev->mc_count < 4)
 | 
			
		||||
		return;
 | 
			
		||||
 | 
			
		||||
	mc_hash = kzalloc(sizeof(struct ip_mc_list *) << MC_HASH_SZ_LOG,
 | 
			
		||||
			  GFP_KERNEL);
 | 
			
		||||
	if (!mc_hash)
 | 
			
		||||
		return;
 | 
			
		||||
 | 
			
		||||
	for_each_pmc_rtnl(in_dev, im) {
 | 
			
		||||
		hash = ip_mc_hash(im);
 | 
			
		||||
		im->next_hash = rtnl_dereference(mc_hash[hash]);
 | 
			
		||||
		RCU_INIT_POINTER(mc_hash[hash], im);
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	rcu_assign_pointer(in_dev->mc_hash, mc_hash);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
static void ip_mc_hash_remove(struct in_device *in_dev,
 | 
			
		||||
			      struct ip_mc_list *im)
 | 
			
		||||
{
 | 
			
		||||
	struct ip_mc_list __rcu **mc_hash = rtnl_dereference(in_dev->mc_hash);
 | 
			
		||||
	struct ip_mc_list *aux;
 | 
			
		||||
 | 
			
		||||
	if (!mc_hash)
 | 
			
		||||
		return;
 | 
			
		||||
	mc_hash += ip_mc_hash(im);
 | 
			
		||||
	while ((aux = rtnl_dereference(*mc_hash)) != im)
 | 
			
		||||
		mc_hash = &aux->next_hash;
 | 
			
		||||
	*mc_hash = im->next_hash;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
/*
 | 
			
		||||
 *	A socket has joined a multicast group on device dev.
 | 
			
		||||
| 
						 | 
				
			
			@ -1258,6 +1309,8 @@ void ip_mc_inc_group(struct in_device *in_dev, __be32 addr)
 | 
			
		|||
	in_dev->mc_count++;
 | 
			
		||||
	rcu_assign_pointer(in_dev->mc_list, im);
 | 
			
		||||
 | 
			
		||||
	ip_mc_hash_add(in_dev, im);
 | 
			
		||||
 | 
			
		||||
#ifdef CONFIG_IP_MULTICAST
 | 
			
		||||
	igmpv3_del_delrec(in_dev, im->multiaddr);
 | 
			
		||||
#endif
 | 
			
		||||
| 
						 | 
				
			
			@ -1314,6 +1367,7 @@ void ip_mc_dec_group(struct in_device *in_dev, __be32 addr)
 | 
			
		|||
	     ip = &i->next_rcu) {
 | 
			
		||||
		if (i->multiaddr == addr) {
 | 
			
		||||
			if (--i->users == 0) {
 | 
			
		||||
				ip_mc_hash_remove(in_dev, i);
 | 
			
		||||
				*ip = i->next_rcu;
 | 
			
		||||
				in_dev->mc_count--;
 | 
			
		||||
				igmp_group_dropped(i);
 | 
			
		||||
| 
						 | 
				
			
			@ -2321,12 +2375,25 @@ void ip_mc_drop_socket(struct sock *sk)
 | 
			
		|||
int ip_check_mc_rcu(struct in_device *in_dev, __be32 mc_addr, __be32 src_addr, u16 proto)
 | 
			
		||||
{
 | 
			
		||||
	struct ip_mc_list *im;
 | 
			
		||||
	struct ip_mc_list __rcu **mc_hash;
 | 
			
		||||
	struct ip_sf_list *psf;
 | 
			
		||||
	int rv = 0;
 | 
			
		||||
 | 
			
		||||
	for_each_pmc_rcu(in_dev, im) {
 | 
			
		||||
		if (im->multiaddr == mc_addr)
 | 
			
		||||
			break;
 | 
			
		||||
	mc_hash = rcu_dereference(in_dev->mc_hash);
 | 
			
		||||
	if (mc_hash) {
 | 
			
		||||
		u32 hash = hash_32((u32)mc_addr, MC_HASH_SZ_LOG);
 | 
			
		||||
 | 
			
		||||
		for (im = rcu_dereference(mc_hash[hash]);
 | 
			
		||||
		     im != NULL;
 | 
			
		||||
		     im = rcu_dereference(im->next_hash)) {
 | 
			
		||||
			if (im->multiaddr == mc_addr)
 | 
			
		||||
				break;
 | 
			
		||||
		}
 | 
			
		||||
	} else {
 | 
			
		||||
		for_each_pmc_rcu(in_dev, im) {
 | 
			
		||||
			if (im->multiaddr == mc_addr)
 | 
			
		||||
				break;
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
	if (im && proto == IPPROTO_IGMP) {
 | 
			
		||||
		rv = 1;
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
		Loading…
	
		Reference in a new issue