Commit e34d5c1a authored by Pablo Neira Ayuso's avatar Pablo Neira Ayuso Committed by pablo

netfilter: conntrack: replace notify chain by function pointer

This patch removes the notify chain infrastructure and replace it
by a simple function pointer. This issue has been mentioned in the
mailing list several times: the use of the notify chain adds
too much overhead for something that is only used by ctnetlink.

This patch also changes nfnetlink_send(). It seems that gfp_any()
returns GFP_KERNEL for user-context request, like those via
ctnetlink, inside the RCU read-side section which is not valid.
Using GFP_KERNEL is also evil since netlink may schedule(),
this leads to "scheduling while atomic" bug reports.
Signed-off-by: default avatarPablo Neira Ayuso <pablo@netfilter.org>
parent 17e6e4ea
...@@ -75,7 +75,7 @@ extern int nfnetlink_subsys_unregister(const struct nfnetlink_subsystem *n); ...@@ -75,7 +75,7 @@ extern int nfnetlink_subsys_unregister(const struct nfnetlink_subsystem *n);
extern int nfnetlink_has_listeners(unsigned int group); extern int nfnetlink_has_listeners(unsigned int group);
extern int nfnetlink_send(struct sk_buff *skb, u32 pid, unsigned group, extern int nfnetlink_send(struct sk_buff *skb, u32 pid, unsigned group,
int echo); int echo, gfp_t flags);
extern void nfnetlink_set_err(u32 pid, u32 group, int error); extern void nfnetlink_set_err(u32 pid, u32 group, int error);
extern int nfnetlink_unicast(struct sk_buff *skb, u_int32_t pid, int flags); extern int nfnetlink_unicast(struct sk_buff *skb, u_int32_t pid, int flags);
......
...@@ -6,7 +6,6 @@ ...@@ -6,7 +6,6 @@
#define _NF_CONNTRACK_ECACHE_H #define _NF_CONNTRACK_ECACHE_H
#include <net/netfilter/nf_conntrack.h> #include <net/netfilter/nf_conntrack.h>
#include <linux/notifier.h>
#include <linux/interrupt.h> #include <linux/interrupt.h>
#include <net/net_namespace.h> #include <net/net_namespace.h>
#include <net/netfilter/nf_conntrack_expect.h> #include <net/netfilter/nf_conntrack_expect.h>
...@@ -69,9 +68,13 @@ struct nf_ct_event { ...@@ -69,9 +68,13 @@ struct nf_ct_event {
int report; int report;
}; };
extern struct atomic_notifier_head nf_conntrack_chain; struct nf_ct_event_notifier {
extern int nf_conntrack_register_notifier(struct notifier_block *nb); int (*fcn)(unsigned int events, struct nf_ct_event *item);
extern int nf_conntrack_unregister_notifier(struct notifier_block *nb); };
extern struct nf_ct_event_notifier *nf_conntrack_event_cb;
extern int nf_conntrack_register_notifier(struct nf_ct_event_notifier *nb);
extern void nf_conntrack_unregister_notifier(struct nf_ct_event_notifier *nb);
extern void nf_ct_deliver_cached_events(const struct nf_conn *ct); extern void nf_ct_deliver_cached_events(const struct nf_conn *ct);
extern void __nf_ct_event_cache_init(struct nf_conn *ct); extern void __nf_ct_event_cache_init(struct nf_conn *ct);
...@@ -97,13 +100,23 @@ nf_conntrack_event_report(enum ip_conntrack_events event, ...@@ -97,13 +100,23 @@ nf_conntrack_event_report(enum ip_conntrack_events event,
u32 pid, u32 pid,
int report) int report)
{ {
struct nf_ct_event_notifier *notify;
rcu_read_lock();
notify = rcu_dereference(nf_conntrack_event_cb);
if (notify == NULL)
goto out_unlock;
if (nf_ct_is_confirmed(ct) && !nf_ct_is_dying(ct)) {
struct nf_ct_event item = { struct nf_ct_event item = {
.ct = ct, .ct = ct,
.pid = pid, .pid = pid,
.report = report .report = report
}; };
if (nf_ct_is_confirmed(ct) && !nf_ct_is_dying(ct)) notify->fcn(event, &item);
atomic_notifier_call_chain(&nf_conntrack_chain, event, &item); }
out_unlock:
rcu_read_unlock();
} }
static inline void static inline void
...@@ -118,9 +131,13 @@ struct nf_exp_event { ...@@ -118,9 +131,13 @@ struct nf_exp_event {
int report; int report;
}; };
extern struct atomic_notifier_head nf_ct_expect_chain; struct nf_exp_event_notifier {
extern int nf_ct_expect_register_notifier(struct notifier_block *nb); int (*fcn)(unsigned int events, struct nf_exp_event *item);
extern int nf_ct_expect_unregister_notifier(struct notifier_block *nb); };
extern struct nf_exp_event_notifier *nf_expect_event_cb;
extern int nf_ct_expect_register_notifier(struct nf_exp_event_notifier *nb);
extern void nf_ct_expect_unregister_notifier(struct nf_exp_event_notifier *nb);
static inline void static inline void
nf_ct_expect_event_report(enum ip_conntrack_expect_events event, nf_ct_expect_event_report(enum ip_conntrack_expect_events event,
...@@ -128,12 +145,23 @@ nf_ct_expect_event_report(enum ip_conntrack_expect_events event, ...@@ -128,12 +145,23 @@ nf_ct_expect_event_report(enum ip_conntrack_expect_events event,
u32 pid, u32 pid,
int report) int report)
{ {
struct nf_exp_event_notifier *notify;
rcu_read_lock();
notify = rcu_dereference(nf_expect_event_cb);
if (notify == NULL)
goto out_unlock;
{
struct nf_exp_event item = { struct nf_exp_event item = {
.exp = exp, .exp = exp,
.pid = pid, .pid = pid,
.report = report .report = report
}; };
atomic_notifier_call_chain(&nf_ct_expect_chain, event, &item); notify->fcn(event, &item);
}
out_unlock:
rcu_read_unlock();
} }
static inline void static inline void
......
...@@ -16,24 +16,32 @@ ...@@ -16,24 +16,32 @@
#include <linux/stddef.h> #include <linux/stddef.h>
#include <linux/err.h> #include <linux/err.h>
#include <linux/percpu.h> #include <linux/percpu.h>
#include <linux/notifier.h>
#include <linux/kernel.h> #include <linux/kernel.h>
#include <linux/netdevice.h> #include <linux/netdevice.h>
#include <net/netfilter/nf_conntrack.h> #include <net/netfilter/nf_conntrack.h>
#include <net/netfilter/nf_conntrack_core.h> #include <net/netfilter/nf_conntrack_core.h>
ATOMIC_NOTIFIER_HEAD(nf_conntrack_chain); static DEFINE_MUTEX(nf_ct_ecache_mutex);
EXPORT_SYMBOL_GPL(nf_conntrack_chain);
ATOMIC_NOTIFIER_HEAD(nf_ct_expect_chain); struct nf_ct_event_notifier *nf_conntrack_event_cb __read_mostly;
EXPORT_SYMBOL_GPL(nf_ct_expect_chain); EXPORT_SYMBOL_GPL(nf_conntrack_event_cb);
struct nf_exp_event_notifier *nf_expect_event_cb __read_mostly;
EXPORT_SYMBOL_GPL(nf_expect_event_cb);
/* deliver cached events and clear cache entry - must be called with locally /* deliver cached events and clear cache entry - must be called with locally
* disabled softirqs */ * disabled softirqs */
static inline void static inline void
__nf_ct_deliver_cached_events(struct nf_conntrack_ecache *ecache) __nf_ct_deliver_cached_events(struct nf_conntrack_ecache *ecache)
{ {
struct nf_ct_event_notifier *notify;
rcu_read_lock();
notify = rcu_dereference(nf_conntrack_event_cb);
if (notify == NULL)
goto out_unlock;
if (nf_ct_is_confirmed(ecache->ct) && !nf_ct_is_dying(ecache->ct) if (nf_ct_is_confirmed(ecache->ct) && !nf_ct_is_dying(ecache->ct)
&& ecache->events) { && ecache->events) {
struct nf_ct_event item = { struct nf_ct_event item = {
...@@ -42,14 +50,15 @@ __nf_ct_deliver_cached_events(struct nf_conntrack_ecache *ecache) ...@@ -42,14 +50,15 @@ __nf_ct_deliver_cached_events(struct nf_conntrack_ecache *ecache)
.report = 0 .report = 0
}; };
atomic_notifier_call_chain(&nf_conntrack_chain, notify->fcn(ecache->events, &item);
ecache->events,
&item);
} }
ecache->events = 0; ecache->events = 0;
nf_ct_put(ecache->ct); nf_ct_put(ecache->ct);
ecache->ct = NULL; ecache->ct = NULL;
out_unlock:
rcu_read_unlock();
} }
/* Deliver all cached events for a particular conntrack. This is called /* Deliver all cached events for a particular conntrack. This is called
...@@ -111,26 +120,68 @@ void nf_conntrack_ecache_fini(struct net *net) ...@@ -111,26 +120,68 @@ void nf_conntrack_ecache_fini(struct net *net)
free_percpu(net->ct.ecache); free_percpu(net->ct.ecache);
} }
int nf_conntrack_register_notifier(struct notifier_block *nb) int nf_conntrack_register_notifier(struct nf_ct_event_notifier *new)
{ {
return atomic_notifier_chain_register(&nf_conntrack_chain, nb); int ret = 0;
struct nf_ct_event_notifier *notify;
mutex_lock(&nf_ct_ecache_mutex);
notify = rcu_dereference(nf_conntrack_event_cb);
if (notify != NULL) {
ret = -EBUSY;
goto out_unlock;
}
rcu_assign_pointer(nf_conntrack_event_cb, new);
mutex_unlock(&nf_ct_ecache_mutex);
return ret;
out_unlock:
mutex_unlock(&nf_ct_ecache_mutex);
return ret;
} }
EXPORT_SYMBOL_GPL(nf_conntrack_register_notifier); EXPORT_SYMBOL_GPL(nf_conntrack_register_notifier);
int nf_conntrack_unregister_notifier(struct notifier_block *nb) void nf_conntrack_unregister_notifier(struct nf_ct_event_notifier *new)
{ {
return atomic_notifier_chain_unregister(&nf_conntrack_chain, nb); struct nf_ct_event_notifier *notify;
mutex_lock(&nf_ct_ecache_mutex);
notify = rcu_dereference(nf_conntrack_event_cb);
BUG_ON(notify != new);
rcu_assign_pointer(nf_conntrack_event_cb, NULL);
mutex_unlock(&nf_ct_ecache_mutex);
} }
EXPORT_SYMBOL_GPL(nf_conntrack_unregister_notifier); EXPORT_SYMBOL_GPL(nf_conntrack_unregister_notifier);
int nf_ct_expect_register_notifier(struct notifier_block *nb) int nf_ct_expect_register_notifier(struct nf_exp_event_notifier *new)
{ {
return atomic_notifier_chain_register(&nf_ct_expect_chain, nb); int ret = 0;
struct nf_exp_event_notifier *notify;
mutex_lock(&nf_ct_ecache_mutex);
notify = rcu_dereference(nf_expect_event_cb);
if (notify != NULL) {
ret = -EBUSY;
goto out_unlock;
}
rcu_assign_pointer(nf_expect_event_cb, new);
mutex_unlock(&nf_ct_ecache_mutex);
return ret;
out_unlock:
mutex_unlock(&nf_ct_ecache_mutex);
return ret;
} }
EXPORT_SYMBOL_GPL(nf_ct_expect_register_notifier); EXPORT_SYMBOL_GPL(nf_ct_expect_register_notifier);
int nf_ct_expect_unregister_notifier(struct notifier_block *nb) void nf_ct_expect_unregister_notifier(struct nf_exp_event_notifier *new)
{ {
return atomic_notifier_chain_unregister(&nf_ct_expect_chain, nb); struct nf_exp_event_notifier *notify;
mutex_lock(&nf_ct_ecache_mutex);
notify = rcu_dereference(nf_expect_event_cb);
BUG_ON(notify != new);
rcu_assign_pointer(nf_expect_event_cb, NULL);
mutex_unlock(&nf_ct_ecache_mutex);
} }
EXPORT_SYMBOL_GPL(nf_ct_expect_unregister_notifier); EXPORT_SYMBOL_GPL(nf_ct_expect_unregister_notifier);
...@@ -27,7 +27,6 @@ ...@@ -27,7 +27,6 @@
#include <linux/netlink.h> #include <linux/netlink.h>
#include <linux/spinlock.h> #include <linux/spinlock.h>
#include <linux/interrupt.h> #include <linux/interrupt.h>
#include <linux/notifier.h>
#include <linux/netfilter.h> #include <linux/netfilter.h>
#include <net/netlink.h> #include <net/netlink.h>
...@@ -454,13 +453,12 @@ ctnetlink_nlmsg_size(const struct nf_conn *ct) ...@@ -454,13 +453,12 @@ ctnetlink_nlmsg_size(const struct nf_conn *ct)
; ;
} }
static int ctnetlink_conntrack_event(struct notifier_block *this, static int
unsigned long events, void *ptr) ctnetlink_conntrack_event(unsigned int events, struct nf_ct_event *item)
{ {
struct nlmsghdr *nlh; struct nlmsghdr *nlh;
struct nfgenmsg *nfmsg; struct nfgenmsg *nfmsg;
struct nlattr *nest_parms; struct nlattr *nest_parms;
struct nf_ct_event *item = (struct nf_ct_event *)ptr;
struct nf_conn *ct = item->ct; struct nf_conn *ct = item->ct;
struct sk_buff *skb; struct sk_buff *skb;
unsigned int type; unsigned int type;
...@@ -468,7 +466,7 @@ static int ctnetlink_conntrack_event(struct notifier_block *this, ...@@ -468,7 +466,7 @@ static int ctnetlink_conntrack_event(struct notifier_block *this,
/* ignore our fake conntrack entry */ /* ignore our fake conntrack entry */
if (ct == &nf_conntrack_untracked) if (ct == &nf_conntrack_untracked)
return NOTIFY_DONE; return 0;
if (events & IPCT_DESTROY) { if (events & IPCT_DESTROY) {
type = IPCTNL_MSG_CT_DELETE; type = IPCTNL_MSG_CT_DELETE;
...@@ -481,10 +479,10 @@ static int ctnetlink_conntrack_event(struct notifier_block *this, ...@@ -481,10 +479,10 @@ static int ctnetlink_conntrack_event(struct notifier_block *this,
type = IPCTNL_MSG_CT_NEW; type = IPCTNL_MSG_CT_NEW;
group = NFNLGRP_CONNTRACK_UPDATE; group = NFNLGRP_CONNTRACK_UPDATE;
} else } else
return NOTIFY_DONE; return 0;
if (!item->report && !nfnetlink_has_listeners(group)) if (!item->report && !nfnetlink_has_listeners(group))
return NOTIFY_DONE; return 0;
skb = nlmsg_new(ctnetlink_nlmsg_size(ct), GFP_ATOMIC); skb = nlmsg_new(ctnetlink_nlmsg_size(ct), GFP_ATOMIC);
if (skb == NULL) if (skb == NULL)
...@@ -560,8 +558,8 @@ static int ctnetlink_conntrack_event(struct notifier_block *this, ...@@ -560,8 +558,8 @@ static int ctnetlink_conntrack_event(struct notifier_block *this,
rcu_read_unlock(); rcu_read_unlock();
nlmsg_end(skb, nlh); nlmsg_end(skb, nlh);
nfnetlink_send(skb, item->pid, group, item->report); nfnetlink_send(skb, item->pid, group, item->report, GFP_ATOMIC);
return NOTIFY_DONE; return 0;
nla_put_failure: nla_put_failure:
rcu_read_unlock(); rcu_read_unlock();
...@@ -570,7 +568,7 @@ static int ctnetlink_conntrack_event(struct notifier_block *this, ...@@ -570,7 +568,7 @@ static int ctnetlink_conntrack_event(struct notifier_block *this,
kfree_skb(skb); kfree_skb(skb);
errout: errout:
nfnetlink_set_err(0, group, -ENOBUFS); nfnetlink_set_err(0, group, -ENOBUFS);
return NOTIFY_DONE; return 0;
} }
#endif /* CONFIG_NF_CONNTRACK_EVENTS */ #endif /* CONFIG_NF_CONNTRACK_EVENTS */
...@@ -1507,12 +1505,11 @@ ctnetlink_exp_fill_info(struct sk_buff *skb, u32 pid, u32 seq, ...@@ -1507,12 +1505,11 @@ ctnetlink_exp_fill_info(struct sk_buff *skb, u32 pid, u32 seq,
} }
#ifdef CONFIG_NF_CONNTRACK_EVENTS #ifdef CONFIG_NF_CONNTRACK_EVENTS
static int ctnetlink_expect_event(struct notifier_block *this, static int
unsigned long events, void *ptr) ctnetlink_expect_event(unsigned int events, struct nf_exp_event *item)
{ {
struct nlmsghdr *nlh; struct nlmsghdr *nlh;
struct nfgenmsg *nfmsg; struct nfgenmsg *nfmsg;
struct nf_exp_event *item = (struct nf_exp_event *)ptr;
struct nf_conntrack_expect *exp = item->exp; struct nf_conntrack_expect *exp = item->exp;
struct sk_buff *skb; struct sk_buff *skb;
unsigned int type; unsigned int type;
...@@ -1522,11 +1519,11 @@ static int ctnetlink_expect_event(struct notifier_block *this, ...@@ -1522,11 +1519,11 @@ static int ctnetlink_expect_event(struct notifier_block *this,
type = IPCTNL_MSG_EXP_NEW; type = IPCTNL_MSG_EXP_NEW;
flags = NLM_F_CREATE|NLM_F_EXCL; flags = NLM_F_CREATE|NLM_F_EXCL;
} else } else
return NOTIFY_DONE; return 0;
if (!item->report && if (!item->report &&
!nfnetlink_has_listeners(NFNLGRP_CONNTRACK_EXP_NEW)) !nfnetlink_has_listeners(NFNLGRP_CONNTRACK_EXP_NEW))
return NOTIFY_DONE; return 0;
skb = nlmsg_new(NLMSG_DEFAULT_SIZE, GFP_ATOMIC); skb = nlmsg_new(NLMSG_DEFAULT_SIZE, GFP_ATOMIC);
if (skb == NULL) if (skb == NULL)
...@@ -1548,8 +1545,9 @@ static int ctnetlink_expect_event(struct notifier_block *this, ...@@ -1548,8 +1545,9 @@ static int ctnetlink_expect_event(struct notifier_block *this,
rcu_read_unlock(); rcu_read_unlock();
nlmsg_end(skb, nlh); nlmsg_end(skb, nlh);
nfnetlink_send(skb, item->pid, NFNLGRP_CONNTRACK_EXP_NEW, item->report); nfnetlink_send(skb, item->pid, NFNLGRP_CONNTRACK_EXP_NEW,
return NOTIFY_DONE; item->report, GFP_ATOMIC);
return 0;
nla_put_failure: nla_put_failure:
rcu_read_unlock(); rcu_read_unlock();
...@@ -1558,7 +1556,7 @@ static int ctnetlink_expect_event(struct notifier_block *this, ...@@ -1558,7 +1556,7 @@ static int ctnetlink_expect_event(struct notifier_block *this,
kfree_skb(skb); kfree_skb(skb);
errout: errout:
nfnetlink_set_err(0, 0, -ENOBUFS); nfnetlink_set_err(0, 0, -ENOBUFS);
return NOTIFY_DONE; return 0;
} }
#endif #endif
static int ctnetlink_exp_done(struct netlink_callback *cb) static int ctnetlink_exp_done(struct netlink_callback *cb)
...@@ -1864,12 +1862,12 @@ ctnetlink_new_expect(struct sock *ctnl, struct sk_buff *skb, ...@@ -1864,12 +1862,12 @@ ctnetlink_new_expect(struct sock *ctnl, struct sk_buff *skb,
} }
#ifdef CONFIG_NF_CONNTRACK_EVENTS #ifdef CONFIG_NF_CONNTRACK_EVENTS
static struct notifier_block ctnl_notifier = { static struct nf_ct_event_notifier ctnl_notifier = {
.notifier_call = ctnetlink_conntrack_event, .fcn = ctnetlink_conntrack_event,
}; };
static struct notifier_block ctnl_notifier_exp = { static struct nf_exp_event_notifier ctnl_notifier_exp = {
.notifier_call = ctnetlink_expect_event, .fcn = ctnetlink_expect_event,
}; };
#endif #endif
......
...@@ -107,9 +107,10 @@ int nfnetlink_has_listeners(unsigned int group) ...@@ -107,9 +107,10 @@ int nfnetlink_has_listeners(unsigned int group)
} }
EXPORT_SYMBOL_GPL(nfnetlink_has_listeners); EXPORT_SYMBOL_GPL(nfnetlink_has_listeners);
int nfnetlink_send(struct sk_buff *skb, u32 pid, unsigned group, int echo) int nfnetlink_send(struct sk_buff *skb, u32 pid,
unsigned group, int echo, gfp_t flags)
{ {
return nlmsg_notify(nfnl, skb, pid, group, echo, gfp_any()); return nlmsg_notify(nfnl, skb, pid, group, echo, flags);
} }
EXPORT_SYMBOL_GPL(nfnetlink_send); EXPORT_SYMBOL_GPL(nfnetlink_send);
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment