netlink: use call_rcu for netlink_change_ngroups
For the network namespace work in generic netlink I need to be able to call this function under rcu_read_lock(), otherwise the locking becomes a nightmare and more locks would be needed. Instead, just embed a struct rcu_head (actually a struct listeners_rcu_head that also carries the pointer to the memory block) into the listeners memory so we can use call_rcu() instead of synchronising and then freeing. No rcu_barrier() is needed since this code cannot be modular. Signed-off-by: Johannes Berg <johannes@sipsolutions.net> Signed-off-by: David S. Miller <davem@davemloft.net>
This commit is contained in:
		
					parent
					
						
							
								487420df79
							
						
					
				
			
			
				commit
				
					
						6c04bb18dd
					
				
			
		
					 1 changed files with 30 additions and 4 deletions
				
			
		| 
						 | 
					@ -83,6 +83,11 @@ struct netlink_sock {
 | 
				
			||||||
	struct module		*module;
 | 
						struct module		*module;
 | 
				
			||||||
};
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					struct listeners_rcu_head {
 | 
				
			||||||
 | 
						struct rcu_head rcu_head;
 | 
				
			||||||
 | 
						void *ptr;
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
#define NETLINK_KERNEL_SOCKET	0x1
 | 
					#define NETLINK_KERNEL_SOCKET	0x1
 | 
				
			||||||
#define NETLINK_RECV_PKTINFO	0x2
 | 
					#define NETLINK_RECV_PKTINFO	0x2
 | 
				
			||||||
#define NETLINK_BROADCAST_SEND_ERROR	0x4
 | 
					#define NETLINK_BROADCAST_SEND_ERROR	0x4
 | 
				
			||||||
| 
						 | 
					@ -1453,7 +1458,8 @@ netlink_kernel_create(struct net *net, int unit, unsigned int groups,
 | 
				
			||||||
	if (groups < 32)
 | 
						if (groups < 32)
 | 
				
			||||||
		groups = 32;
 | 
							groups = 32;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	listeners = kzalloc(NLGRPSZ(groups), GFP_KERNEL);
 | 
						listeners = kzalloc(NLGRPSZ(groups) + sizeof(struct listeners_rcu_head),
 | 
				
			||||||
 | 
								    GFP_KERNEL);
 | 
				
			||||||
	if (!listeners)
 | 
						if (!listeners)
 | 
				
			||||||
		goto out_sock_release;
 | 
							goto out_sock_release;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					@ -1501,6 +1507,14 @@ netlink_kernel_release(struct sock *sk)
 | 
				
			||||||
EXPORT_SYMBOL(netlink_kernel_release);
 | 
					EXPORT_SYMBOL(netlink_kernel_release);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					static void netlink_free_old_listeners(struct rcu_head *rcu_head)
 | 
				
			||||||
 | 
					{
 | 
				
			||||||
 | 
						struct listeners_rcu_head *lrh;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						lrh = container_of(rcu_head, struct listeners_rcu_head, rcu_head);
 | 
				
			||||||
 | 
						kfree(lrh->ptr);
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
/**
 | 
					/**
 | 
				
			||||||
 * netlink_change_ngroups - change number of multicast groups
 | 
					 * netlink_change_ngroups - change number of multicast groups
 | 
				
			||||||
 *
 | 
					 *
 | 
				
			||||||
| 
						 | 
					@ -1516,6 +1530,7 @@ EXPORT_SYMBOL(netlink_kernel_release);
 | 
				
			||||||
int netlink_change_ngroups(struct sock *sk, unsigned int groups)
 | 
					int netlink_change_ngroups(struct sock *sk, unsigned int groups)
 | 
				
			||||||
{
 | 
					{
 | 
				
			||||||
	unsigned long *listeners, *old = NULL;
 | 
						unsigned long *listeners, *old = NULL;
 | 
				
			||||||
 | 
						struct listeners_rcu_head *old_rcu_head;
 | 
				
			||||||
	struct netlink_table *tbl = &nl_table[sk->sk_protocol];
 | 
						struct netlink_table *tbl = &nl_table[sk->sk_protocol];
 | 
				
			||||||
	int err = 0;
 | 
						int err = 0;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					@ -1524,7 +1539,9 @@ int netlink_change_ngroups(struct sock *sk, unsigned int groups)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	netlink_table_grab();
 | 
						netlink_table_grab();
 | 
				
			||||||
	if (NLGRPSZ(tbl->groups) < NLGRPSZ(groups)) {
 | 
						if (NLGRPSZ(tbl->groups) < NLGRPSZ(groups)) {
 | 
				
			||||||
		listeners = kzalloc(NLGRPSZ(groups), GFP_ATOMIC);
 | 
							listeners = kzalloc(NLGRPSZ(groups) +
 | 
				
			||||||
 | 
									    sizeof(struct listeners_rcu_head),
 | 
				
			||||||
 | 
									    GFP_ATOMIC);
 | 
				
			||||||
		if (!listeners) {
 | 
							if (!listeners) {
 | 
				
			||||||
			err = -ENOMEM;
 | 
								err = -ENOMEM;
 | 
				
			||||||
			goto out_ungrab;
 | 
								goto out_ungrab;
 | 
				
			||||||
| 
						 | 
					@ -1532,13 +1549,22 @@ int netlink_change_ngroups(struct sock *sk, unsigned int groups)
 | 
				
			||||||
		old = tbl->listeners;
 | 
							old = tbl->listeners;
 | 
				
			||||||
		memcpy(listeners, old, NLGRPSZ(tbl->groups));
 | 
							memcpy(listeners, old, NLGRPSZ(tbl->groups));
 | 
				
			||||||
		rcu_assign_pointer(tbl->listeners, listeners);
 | 
							rcu_assign_pointer(tbl->listeners, listeners);
 | 
				
			||||||
 | 
							/*
 | 
				
			||||||
 | 
							 * Free the old memory after an RCU grace period so we
 | 
				
			||||||
 | 
							 * don't leak it. We use call_rcu() here in order to be
 | 
				
			||||||
 | 
							 * able to call this function from atomic contexts. The
 | 
				
			||||||
 | 
							 * allocation of this memory will have reserved enough
 | 
				
			||||||
 | 
							 * space for struct listeners_rcu_head at the end.
 | 
				
			||||||
 | 
							 */
 | 
				
			||||||
 | 
							old_rcu_head = (void *)(tbl->listeners +
 | 
				
			||||||
 | 
										NLGRPLONGS(tbl->groups));
 | 
				
			||||||
 | 
							old_rcu_head->ptr = old;
 | 
				
			||||||
 | 
							call_rcu(&old_rcu_head->rcu_head, netlink_free_old_listeners);
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
	tbl->groups = groups;
 | 
						tbl->groups = groups;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 out_ungrab:
 | 
					 out_ungrab:
 | 
				
			||||||
	netlink_table_ungrab();
 | 
						netlink_table_ungrab();
 | 
				
			||||||
	synchronize_rcu();
 | 
					 | 
				
			||||||
	kfree(old);
 | 
					 | 
				
			||||||
	return err;
 | 
						return err;
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue