Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit 6c04bb18 authored by Johannes Berg's avatar Johannes Berg Committed by David S. Miller
Browse files

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: default avatarJohannes Berg <johannes@sipsolutions.net>
Signed-off-by: default avatarDavid S. Miller <davem@davemloft.net>
parent 487420df
Loading
Loading
Loading
Loading
+30 −4
Original line number Diff line number Diff line
@@ -83,6 +83,11 @@ struct netlink_sock {
	struct module		*module;
};

struct listeners_rcu_head {
	struct rcu_head rcu_head;
	void *ptr;
};

#define NETLINK_KERNEL_SOCKET	0x1
#define NETLINK_RECV_PKTINFO	0x2
#define NETLINK_BROADCAST_SEND_ERROR	0x4
@@ -1453,7 +1458,8 @@ netlink_kernel_create(struct net *net, int unit, unsigned int groups,
	if (groups < 32)
		groups = 32;

	listeners = kzalloc(NLGRPSZ(groups), GFP_KERNEL);
	listeners = kzalloc(NLGRPSZ(groups) + sizeof(struct listeners_rcu_head),
			    GFP_KERNEL);
	if (!listeners)
		goto out_sock_release;

@@ -1501,6 +1507,14 @@ netlink_kernel_release(struct sock *sk)
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
 *
@@ -1516,6 +1530,7 @@ EXPORT_SYMBOL(netlink_kernel_release);
int netlink_change_ngroups(struct sock *sk, unsigned int groups)
{
	unsigned long *listeners, *old = NULL;
	struct listeners_rcu_head *old_rcu_head;
	struct netlink_table *tbl = &nl_table[sk->sk_protocol];
	int err = 0;

@@ -1524,7 +1539,9 @@ int netlink_change_ngroups(struct sock *sk, unsigned int groups)

	netlink_table_grab();
	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) {
			err = -ENOMEM;
			goto out_ungrab;
@@ -1532,13 +1549,22 @@ int netlink_change_ngroups(struct sock *sk, unsigned int groups)
		old = tbl->listeners;
		memcpy(listeners, old, NLGRPSZ(tbl->groups));
		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;

 out_ungrab:
	netlink_table_ungrab();
	synchronize_rcu();
	kfree(old);
	return err;
}