struct nf_conntrack_helper {
struct hlist_node hnode; /* Internal use. */
+ struct rcu_head rcu;
+
char name[NF_CT_HELPER_NAME_LEN]; /* name of the module */
- refcount_t refcnt;
struct module *me; /* pointer to self */
struct nf_conntrack_expect_policy expect_policy[NF_CT_MAX_EXPECT_CLASSES];
+ refcount_t ct_refcnt;
+
/* Tuple of things we will help (compared against server response) */
struct nf_conntrack_tuple tuple;
/* Function to call when data passes; return verdict, or -1 to
invalidate. */
- int (*help)(struct sk_buff *skb,
- unsigned int protoff,
- struct nf_conn *ct,
- enum ip_conntrack_info conntrackinfo);
+ int __rcu (*help)(struct sk_buff *skb, unsigned int protoff,
+ struct nf_conn *ct,
+ enum ip_conntrack_info conntrackinfo);
void (*destroy)(struct nf_conn *ct);
return (void *)help->data;
}
+static inline void nf_ct_help_put(const struct nf_conn *ct)
+{
+ struct nf_conntrack_helper *helper;
+ struct nf_conn_help *help;
+
+ help = nfct_help(ct);
+ if (!help)
+ return;
+
+ helper = rcu_dereference(help->helper);
+ if (helper && refcount_dec_and_test(&helper->ct_refcnt))
+ kfree_rcu(helper, rcu);
+}
+
int nf_conntrack_helper_init(void);
void nf_conntrack_helper_fini(void);
nat_hook->remove_nat_bysrc(ct);
}
+ nf_ct_help_put(ct);
nf_ct_timeout_put(ct);
rcu_read_unlock();
assign_helper = rcu_dereference(exp->assign_helper);
if (assign_helper) {
help = nf_ct_helper_ext_add(ct, GFP_ATOMIC);
- if (help)
+ if (help && refcount_inc_not_zero(&assign_helper->ct_refcnt))
rcu_assign_pointer(help->helper, assign_helper);
}
#endif
if (h != NULL && !try_module_get(h->me))
h = NULL;
- if (h != NULL && !refcount_inc_not_zero(&h->refcnt)) {
+ if (h != NULL && !refcount_inc_not_zero(&h->ct_refcnt)) {
module_put(h->me);
h = NULL;
}
void nf_conntrack_helper_put(struct nf_conntrack_helper *helper)
{
- refcount_dec(&helper->refcnt);
module_put(helper->me);
+ if (refcount_dec_and_test(&helper->ct_refcnt))
+ kfree_rcu(helper, rcu);
}
EXPORT_SYMBOL_GPL(nf_conntrack_helper_put);
help = nfct_help(ct);
if (helper == NULL) {
- if (help)
+ if (help) {
+ struct nf_conntrack_helper *tmp = rcu_dereference(help->helper);
+
RCU_INIT_POINTER(help->helper, NULL);
+ if (tmp && refcount_dec_and_test(&tmp->ct_refcnt))
+ kfree_rcu(tmp, rcu);
+ }
return 0;
}
*/
struct nf_conntrack_helper *tmp = rcu_dereference(help->helper);
- if (tmp && tmp->help != helper->help) {
- RCU_INIT_POINTER(help->helper, NULL);
+ if (tmp) {
+ if (tmp->help != helper->help) {
+ RCU_INIT_POINTER(help->helper, NULL);
+ if (refcount_dec_and_test(&tmp->ct_refcnt))
+ kfree_rcu(tmp, rcu);
+ }
return 0;
}
}
- rcu_assign_pointer(help->helper, helper);
+ if (refcount_inc_not_zero(&helper->ct_refcnt))
+ rcu_assign_pointer(help->helper, helper);
return 0;
}
EXPORT_SYMBOL_GPL(__nf_ct_try_assign_helper);
-/* appropriate ct lock protecting must be taken by caller */
-static int unhelp(struct nf_conn *ct, void *me)
-{
- struct nf_conn_help *help = nfct_help(ct);
-
- if (help && rcu_dereference_raw(help->helper) == me) {
- nf_conntrack_event(IPCT_HELPER, ct);
- RCU_INIT_POINTER(help->helper, NULL);
- }
-
- /* We are not intended to delete this conntrack. */
- return 0;
-}
-
void nf_ct_helper_destroy(struct nf_conn *ct)
{
struct nf_conn_help *help = nfct_help(ct);
}
}
}
- refcount_set(&me->refcnt, 1);
+ refcount_set(&me->ct_refcnt, 1);
hlist_add_head_rcu(&me->hnode, &nf_ct_helper_hash[h]);
nf_ct_helper_count++;
out:
nf_ct_helper_count--;
mutex_unlock(&nf_ct_helper_mutex);
+ /* This helper is going away, disable it. */
+ rcu_assign_pointer(me->help, NULL);
+
/* Make sure every nothing is still using the helper unless its a
* connection in the hash.
*/
synchronize_rcu();
nf_ct_expect_iterate_destroy(expect_iter_me, me);
- nf_ct_iterate_destroy(unhelp, me);
- /* nf_ct_iterate_destroy() does an unconditional synchronize_rcu() as
- * last step, this ensures rcu readers of exp->helper are done.
- * No need for another synchronize_rcu() here.
- */
- kfree(me);
+ if (refcount_dec_and_test(&me->ct_refcnt))
+ kfree_rcu(me, rcu);
}
EXPORT_SYMBOL_GPL(nf_conntrack_helper_unregister);
helper->tuple.dst.protonum = protonum;
helper->tuple.src.u.all = htons(spec_port);
- helper->help = help;
+ rcu_assign_pointer(helper->help, help);
helper->from_nlattr = from_nlattr;
helper->me = module;
snprintf(helper->nat_mod_name, sizeof(helper->nat_mod_name),
if (nla_put_string(skb, CTA_HELP_NAME, helper->name))
goto nla_put_failure;
- if (helper->to_nlattr)
+ if (rcu_access_pointer(helper->help) &&
+ helper->to_nlattr)
helper->to_nlattr(skb, ct);
nla_nest_end(skb, nest_helper);
if (err < 0)
return err;
+ rcu_read_lock();
/* don't change helper of sibling connections */
if (ct->master) {
/* If we try to change the helper to the same thing twice,
*/
err = -EBUSY;
if (help) {
- rcu_read_lock();
helper = rcu_dereference(help->helper);
if (helper && !strcmp(helper->name, helpname))
err = 0;
- rcu_read_unlock();
}
-
+ rcu_read_unlock();
return err;
}
- if (!strcmp(helpname, "")) {
- if (help && help->helper) {
+ if (!strcmp(helpname, "") && help) {
+ helper = rcu_dereference(help->helper);
+ if (helper) {
/* we had a helper before ... */
nf_ct_remove_expectations(ct);
RCU_INIT_POINTER(help->helper, NULL);
+ if (refcount_dec_and_test(&helper->ct_refcnt))
+ kfree_rcu(helper, rcu);
}
-
+ rcu_read_unlock();
return 0;
}
- rcu_read_lock();
helper = __nf_conntrack_helper_find(helpname, nf_ct_l3num(ct),
nf_ct_protonum(ct));
if (helper == NULL) {
if (help) {
if (rcu_access_pointer(help->helper) == helper) {
/* update private helper data if allowed. */
- if (helper->from_nlattr)
+ if (rcu_access_pointer(helper->help) &&
+ helper->from_nlattr)
helper->from_nlattr(helpinfo, ct);
err = 0;
} else
goto err2;
}
/* set private helper data if allowed. */
- if (helper->from_nlattr)
+ if (rcu_access_pointer(helper->help) &&
+ helper->from_nlattr)
helper->from_nlattr(helpinfo, ct);
/* disable helper auto-assignment for this entry */
ct->status |= IPS_HELPER;
+ if (!refcount_inc_not_zero(&helper->ct_refcnt)) {
+ err = -ENOENT;
+ goto err2;
+ }
RCU_INIT_POINTER(help->helper, helper);
}
}
int nf_ct_helper(struct sk_buff *skb, struct nf_conn *ct,
enum ip_conntrack_info ctinfo, u16 proto)
{
+ int (*helper_cb)(struct sk_buff *skb, unsigned int protoff,
+ struct nf_conn *ct,
+ enum ip_conntrack_info conntrackinfo);
const struct nf_conntrack_helper *helper;
const struct nf_conn_help *help;
unsigned int protoff;
if (helper->tuple.dst.protonum != proto)
return NF_ACCEPT;
- err = helper->help(skb, protoff, ct, ctinfo);
+ helper_cb = rcu_dereference(helper->help);
+ if (!helper_cb)
+ return NF_ACCEPT;
+
+ err = helper_cb(skb, protoff, ct, ctinfo);
if (err != NF_ACCEPT)
return err;
struct sk_buff *skb,
const struct nf_hook_state *state)
{
+ int (*helper_cb)(struct sk_buff *skb, unsigned int protoff,
+ struct nf_conn *ct,
+ enum ip_conntrack_info conntrackinfo);
const struct nf_conn_help *help;
enum ip_conntrack_info ctinfo;
unsigned int protoff;
/* rcu_read_lock()ed by nf_hook */
helper = rcu_dereference(help->helper);
if (helper) {
- ret = helper->help(skb,
- protoff,
- ct, ctinfo);
- if (ret != NF_ACCEPT)
- return ret;
+ helper_cb = rcu_dereference(helper->help);
+ if (helper_cb) {
+ ret = helper_cb(skb, protoff,
+ ct, ctinfo);
+ if (ret != NF_ACCEPT)
+ return ret;
+ }
}
}
tuple.dst.protonum != cur->tuple.dst.protonum))
continue;
- if (refcount_dec_if_one(&cur->refcnt)) {
- found = true;
- nf_conntrack_helper_unregister(cur);
-
- list_del(&nlcth->list);
- kfree(nlcth);
- } else {
- ret = -EBUSY;
- }
+ found = true;
+ nf_conntrack_helper_unregister(cur);
+
+ list_del(&nlcth->list);
+ kfree(nlcth);
}
/* Make sure we return success if we flush and there is no helpers */
{
struct nft_ct_helper_obj *priv = nft_obj_data(obj);
- nf_queue_nf_hook_drop(ctx->net);
if (priv->helper4)
nf_conntrack_helper_put(priv->helper4);
if (priv->helper6)
return;
help = nf_ct_helper_ext_add(ct, GFP_ATOMIC);
- if (help) {
+ if (help && refcount_inc_not_zero(&to_assign->ct_refcnt)) {
rcu_assign_pointer(help->helper, to_assign);
set_bit(IPS_HELPER_BIT, &ct->status);
struct nf_conn_help *help;
if (ct) {
- if (info->helper[0])
- nf_queue_nf_hook_drop(par->net);
-
help = nfct_help(ct);
xt_ct_put_helper(help);