Commit 70e9942f authored by Pablo Neira Ayuso's avatar Pablo Neira Ayuso

netfilter: nf_conntrack: make event callback registration per-netns

This patch fixes an oops that can be triggered following this recipe:

0) make sure nf_conntrack_netlink and nf_conntrack_ipv4 are loaded.
1) container is started.
2) connect to it via lxc-console.
3) generate some traffic with the container to create some conntrack
   entries in its table.
4) stop the container: you hit one oops because the conntrack table
   cleanup tries to report the destroy event to user-space but the
   per-netns nfnetlink socket has already gone (as the nfnetlink
   socket is per-netns but event callback registration is global).

To fix this situation, we make the ctnl_notifier per-netns so the
callback is registered/unregistered if the container is
created/destroyed.

Alex Bligh and Alexey Dobriyan originally proposed one small patch to
check if the nfnetlink socket is gone in nfnetlink_has_listeners,
but this is a very visited path for events, thus, it may reduce
performance and it looks a bit hackish to check for the nfnetlink
socket only to workaround this situation. As a result, I decided
to follow the bigger path choice, which seems to look nicer to me.

Cc: Alexey Dobriyan <adobriyan@gmail.com>
Reported-by: default avatarAlex Bligh <alex@alex.org.uk>
Signed-off-by: default avatarPablo Neira Ayuso <pablo@netfilter.org>
parent 5e2afba4
...@@ -67,18 +67,18 @@ struct nf_ct_event_notifier { ...@@ -67,18 +67,18 @@ struct nf_ct_event_notifier {
int (*fcn)(unsigned int events, struct nf_ct_event *item); int (*fcn)(unsigned int events, struct nf_ct_event *item);
}; };
extern struct nf_ct_event_notifier __rcu *nf_conntrack_event_cb; extern int nf_conntrack_register_notifier(struct net *net, struct nf_ct_event_notifier *nb);
extern int nf_conntrack_register_notifier(struct nf_ct_event_notifier *nb); extern void nf_conntrack_unregister_notifier(struct net *net, 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(struct nf_conn *ct); extern void nf_ct_deliver_cached_events(struct nf_conn *ct);
static inline void static inline void
nf_conntrack_event_cache(enum ip_conntrack_events event, struct nf_conn *ct) nf_conntrack_event_cache(enum ip_conntrack_events event, struct nf_conn *ct)
{ {
struct net *net = nf_ct_net(ct);
struct nf_conntrack_ecache *e; struct nf_conntrack_ecache *e;
if (nf_conntrack_event_cb == NULL) if (net->ct.nf_conntrack_event_cb == NULL)
return; return;
e = nf_ct_ecache_find(ct); e = nf_ct_ecache_find(ct);
...@@ -95,11 +95,12 @@ nf_conntrack_eventmask_report(unsigned int eventmask, ...@@ -95,11 +95,12 @@ nf_conntrack_eventmask_report(unsigned int eventmask,
int report) int report)
{ {
int ret = 0; int ret = 0;
struct net *net = nf_ct_net(ct);
struct nf_ct_event_notifier *notify; struct nf_ct_event_notifier *notify;
struct nf_conntrack_ecache *e; struct nf_conntrack_ecache *e;
rcu_read_lock(); rcu_read_lock();
notify = rcu_dereference(nf_conntrack_event_cb); notify = rcu_dereference(net->ct.nf_conntrack_event_cb);
if (notify == NULL) if (notify == NULL)
goto out_unlock; goto out_unlock;
...@@ -164,9 +165,8 @@ struct nf_exp_event_notifier { ...@@ -164,9 +165,8 @@ struct nf_exp_event_notifier {
int (*fcn)(unsigned int events, struct nf_exp_event *item); int (*fcn)(unsigned int events, struct nf_exp_event *item);
}; };
extern struct nf_exp_event_notifier __rcu *nf_expect_event_cb; extern int nf_ct_expect_register_notifier(struct net *net, struct nf_exp_event_notifier *nb);
extern int nf_ct_expect_register_notifier(struct nf_exp_event_notifier *nb); extern void nf_ct_expect_unregister_notifier(struct net *net, 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,
...@@ -174,11 +174,12 @@ nf_ct_expect_event_report(enum ip_conntrack_expect_events event, ...@@ -174,11 +174,12 @@ nf_ct_expect_event_report(enum ip_conntrack_expect_events event,
u32 pid, u32 pid,
int report) int report)
{ {
struct net *net = nf_ct_exp_net(exp);
struct nf_exp_event_notifier *notify; struct nf_exp_event_notifier *notify;
struct nf_conntrack_ecache *e; struct nf_conntrack_ecache *e;
rcu_read_lock(); rcu_read_lock();
notify = rcu_dereference(nf_expect_event_cb); notify = rcu_dereference(net->ct.nf_expect_event_cb);
if (notify == NULL) if (notify == NULL)
goto out_unlock; goto out_unlock;
......
...@@ -18,6 +18,8 @@ struct netns_ct { ...@@ -18,6 +18,8 @@ struct netns_ct {
struct hlist_nulls_head unconfirmed; struct hlist_nulls_head unconfirmed;
struct hlist_nulls_head dying; struct hlist_nulls_head dying;
struct ip_conntrack_stat __percpu *stat; struct ip_conntrack_stat __percpu *stat;
struct nf_ct_event_notifier __rcu *nf_conntrack_event_cb;
struct nf_exp_event_notifier __rcu *nf_expect_event_cb;
int sysctl_events; int sysctl_events;
unsigned int sysctl_events_retry_timeout; unsigned int sysctl_events_retry_timeout;
int sysctl_acct; int sysctl_acct;
......
...@@ -27,22 +27,17 @@ ...@@ -27,22 +27,17 @@
static DEFINE_MUTEX(nf_ct_ecache_mutex); static DEFINE_MUTEX(nf_ct_ecache_mutex);
struct nf_ct_event_notifier __rcu *nf_conntrack_event_cb __read_mostly;
EXPORT_SYMBOL_GPL(nf_conntrack_event_cb);
struct nf_exp_event_notifier __rcu *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 */
void nf_ct_deliver_cached_events(struct nf_conn *ct) void nf_ct_deliver_cached_events(struct nf_conn *ct)
{ {
struct net *net = nf_ct_net(ct);
unsigned long events; unsigned long events;
struct nf_ct_event_notifier *notify; struct nf_ct_event_notifier *notify;
struct nf_conntrack_ecache *e; struct nf_conntrack_ecache *e;
rcu_read_lock(); rcu_read_lock();
notify = rcu_dereference(nf_conntrack_event_cb); notify = rcu_dereference(net->ct.nf_conntrack_event_cb);
if (notify == NULL) if (notify == NULL)
goto out_unlock; goto out_unlock;
...@@ -83,19 +78,20 @@ void nf_ct_deliver_cached_events(struct nf_conn *ct) ...@@ -83,19 +78,20 @@ void nf_ct_deliver_cached_events(struct nf_conn *ct)
} }
EXPORT_SYMBOL_GPL(nf_ct_deliver_cached_events); EXPORT_SYMBOL_GPL(nf_ct_deliver_cached_events);
int nf_conntrack_register_notifier(struct nf_ct_event_notifier *new) int nf_conntrack_register_notifier(struct net *net,
struct nf_ct_event_notifier *new)
{ {
int ret = 0; int ret = 0;
struct nf_ct_event_notifier *notify; struct nf_ct_event_notifier *notify;
mutex_lock(&nf_ct_ecache_mutex); mutex_lock(&nf_ct_ecache_mutex);
notify = rcu_dereference_protected(nf_conntrack_event_cb, notify = rcu_dereference_protected(net->ct.nf_conntrack_event_cb,
lockdep_is_held(&nf_ct_ecache_mutex)); lockdep_is_held(&nf_ct_ecache_mutex));
if (notify != NULL) { if (notify != NULL) {
ret = -EBUSY; ret = -EBUSY;
goto out_unlock; goto out_unlock;
} }
RCU_INIT_POINTER(nf_conntrack_event_cb, new); RCU_INIT_POINTER(net->ct.nf_conntrack_event_cb, new);
mutex_unlock(&nf_ct_ecache_mutex); mutex_unlock(&nf_ct_ecache_mutex);
return ret; return ret;
...@@ -105,32 +101,34 @@ int nf_conntrack_register_notifier(struct nf_ct_event_notifier *new) ...@@ -105,32 +101,34 @@ int nf_conntrack_register_notifier(struct nf_ct_event_notifier *new)
} }
EXPORT_SYMBOL_GPL(nf_conntrack_register_notifier); EXPORT_SYMBOL_GPL(nf_conntrack_register_notifier);
void nf_conntrack_unregister_notifier(struct nf_ct_event_notifier *new) void nf_conntrack_unregister_notifier(struct net *net,
struct nf_ct_event_notifier *new)
{ {
struct nf_ct_event_notifier *notify; struct nf_ct_event_notifier *notify;
mutex_lock(&nf_ct_ecache_mutex); mutex_lock(&nf_ct_ecache_mutex);
notify = rcu_dereference_protected(nf_conntrack_event_cb, notify = rcu_dereference_protected(net->ct.nf_conntrack_event_cb,
lockdep_is_held(&nf_ct_ecache_mutex)); lockdep_is_held(&nf_ct_ecache_mutex));
BUG_ON(notify != new); BUG_ON(notify != new);
RCU_INIT_POINTER(nf_conntrack_event_cb, NULL); RCU_INIT_POINTER(net->ct.nf_conntrack_event_cb, NULL);
mutex_unlock(&nf_ct_ecache_mutex); 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 nf_exp_event_notifier *new) int nf_ct_expect_register_notifier(struct net *net,
struct nf_exp_event_notifier *new)
{ {
int ret = 0; int ret = 0;
struct nf_exp_event_notifier *notify; struct nf_exp_event_notifier *notify;
mutex_lock(&nf_ct_ecache_mutex); mutex_lock(&nf_ct_ecache_mutex);
notify = rcu_dereference_protected(nf_expect_event_cb, notify = rcu_dereference_protected(net->ct.nf_expect_event_cb,
lockdep_is_held(&nf_ct_ecache_mutex)); lockdep_is_held(&nf_ct_ecache_mutex));
if (notify != NULL) { if (notify != NULL) {
ret = -EBUSY; ret = -EBUSY;
goto out_unlock; goto out_unlock;
} }
RCU_INIT_POINTER(nf_expect_event_cb, new); RCU_INIT_POINTER(net->ct.nf_expect_event_cb, new);
mutex_unlock(&nf_ct_ecache_mutex); mutex_unlock(&nf_ct_ecache_mutex);
return ret; return ret;
...@@ -140,15 +138,16 @@ int nf_ct_expect_register_notifier(struct nf_exp_event_notifier *new) ...@@ -140,15 +138,16 @@ int nf_ct_expect_register_notifier(struct nf_exp_event_notifier *new)
} }
EXPORT_SYMBOL_GPL(nf_ct_expect_register_notifier); EXPORT_SYMBOL_GPL(nf_ct_expect_register_notifier);
void nf_ct_expect_unregister_notifier(struct nf_exp_event_notifier *new) void nf_ct_expect_unregister_notifier(struct net *net,
struct nf_exp_event_notifier *new)
{ {
struct nf_exp_event_notifier *notify; struct nf_exp_event_notifier *notify;
mutex_lock(&nf_ct_ecache_mutex); mutex_lock(&nf_ct_ecache_mutex);
notify = rcu_dereference_protected(nf_expect_event_cb, notify = rcu_dereference_protected(net->ct.nf_expect_event_cb,
lockdep_is_held(&nf_ct_ecache_mutex)); lockdep_is_held(&nf_ct_ecache_mutex));
BUG_ON(notify != new); BUG_ON(notify != new);
RCU_INIT_POINTER(nf_expect_event_cb, NULL); RCU_INIT_POINTER(net->ct.nf_expect_event_cb, NULL);
mutex_unlock(&nf_ct_ecache_mutex); mutex_unlock(&nf_ct_ecache_mutex);
} }
EXPORT_SYMBOL_GPL(nf_ct_expect_unregister_notifier); EXPORT_SYMBOL_GPL(nf_ct_expect_unregister_notifier);
......
...@@ -4,7 +4,7 @@ ...@@ -4,7 +4,7 @@
* (C) 2001 by Jay Schulist <jschlst@samba.org> * (C) 2001 by Jay Schulist <jschlst@samba.org>
* (C) 2002-2006 by Harald Welte <laforge@gnumonks.org> * (C) 2002-2006 by Harald Welte <laforge@gnumonks.org>
* (C) 2003 by Patrick Mchardy <kaber@trash.net> * (C) 2003 by Patrick Mchardy <kaber@trash.net>
* (C) 2005-2008 by Pablo Neira Ayuso <pablo@netfilter.org> * (C) 2005-2011 by Pablo Neira Ayuso <pablo@netfilter.org>
* *
* Initial connection tracking via netlink development funded and * Initial connection tracking via netlink development funded and
* generally made possible by Network Robots, Inc. (www.networkrobots.com) * generally made possible by Network Robots, Inc. (www.networkrobots.com)
...@@ -2163,6 +2163,54 @@ MODULE_ALIAS("ip_conntrack_netlink"); ...@@ -2163,6 +2163,54 @@ MODULE_ALIAS("ip_conntrack_netlink");
MODULE_ALIAS_NFNL_SUBSYS(NFNL_SUBSYS_CTNETLINK); MODULE_ALIAS_NFNL_SUBSYS(NFNL_SUBSYS_CTNETLINK);
MODULE_ALIAS_NFNL_SUBSYS(NFNL_SUBSYS_CTNETLINK_EXP); MODULE_ALIAS_NFNL_SUBSYS(NFNL_SUBSYS_CTNETLINK_EXP);
static int __net_init ctnetlink_net_init(struct net *net)
{
#ifdef CONFIG_NF_CONNTRACK_EVENTS
int ret;
ret = nf_conntrack_register_notifier(net, &ctnl_notifier);
if (ret < 0) {
pr_err("ctnetlink_init: cannot register notifier.\n");
goto err_out;
}
ret = nf_ct_expect_register_notifier(net, &ctnl_notifier_exp);
if (ret < 0) {
pr_err("ctnetlink_init: cannot expect register notifier.\n");
goto err_unreg_notifier;
}
#endif
return 0;
#ifdef CONFIG_NF_CONNTRACK_EVENTS
err_unreg_notifier:
nf_conntrack_unregister_notifier(net, &ctnl_notifier);
err_out:
return ret;
#endif
}
static void ctnetlink_net_exit(struct net *net)
{
#ifdef CONFIG_NF_CONNTRACK_EVENTS
nf_ct_expect_unregister_notifier(net, &ctnl_notifier_exp);
nf_conntrack_unregister_notifier(net, &ctnl_notifier);
#endif
}
static void __net_exit ctnetlink_net_exit_batch(struct list_head *net_exit_list)
{
struct net *net;
list_for_each_entry(net, net_exit_list, exit_list)
ctnetlink_net_exit(net);
}
static struct pernet_operations ctnetlink_net_ops = {
.init = ctnetlink_net_init,
.exit_batch = ctnetlink_net_exit_batch,
};
static int __init ctnetlink_init(void) static int __init ctnetlink_init(void)
{ {
int ret; int ret;
...@@ -2180,28 +2228,15 @@ static int __init ctnetlink_init(void) ...@@ -2180,28 +2228,15 @@ static int __init ctnetlink_init(void)
goto err_unreg_subsys; goto err_unreg_subsys;
} }
#ifdef CONFIG_NF_CONNTRACK_EVENTS if (register_pernet_subsys(&ctnetlink_net_ops)) {
ret = nf_conntrack_register_notifier(&ctnl_notifier); pr_err("ctnetlink_init: cannot register pernet operations\n");
if (ret < 0) {
pr_err("ctnetlink_init: cannot register notifier.\n");
goto err_unreg_exp_subsys; goto err_unreg_exp_subsys;
} }
ret = nf_ct_expect_register_notifier(&ctnl_notifier_exp);
if (ret < 0) {
pr_err("ctnetlink_init: cannot expect register notifier.\n");
goto err_unreg_notifier;
}
#endif
return 0; return 0;
#ifdef CONFIG_NF_CONNTRACK_EVENTS
err_unreg_notifier:
nf_conntrack_unregister_notifier(&ctnl_notifier);
err_unreg_exp_subsys: err_unreg_exp_subsys:
nfnetlink_subsys_unregister(&ctnl_exp_subsys); nfnetlink_subsys_unregister(&ctnl_exp_subsys);
#endif
err_unreg_subsys: err_unreg_subsys:
nfnetlink_subsys_unregister(&ctnl_subsys); nfnetlink_subsys_unregister(&ctnl_subsys);
err_out: err_out:
...@@ -2213,11 +2248,7 @@ static void __exit ctnetlink_exit(void) ...@@ -2213,11 +2248,7 @@ static void __exit ctnetlink_exit(void)
pr_info("ctnetlink: unregistering from nfnetlink.\n"); pr_info("ctnetlink: unregistering from nfnetlink.\n");
nf_ct_remove_userspace_expectations(); nf_ct_remove_userspace_expectations();
#ifdef CONFIG_NF_CONNTRACK_EVENTS unregister_pernet_subsys(&ctnetlink_net_ops);
nf_ct_expect_unregister_notifier(&ctnl_notifier_exp);
nf_conntrack_unregister_notifier(&ctnl_notifier);
#endif
nfnetlink_subsys_unregister(&ctnl_exp_subsys); nfnetlink_subsys_unregister(&ctnl_exp_subsys);
nfnetlink_subsys_unregister(&ctnl_subsys); nfnetlink_subsys_unregister(&ctnl_subsys);
} }
......
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