netfilter: nfnetlink: add RCU in nfnetlink_rcv_msg()

Goal of this patch is to permit nfnetlink providers not mandate
nfnl_mutex being held while nfnetlink_rcv_msg() calls them.

If struct nfnl_callback contains a non NULL call_rcu(), then
nfnetlink_rcv_msg() will use it instead of call() field, holding
rcu_read_lock instead of nfnl_mutex

Signed-off-by: Eric Dumazet <eric.dumazet@gmail.com>
CC: Florian Westphal <fw@strlen.de>
CC: Eric Leblond <eric@regit.org>
Signed-off-by: Patrick McHardy <kaber@trash.net>
This commit is contained in:
Eric Dumazet 2011-07-18 16:08:07 +02:00 коммит произвёл Patrick McHardy
Родитель 131ad62d8f
Коммит 6b75e3e8d6
2 изменённых файлов: 33 добавлений и 10 удалений

Просмотреть файл

@ -60,6 +60,9 @@ struct nfnl_callback {
int (*call)(struct sock *nl, struct sk_buff *skb, int (*call)(struct sock *nl, struct sk_buff *skb,
const struct nlmsghdr *nlh, const struct nlmsghdr *nlh,
const struct nlattr * const cda[]); const struct nlattr * const cda[]);
int (*call_rcu)(struct sock *nl, struct sk_buff *skb,
const struct nlmsghdr *nlh,
const struct nlattr * const cda[]);
const struct nla_policy *policy; /* netlink attribute policy */ const struct nla_policy *policy; /* netlink attribute policy */
const u_int16_t attr_count; /* number of nlattr's */ const u_int16_t attr_count; /* number of nlattr's */
}; };

Просмотреть файл

@ -37,7 +37,7 @@ MODULE_ALIAS_NET_PF_PROTO(PF_NETLINK, NETLINK_NETFILTER);
static char __initdata nfversion[] = "0.30"; static char __initdata nfversion[] = "0.30";
static const struct nfnetlink_subsystem *subsys_table[NFNL_SUBSYS_COUNT]; static const struct nfnetlink_subsystem __rcu *subsys_table[NFNL_SUBSYS_COUNT];
static DEFINE_MUTEX(nfnl_mutex); static DEFINE_MUTEX(nfnl_mutex);
void nfnl_lock(void) void nfnl_lock(void)
@ -59,7 +59,7 @@ int nfnetlink_subsys_register(const struct nfnetlink_subsystem *n)
nfnl_unlock(); nfnl_unlock();
return -EBUSY; return -EBUSY;
} }
subsys_table[n->subsys_id] = n; rcu_assign_pointer(subsys_table[n->subsys_id], n);
nfnl_unlock(); nfnl_unlock();
return 0; return 0;
@ -71,7 +71,7 @@ int nfnetlink_subsys_unregister(const struct nfnetlink_subsystem *n)
nfnl_lock(); nfnl_lock();
subsys_table[n->subsys_id] = NULL; subsys_table[n->subsys_id] = NULL;
nfnl_unlock(); nfnl_unlock();
synchronize_rcu();
return 0; return 0;
} }
EXPORT_SYMBOL_GPL(nfnetlink_subsys_unregister); EXPORT_SYMBOL_GPL(nfnetlink_subsys_unregister);
@ -83,7 +83,7 @@ static inline const struct nfnetlink_subsystem *nfnetlink_get_subsys(u_int16_t t
if (subsys_id >= NFNL_SUBSYS_COUNT) if (subsys_id >= NFNL_SUBSYS_COUNT)
return NULL; return NULL;
return subsys_table[subsys_id]; return rcu_dereference(subsys_table[subsys_id]);
} }
static inline const struct nfnl_callback * static inline const struct nfnl_callback *
@ -139,21 +139,27 @@ static int nfnetlink_rcv_msg(struct sk_buff *skb, struct nlmsghdr *nlh)
type = nlh->nlmsg_type; type = nlh->nlmsg_type;
replay: replay:
rcu_read_lock();
ss = nfnetlink_get_subsys(type); ss = nfnetlink_get_subsys(type);
if (!ss) { if (!ss) {
#ifdef CONFIG_MODULES #ifdef CONFIG_MODULES
nfnl_unlock(); rcu_read_unlock();
request_module("nfnetlink-subsys-%d", NFNL_SUBSYS_ID(type)); request_module("nfnetlink-subsys-%d", NFNL_SUBSYS_ID(type));
nfnl_lock(); rcu_read_lock();
ss = nfnetlink_get_subsys(type); ss = nfnetlink_get_subsys(type);
if (!ss) if (!ss)
#endif #endif
{
rcu_read_unlock();
return -EINVAL; return -EINVAL;
}
} }
nc = nfnetlink_find_client(type, ss); nc = nfnetlink_find_client(type, ss);
if (!nc) if (!nc) {
rcu_read_unlock();
return -EINVAL; return -EINVAL;
}
{ {
int min_len = NLMSG_SPACE(sizeof(struct nfgenmsg)); int min_len = NLMSG_SPACE(sizeof(struct nfgenmsg));
@ -167,7 +173,23 @@ replay:
if (err < 0) if (err < 0)
return err; return err;
err = nc->call(net->nfnl, skb, nlh, (const struct nlattr **)cda); if (nc->call_rcu) {
err = nc->call_rcu(net->nfnl, skb, nlh,
(const struct nlattr **)cda);
rcu_read_unlock();
} else {
rcu_read_unlock();
nfnl_lock();
if (rcu_dereference_protected(
subsys_table[NFNL_SUBSYS_ID(type)],
lockdep_is_held(&nfnl_mutex)) != ss ||
nfnetlink_find_client(type, ss) != nc)
err = -EAGAIN;
else
err = nc->call(net->nfnl, skb, nlh,
(const struct nlattr **)cda);
nfnl_unlock();
}
if (err == -EAGAIN) if (err == -EAGAIN)
goto replay; goto replay;
return err; return err;
@ -176,9 +198,7 @@ replay:
static void nfnetlink_rcv(struct sk_buff *skb) static void nfnetlink_rcv(struct sk_buff *skb)
{ {
nfnl_lock();
netlink_rcv_skb(skb, &nfnetlink_rcv_msg); netlink_rcv_skb(skb, &nfnetlink_rcv_msg);
nfnl_unlock();
} }
static int __net_init nfnetlink_net_init(struct net *net) static int __net_init nfnetlink_net_init(struct net *net)