Commit 23416e23 authored by David S. Miller's avatar David S. Miller

Merge git://git.kernel.org/pub/scm/linux/kernel/git/pablo/nf

Pablo Neira Ayuso says:

====================
Netfilter/IPVS fixes for net

The following patchset contains Netfilter/IPVS fixes for your net tree,
they are:

1) When using IPVS in direct-routing mode, normal traffic from the LVS
   host to a back-end server is sometimes incorrectly NATed on the way
   back into the LVS host. Patch to fix this from Julian Anastasov.

2) Calm down clang compilation warning in ctnetlink due to type
   mismatch, from Matthias Kaehlcke.

3) Do not re-setup NAT for conntracks that are already confirmed, this
   is fixing a problem that was introduced in the previous nf-next batch.
   Patch from Liping Zhang.

4) Do not allow conntrack helper removal from userspace cthelper
   infrastructure if already in used. This comes with an initial patch
   to introduce nf_conntrack_helper_put() that is required by this fix.
   From Liping Zhang.

5) Zero the pad when copying data to userspace, otherwise iptables fails
   to remove rules. This is a follow up on the patchset that sorts out
   the internal match/target structure pointer leak to userspace. Patch
   from the same author, Willem de Bruijn. This also comes with a build
   failure when CONFIG_COMPAT is not on, coming in the last patch of
   this series.

6) SYNPROXY crashes with conntrack entries that are created via
   ctnetlink, more specifically via conntrackd state sync. Patch from
   Eric Leblond.

7) RCU safe iteration on set element dumping in nf_tables, from
   Liping Zhang.

8) Missing sanitization of immediate date for the bitwise and cmp
   expressions in nf_tables.

9) Refcounting logic for chain and objects from set elements does not
   integrate into the nf_tables 2-phase commit protocol.

10) Missing sanitization of target verdict in ebtables arpreply target,
    from Gao Feng.
====================
Signed-off-by: default avatarDavid S. Miller <davem@davemloft.net>
parents 8b4822de 751a9c76
...@@ -294,7 +294,7 @@ int xt_match_to_user(const struct xt_entry_match *m, ...@@ -294,7 +294,7 @@ int xt_match_to_user(const struct xt_entry_match *m,
int xt_target_to_user(const struct xt_entry_target *t, int xt_target_to_user(const struct xt_entry_target *t,
struct xt_entry_target __user *u); struct xt_entry_target __user *u);
int xt_data_to_user(void __user *dst, const void *src, int xt_data_to_user(void __user *dst, const void *src,
int usersize, int size); int usersize, int size, int aligned_size);
void *xt_copy_counters_from_user(const void __user *user, unsigned int len, void *xt_copy_counters_from_user(const void __user *user, unsigned int len,
struct xt_counters_info *info, bool compat); struct xt_counters_info *info, bool compat);
......
...@@ -125,4 +125,9 @@ extern unsigned int ebt_do_table(struct sk_buff *skb, ...@@ -125,4 +125,9 @@ extern unsigned int ebt_do_table(struct sk_buff *skb,
/* True if the target is not a standard target */ /* True if the target is not a standard target */
#define INVALID_TARGET (info->target < -NUM_STANDARD_TARGETS || info->target >= 0) #define INVALID_TARGET (info->target < -NUM_STANDARD_TARGETS || info->target >= 0)
static inline bool ebt_invalid_target(int target)
{
return (target < -NUM_STANDARD_TARGETS || target >= 0);
}
#endif #endif
...@@ -9,6 +9,7 @@ ...@@ -9,6 +9,7 @@
#ifndef _NF_CONNTRACK_HELPER_H #ifndef _NF_CONNTRACK_HELPER_H
#define _NF_CONNTRACK_HELPER_H #define _NF_CONNTRACK_HELPER_H
#include <linux/refcount.h>
#include <net/netfilter/nf_conntrack.h> #include <net/netfilter/nf_conntrack.h>
#include <net/netfilter/nf_conntrack_extend.h> #include <net/netfilter/nf_conntrack_extend.h>
#include <net/netfilter/nf_conntrack_expect.h> #include <net/netfilter/nf_conntrack_expect.h>
...@@ -26,6 +27,7 @@ struct nf_conntrack_helper { ...@@ -26,6 +27,7 @@ struct nf_conntrack_helper {
struct hlist_node hnode; /* Internal use. */ struct hlist_node hnode; /* Internal use. */
char name[NF_CT_HELPER_NAME_LEN]; /* name of the module */ char name[NF_CT_HELPER_NAME_LEN]; /* name of the module */
refcount_t refcnt;
struct module *me; /* pointer to self */ struct module *me; /* pointer to self */
const struct nf_conntrack_expect_policy *expect_policy; const struct nf_conntrack_expect_policy *expect_policy;
...@@ -79,6 +81,8 @@ struct nf_conntrack_helper *__nf_conntrack_helper_find(const char *name, ...@@ -79,6 +81,8 @@ struct nf_conntrack_helper *__nf_conntrack_helper_find(const char *name,
struct nf_conntrack_helper *nf_conntrack_helper_try_module_get(const char *name, struct nf_conntrack_helper *nf_conntrack_helper_try_module_get(const char *name,
u16 l3num, u16 l3num,
u8 protonum); u8 protonum);
void nf_conntrack_helper_put(struct nf_conntrack_helper *helper);
void nf_ct_helper_init(struct nf_conntrack_helper *helper, void nf_ct_helper_init(struct nf_conntrack_helper *helper,
u16 l3num, u16 protonum, const char *name, u16 l3num, u16 protonum, const char *name,
u16 default_port, u16 spec_port, u32 id, u16 default_port, u16 spec_port, u32 id,
......
...@@ -176,7 +176,7 @@ struct nft_data_desc { ...@@ -176,7 +176,7 @@ struct nft_data_desc {
int nft_data_init(const struct nft_ctx *ctx, int nft_data_init(const struct nft_ctx *ctx,
struct nft_data *data, unsigned int size, struct nft_data *data, unsigned int size,
struct nft_data_desc *desc, const struct nlattr *nla); struct nft_data_desc *desc, const struct nlattr *nla);
void nft_data_uninit(const struct nft_data *data, enum nft_data_types type); void nft_data_release(const struct nft_data *data, enum nft_data_types type);
int nft_data_dump(struct sk_buff *skb, int attr, const struct nft_data *data, int nft_data_dump(struct sk_buff *skb, int attr, const struct nft_data *data,
enum nft_data_types type, unsigned int len); enum nft_data_types type, unsigned int len);
......
...@@ -68,6 +68,9 @@ static int ebt_arpreply_tg_check(const struct xt_tgchk_param *par) ...@@ -68,6 +68,9 @@ static int ebt_arpreply_tg_check(const struct xt_tgchk_param *par)
if (e->ethproto != htons(ETH_P_ARP) || if (e->ethproto != htons(ETH_P_ARP) ||
e->invflags & EBT_IPROTO) e->invflags & EBT_IPROTO)
return -EINVAL; return -EINVAL;
if (ebt_invalid_target(info->target))
return -EINVAL;
return 0; return 0;
} }
......
...@@ -1373,7 +1373,8 @@ static inline int ebt_obj_to_user(char __user *um, const char *_name, ...@@ -1373,7 +1373,8 @@ static inline int ebt_obj_to_user(char __user *um, const char *_name,
strlcpy(name, _name, sizeof(name)); strlcpy(name, _name, sizeof(name));
if (copy_to_user(um, name, EBT_FUNCTION_MAXNAMELEN) || if (copy_to_user(um, name, EBT_FUNCTION_MAXNAMELEN) ||
put_user(datasize, (int __user *)(um + EBT_FUNCTION_MAXNAMELEN)) || put_user(datasize, (int __user *)(um + EBT_FUNCTION_MAXNAMELEN)) ||
xt_data_to_user(um + entrysize, data, usersize, datasize)) xt_data_to_user(um + entrysize, data, usersize, datasize,
XT_ALIGN(datasize)))
return -EFAULT; return -EFAULT;
return 0; return 0;
...@@ -1658,7 +1659,8 @@ static int compat_match_to_user(struct ebt_entry_match *m, void __user **dstptr, ...@@ -1658,7 +1659,8 @@ static int compat_match_to_user(struct ebt_entry_match *m, void __user **dstptr,
if (match->compat_to_user(cm->data, m->data)) if (match->compat_to_user(cm->data, m->data))
return -EFAULT; return -EFAULT;
} else { } else {
if (xt_data_to_user(cm->data, m->data, match->usersize, msize)) if (xt_data_to_user(cm->data, m->data, match->usersize, msize,
COMPAT_XT_ALIGN(msize)))
return -EFAULT; return -EFAULT;
} }
...@@ -1687,7 +1689,8 @@ static int compat_target_to_user(struct ebt_entry_target *t, ...@@ -1687,7 +1689,8 @@ static int compat_target_to_user(struct ebt_entry_target *t,
if (target->compat_to_user(cm->data, t->data)) if (target->compat_to_user(cm->data, t->data))
return -EFAULT; return -EFAULT;
} else { } else {
if (xt_data_to_user(cm->data, t->data, target->usersize, tsize)) if (xt_data_to_user(cm->data, t->data, target->usersize, tsize,
COMPAT_XT_ALIGN(tsize)))
return -EFAULT; return -EFAULT;
} }
......
...@@ -849,10 +849,8 @@ static int handle_response_icmp(int af, struct sk_buff *skb, ...@@ -849,10 +849,8 @@ static int handle_response_icmp(int af, struct sk_buff *skb,
{ {
unsigned int verdict = NF_DROP; unsigned int verdict = NF_DROP;
if (IP_VS_FWD_METHOD(cp) != 0) { if (IP_VS_FWD_METHOD(cp) != IP_VS_CONN_F_MASQ)
pr_err("shouldn't reach here, because the box is on the " goto ignore_cp;
"half connection in the tun/dr module.\n");
}
/* Ensure the checksum is correct */ /* Ensure the checksum is correct */
if (!skb_csum_unnecessary(skb) && ip_vs_checksum_complete(skb, ihl)) { if (!skb_csum_unnecessary(skb) && ip_vs_checksum_complete(skb, ihl)) {
...@@ -886,6 +884,8 @@ static int handle_response_icmp(int af, struct sk_buff *skb, ...@@ -886,6 +884,8 @@ static int handle_response_icmp(int af, struct sk_buff *skb,
ip_vs_notrack(skb); ip_vs_notrack(skb);
else else
ip_vs_update_conntrack(skb, cp, 0); ip_vs_update_conntrack(skb, cp, 0);
ignore_cp:
verdict = NF_ACCEPT; verdict = NF_ACCEPT;
out: out:
...@@ -1385,8 +1385,11 @@ ip_vs_out(struct netns_ipvs *ipvs, unsigned int hooknum, struct sk_buff *skb, in ...@@ -1385,8 +1385,11 @@ ip_vs_out(struct netns_ipvs *ipvs, unsigned int hooknum, struct sk_buff *skb, in
*/ */
cp = pp->conn_out_get(ipvs, af, skb, &iph); cp = pp->conn_out_get(ipvs, af, skb, &iph);
if (likely(cp)) if (likely(cp)) {
if (IP_VS_FWD_METHOD(cp) != IP_VS_CONN_F_MASQ)
goto ignore_cp;
return handle_response(af, skb, pd, cp, &iph, hooknum); return handle_response(af, skb, pd, cp, &iph, hooknum);
}
/* Check for real-server-started requests */ /* Check for real-server-started requests */
if (atomic_read(&ipvs->conn_out_counter)) { if (atomic_read(&ipvs->conn_out_counter)) {
...@@ -1444,9 +1447,15 @@ ip_vs_out(struct netns_ipvs *ipvs, unsigned int hooknum, struct sk_buff *skb, in ...@@ -1444,9 +1447,15 @@ ip_vs_out(struct netns_ipvs *ipvs, unsigned int hooknum, struct sk_buff *skb, in
} }
} }
} }
out:
IP_VS_DBG_PKT(12, af, pp, skb, iph.off, IP_VS_DBG_PKT(12, af, pp, skb, iph.off,
"ip_vs_out: packet continues traversal as normal"); "ip_vs_out: packet continues traversal as normal");
return NF_ACCEPT; return NF_ACCEPT;
ignore_cp:
__ip_vs_conn_put(cp);
goto out;
} }
/* /*
......
...@@ -174,6 +174,10 @@ nf_conntrack_helper_try_module_get(const char *name, u16 l3num, u8 protonum) ...@@ -174,6 +174,10 @@ nf_conntrack_helper_try_module_get(const char *name, u16 l3num, u8 protonum)
#endif #endif
if (h != NULL && !try_module_get(h->me)) if (h != NULL && !try_module_get(h->me))
h = NULL; h = NULL;
if (h != NULL && !refcount_inc_not_zero(&h->refcnt)) {
module_put(h->me);
h = NULL;
}
rcu_read_unlock(); rcu_read_unlock();
...@@ -181,6 +185,13 @@ nf_conntrack_helper_try_module_get(const char *name, u16 l3num, u8 protonum) ...@@ -181,6 +185,13 @@ nf_conntrack_helper_try_module_get(const char *name, u16 l3num, u8 protonum)
} }
EXPORT_SYMBOL_GPL(nf_conntrack_helper_try_module_get); EXPORT_SYMBOL_GPL(nf_conntrack_helper_try_module_get);
void nf_conntrack_helper_put(struct nf_conntrack_helper *helper)
{
refcount_dec(&helper->refcnt);
module_put(helper->me);
}
EXPORT_SYMBOL_GPL(nf_conntrack_helper_put);
struct nf_conn_help * struct nf_conn_help *
nf_ct_helper_ext_add(struct nf_conn *ct, nf_ct_helper_ext_add(struct nf_conn *ct,
struct nf_conntrack_helper *helper, gfp_t gfp) struct nf_conntrack_helper *helper, gfp_t gfp)
...@@ -417,6 +428,7 @@ int nf_conntrack_helper_register(struct nf_conntrack_helper *me) ...@@ -417,6 +428,7 @@ int nf_conntrack_helper_register(struct nf_conntrack_helper *me)
} }
} }
} }
refcount_set(&me->refcnt, 1);
hlist_add_head_rcu(&me->hnode, &nf_ct_helper_hash[h]); hlist_add_head_rcu(&me->hnode, &nf_ct_helper_hash[h]);
nf_ct_helper_count++; nf_ct_helper_count++;
out: out:
......
...@@ -45,6 +45,8 @@ ...@@ -45,6 +45,8 @@
#include <net/netfilter/nf_conntrack_zones.h> #include <net/netfilter/nf_conntrack_zones.h>
#include <net/netfilter/nf_conntrack_timestamp.h> #include <net/netfilter/nf_conntrack_timestamp.h>
#include <net/netfilter/nf_conntrack_labels.h> #include <net/netfilter/nf_conntrack_labels.h>
#include <net/netfilter/nf_conntrack_seqadj.h>
#include <net/netfilter/nf_conntrack_synproxy.h>
#ifdef CONFIG_NF_NAT_NEEDED #ifdef CONFIG_NF_NAT_NEEDED
#include <net/netfilter/nf_nat_core.h> #include <net/netfilter/nf_nat_core.h>
#include <net/netfilter/nf_nat_l4proto.h> #include <net/netfilter/nf_nat_l4proto.h>
...@@ -1007,9 +1009,8 @@ static const struct nla_policy tuple_nla_policy[CTA_TUPLE_MAX+1] = { ...@@ -1007,9 +1009,8 @@ static const struct nla_policy tuple_nla_policy[CTA_TUPLE_MAX+1] = {
static int static int
ctnetlink_parse_tuple(const struct nlattr * const cda[], ctnetlink_parse_tuple(const struct nlattr * const cda[],
struct nf_conntrack_tuple *tuple, struct nf_conntrack_tuple *tuple, u32 type,
enum ctattr_type type, u_int8_t l3num, u_int8_t l3num, struct nf_conntrack_zone *zone)
struct nf_conntrack_zone *zone)
{ {
struct nlattr *tb[CTA_TUPLE_MAX+1]; struct nlattr *tb[CTA_TUPLE_MAX+1];
int err; int err;
...@@ -1828,6 +1829,8 @@ ctnetlink_create_conntrack(struct net *net, ...@@ -1828,6 +1829,8 @@ ctnetlink_create_conntrack(struct net *net,
nf_ct_tstamp_ext_add(ct, GFP_ATOMIC); nf_ct_tstamp_ext_add(ct, GFP_ATOMIC);
nf_ct_ecache_ext_add(ct, 0, 0, GFP_ATOMIC); nf_ct_ecache_ext_add(ct, 0, 0, GFP_ATOMIC);
nf_ct_labels_ext_add(ct); nf_ct_labels_ext_add(ct);
nfct_seqadj_ext_add(ct);
nfct_synproxy_ext_add(ct);
/* we must add conntrack extensions before confirmation. */ /* we must add conntrack extensions before confirmation. */
ct->status |= IPS_CONFIRMED; ct->status |= IPS_CONFIRMED;
...@@ -2447,7 +2450,7 @@ static struct nfnl_ct_hook ctnetlink_glue_hook = { ...@@ -2447,7 +2450,7 @@ static struct nfnl_ct_hook ctnetlink_glue_hook = {
static int ctnetlink_exp_dump_tuple(struct sk_buff *skb, static int ctnetlink_exp_dump_tuple(struct sk_buff *skb,
const struct nf_conntrack_tuple *tuple, const struct nf_conntrack_tuple *tuple,
enum ctattr_expect type) u32 type)
{ {
struct nlattr *nest_parms; struct nlattr *nest_parms;
......
...@@ -409,6 +409,10 @@ nf_nat_setup_info(struct nf_conn *ct, ...@@ -409,6 +409,10 @@ nf_nat_setup_info(struct nf_conn *ct,
{ {
struct nf_conntrack_tuple curr_tuple, new_tuple; struct nf_conntrack_tuple curr_tuple, new_tuple;
/* Can't setup nat info for confirmed ct. */
if (nf_ct_is_confirmed(ct))
return NF_ACCEPT;
NF_CT_ASSERT(maniptype == NF_NAT_MANIP_SRC || NF_CT_ASSERT(maniptype == NF_NAT_MANIP_SRC ||
maniptype == NF_NAT_MANIP_DST); maniptype == NF_NAT_MANIP_DST);
BUG_ON(nf_nat_initialized(ct, maniptype)); BUG_ON(nf_nat_initialized(ct, maniptype));
......
...@@ -3367,35 +3367,50 @@ static int nf_tables_dump_setelem(const struct nft_ctx *ctx, ...@@ -3367,35 +3367,50 @@ static int nf_tables_dump_setelem(const struct nft_ctx *ctx,
return nf_tables_fill_setelem(args->skb, set, elem); return nf_tables_fill_setelem(args->skb, set, elem);
} }
struct nft_set_dump_ctx {
const struct nft_set *set;
struct nft_ctx ctx;
};
static int nf_tables_dump_set(struct sk_buff *skb, struct netlink_callback *cb) static int nf_tables_dump_set(struct sk_buff *skb, struct netlink_callback *cb)
{ {
struct nft_set_dump_ctx *dump_ctx = cb->data;
struct net *net = sock_net(skb->sk); struct net *net = sock_net(skb->sk);
u8 genmask = nft_genmask_cur(net); struct nft_af_info *afi;
struct nft_table *table;
struct nft_set *set; struct nft_set *set;
struct nft_set_dump_args args; struct nft_set_dump_args args;
struct nft_ctx ctx; bool set_found = false;
struct nlattr *nla[NFTA_SET_ELEM_LIST_MAX + 1];
struct nfgenmsg *nfmsg; struct nfgenmsg *nfmsg;
struct nlmsghdr *nlh; struct nlmsghdr *nlh;
struct nlattr *nest; struct nlattr *nest;
u32 portid, seq; u32 portid, seq;
int event, err; int event;
err = nlmsg_parse(cb->nlh, sizeof(struct nfgenmsg), nla, rcu_read_lock();
NFTA_SET_ELEM_LIST_MAX, nft_set_elem_list_policy, list_for_each_entry_rcu(afi, &net->nft.af_info, list) {
NULL); if (afi != dump_ctx->ctx.afi)
if (err < 0) continue;
return err;
err = nft_ctx_init_from_elemattr(&ctx, net, cb->skb, cb->nlh, list_for_each_entry_rcu(table, &afi->tables, list) {
(void *)nla, genmask); if (table != dump_ctx->ctx.table)
if (err < 0) continue;
return err;
set = nf_tables_set_lookup(ctx.table, nla[NFTA_SET_ELEM_LIST_SET], list_for_each_entry_rcu(set, &table->sets, list) {
genmask); if (set == dump_ctx->set) {
if (IS_ERR(set)) set_found = true;
return PTR_ERR(set); break;
}
}
break;
}
break;
}
if (!set_found) {
rcu_read_unlock();
return -ENOENT;
}
event = nfnl_msg_type(NFNL_SUBSYS_NFTABLES, NFT_MSG_NEWSETELEM); event = nfnl_msg_type(NFNL_SUBSYS_NFTABLES, NFT_MSG_NEWSETELEM);
portid = NETLINK_CB(cb->skb).portid; portid = NETLINK_CB(cb->skb).portid;
...@@ -3407,11 +3422,11 @@ static int nf_tables_dump_set(struct sk_buff *skb, struct netlink_callback *cb) ...@@ -3407,11 +3422,11 @@ static int nf_tables_dump_set(struct sk_buff *skb, struct netlink_callback *cb)
goto nla_put_failure; goto nla_put_failure;
nfmsg = nlmsg_data(nlh); nfmsg = nlmsg_data(nlh);
nfmsg->nfgen_family = ctx.afi->family; nfmsg->nfgen_family = afi->family;
nfmsg->version = NFNETLINK_V0; nfmsg->version = NFNETLINK_V0;
nfmsg->res_id = htons(ctx.net->nft.base_seq & 0xffff); nfmsg->res_id = htons(net->nft.base_seq & 0xffff);
if (nla_put_string(skb, NFTA_SET_ELEM_LIST_TABLE, ctx.table->name)) if (nla_put_string(skb, NFTA_SET_ELEM_LIST_TABLE, table->name))
goto nla_put_failure; goto nla_put_failure;
if (nla_put_string(skb, NFTA_SET_ELEM_LIST_SET, set->name)) if (nla_put_string(skb, NFTA_SET_ELEM_LIST_SET, set->name))
goto nla_put_failure; goto nla_put_failure;
...@@ -3422,12 +3437,13 @@ static int nf_tables_dump_set(struct sk_buff *skb, struct netlink_callback *cb) ...@@ -3422,12 +3437,13 @@ static int nf_tables_dump_set(struct sk_buff *skb, struct netlink_callback *cb)
args.cb = cb; args.cb = cb;
args.skb = skb; args.skb = skb;
args.iter.genmask = nft_genmask_cur(ctx.net); args.iter.genmask = nft_genmask_cur(net);
args.iter.skip = cb->args[0]; args.iter.skip = cb->args[0];
args.iter.count = 0; args.iter.count = 0;
args.iter.err = 0; args.iter.err = 0;
args.iter.fn = nf_tables_dump_setelem; args.iter.fn = nf_tables_dump_setelem;
set->ops->walk(&ctx, set, &args.iter); set->ops->walk(&dump_ctx->ctx, set, &args.iter);
rcu_read_unlock();
nla_nest_end(skb, nest); nla_nest_end(skb, nest);
nlmsg_end(skb, nlh); nlmsg_end(skb, nlh);
...@@ -3441,9 +3457,16 @@ static int nf_tables_dump_set(struct sk_buff *skb, struct netlink_callback *cb) ...@@ -3441,9 +3457,16 @@ static int nf_tables_dump_set(struct sk_buff *skb, struct netlink_callback *cb)
return skb->len; return skb->len;
nla_put_failure: nla_put_failure:
rcu_read_unlock();
return -ENOSPC; return -ENOSPC;
} }
static int nf_tables_dump_set_done(struct netlink_callback *cb)
{
kfree(cb->data);
return 0;
}
static int nf_tables_getsetelem(struct net *net, struct sock *nlsk, static int nf_tables_getsetelem(struct net *net, struct sock *nlsk,
struct sk_buff *skb, const struct nlmsghdr *nlh, struct sk_buff *skb, const struct nlmsghdr *nlh,
const struct nlattr * const nla[]) const struct nlattr * const nla[])
...@@ -3465,7 +3488,18 @@ static int nf_tables_getsetelem(struct net *net, struct sock *nlsk, ...@@ -3465,7 +3488,18 @@ static int nf_tables_getsetelem(struct net *net, struct sock *nlsk,
if (nlh->nlmsg_flags & NLM_F_DUMP) { if (nlh->nlmsg_flags & NLM_F_DUMP) {
struct netlink_dump_control c = { struct netlink_dump_control c = {
.dump = nf_tables_dump_set, .dump = nf_tables_dump_set,
.done = nf_tables_dump_set_done,
}; };
struct nft_set_dump_ctx *dump_ctx;
dump_ctx = kmalloc(sizeof(*dump_ctx), GFP_KERNEL);
if (!dump_ctx)
return -ENOMEM;
dump_ctx->set = set;
dump_ctx->ctx = ctx;
c.data = dump_ctx;
return netlink_dump_start(nlsk, skb, nlh, &c); return netlink_dump_start(nlsk, skb, nlh, &c);
} }
return -EOPNOTSUPP; return -EOPNOTSUPP;
...@@ -3593,9 +3627,9 @@ void nft_set_elem_destroy(const struct nft_set *set, void *elem, ...@@ -3593,9 +3627,9 @@ void nft_set_elem_destroy(const struct nft_set *set, void *elem,
{ {
struct nft_set_ext *ext = nft_set_elem_ext(set, elem); struct nft_set_ext *ext = nft_set_elem_ext(set, elem);
nft_data_uninit(nft_set_ext_key(ext), NFT_DATA_VALUE); nft_data_release(nft_set_ext_key(ext), NFT_DATA_VALUE);
if (nft_set_ext_exists(ext, NFT_SET_EXT_DATA)) if (nft_set_ext_exists(ext, NFT_SET_EXT_DATA))
nft_data_uninit(nft_set_ext_data(ext), set->dtype); nft_data_release(nft_set_ext_data(ext), set->dtype);
if (destroy_expr && nft_set_ext_exists(ext, NFT_SET_EXT_EXPR)) if (destroy_expr && nft_set_ext_exists(ext, NFT_SET_EXT_EXPR))
nf_tables_expr_destroy(NULL, nft_set_ext_expr(ext)); nf_tables_expr_destroy(NULL, nft_set_ext_expr(ext));
if (nft_set_ext_exists(ext, NFT_SET_EXT_OBJREF)) if (nft_set_ext_exists(ext, NFT_SET_EXT_OBJREF))
...@@ -3604,6 +3638,18 @@ void nft_set_elem_destroy(const struct nft_set *set, void *elem, ...@@ -3604,6 +3638,18 @@ void nft_set_elem_destroy(const struct nft_set *set, void *elem,
} }
EXPORT_SYMBOL_GPL(nft_set_elem_destroy); EXPORT_SYMBOL_GPL(nft_set_elem_destroy);
/* Only called from commit path, nft_set_elem_deactivate() already deals with
* the refcounting from the preparation phase.
*/
static void nf_tables_set_elem_destroy(const struct nft_set *set, void *elem)
{
struct nft_set_ext *ext = nft_set_elem_ext(set, elem);
if (nft_set_ext_exists(ext, NFT_SET_EXT_EXPR))
nf_tables_expr_destroy(NULL, nft_set_ext_expr(ext));
kfree(elem);
}
static int nft_setelem_parse_flags(const struct nft_set *set, static int nft_setelem_parse_flags(const struct nft_set *set,
const struct nlattr *attr, u32 *flags) const struct nlattr *attr, u32 *flags)
{ {
...@@ -3815,9 +3861,9 @@ static int nft_add_set_elem(struct nft_ctx *ctx, struct nft_set *set, ...@@ -3815,9 +3861,9 @@ static int nft_add_set_elem(struct nft_ctx *ctx, struct nft_set *set,
kfree(elem.priv); kfree(elem.priv);
err3: err3:
if (nla[NFTA_SET_ELEM_DATA] != NULL) if (nla[NFTA_SET_ELEM_DATA] != NULL)
nft_data_uninit(&data, d2.type); nft_data_release(&data, d2.type);
err2: err2:
nft_data_uninit(&elem.key.val, d1.type); nft_data_release(&elem.key.val, d1.type);
err1: err1:
return err; return err;
} }
...@@ -3862,6 +3908,53 @@ static int nf_tables_newsetelem(struct net *net, struct sock *nlsk, ...@@ -3862,6 +3908,53 @@ static int nf_tables_newsetelem(struct net *net, struct sock *nlsk,
return err; return err;
} }
/**
* nft_data_hold - hold a nft_data item
*
* @data: struct nft_data to release
* @type: type of data
*
* Hold a nft_data item. NFT_DATA_VALUE types can be silently discarded,
* NFT_DATA_VERDICT bumps the reference to chains in case of NFT_JUMP and
* NFT_GOTO verdicts. This function must be called on active data objects
* from the second phase of the commit protocol.
*/
static void nft_data_hold(const struct nft_data *data, enum nft_data_types type)
{
if (type == NFT_DATA_VERDICT) {
switch (data->verdict.code) {
case NFT_JUMP:
case NFT_GOTO:
data->verdict.chain->use++;
break;
}
}
}
static void nft_set_elem_activate(const struct net *net,
const struct nft_set *set,
struct nft_set_elem *elem)
{
const struct nft_set_ext *ext = nft_set_elem_ext(set, elem->priv);
if (nft_set_ext_exists(ext, NFT_SET_EXT_DATA))
nft_data_hold(nft_set_ext_data(ext), set->dtype);
if (nft_set_ext_exists(ext, NFT_SET_EXT_OBJREF))
(*nft_set_ext_obj(ext))->use++;
}
static void nft_set_elem_deactivate(const struct net *net,
const struct nft_set *set,
struct nft_set_elem *elem)
{
const struct nft_set_ext *ext = nft_set_elem_ext(set, elem->priv);
if (nft_set_ext_exists(ext, NFT_SET_EXT_DATA))
nft_data_release(nft_set_ext_data(ext), set->dtype);
if (nft_set_ext_exists(ext, NFT_SET_EXT_OBJREF))
(*nft_set_ext_obj(ext))->use--;
}
static int nft_del_setelem(struct nft_ctx *ctx, struct nft_set *set, static int nft_del_setelem(struct nft_ctx *ctx, struct nft_set *set,
const struct nlattr *attr) const struct nlattr *attr)
{ {
...@@ -3927,6 +4020,8 @@ static int nft_del_setelem(struct nft_ctx *ctx, struct nft_set *set, ...@@ -3927,6 +4020,8 @@ static int nft_del_setelem(struct nft_ctx *ctx, struct nft_set *set,
kfree(elem.priv); kfree(elem.priv);
elem.priv = priv; elem.priv = priv;
nft_set_elem_deactivate(ctx->net, set, &elem);
nft_trans_elem(trans) = elem; nft_trans_elem(trans) = elem;
list_add_tail(&trans->list, &ctx->net->nft.commit_list); list_add_tail(&trans->list, &ctx->net->nft.commit_list);
return 0; return 0;
...@@ -3936,7 +4031,7 @@ static int nft_del_setelem(struct nft_ctx *ctx, struct nft_set *set, ...@@ -3936,7 +4031,7 @@ static int nft_del_setelem(struct nft_ctx *ctx, struct nft_set *set,
err3: err3:
kfree(elem.priv); kfree(elem.priv);
err2: err2:
nft_data_uninit(&elem.key.val, desc.type); nft_data_release(&elem.key.val, desc.type);
err1: err1:
return err; return err;
} }
...@@ -4743,8 +4838,8 @@ static void nf_tables_commit_release(struct nft_trans *trans) ...@@ -4743,8 +4838,8 @@ static void nf_tables_commit_release(struct nft_trans *trans)
nft_set_destroy(nft_trans_set(trans)); nft_set_destroy(nft_trans_set(trans));
break; break;
case NFT_MSG_DELSETELEM: case NFT_MSG_DELSETELEM:
nft_set_elem_destroy(nft_trans_elem_set(trans), nf_tables_set_elem_destroy(nft_trans_elem_set(trans),
nft_trans_elem(trans).priv, true); nft_trans_elem(trans).priv);
break; break;
case NFT_MSG_DELOBJ: case NFT_MSG_DELOBJ:
nft_obj_destroy(nft_trans_obj(trans)); nft_obj_destroy(nft_trans_obj(trans));
...@@ -4979,6 +5074,7 @@ static int nf_tables_abort(struct net *net, struct sk_buff *skb) ...@@ -4979,6 +5074,7 @@ static int nf_tables_abort(struct net *net, struct sk_buff *skb)
case NFT_MSG_DELSETELEM: case NFT_MSG_DELSETELEM:
te = (struct nft_trans_elem *)trans->data; te = (struct nft_trans_elem *)trans->data;
nft_set_elem_activate(net, te->set, &te->elem);
te->set->ops->activate(net, te->set, &te->elem); te->set->ops->activate(net, te->set, &te->elem);
te->set->ndeact--; te->set->ndeact--;
...@@ -5464,7 +5560,7 @@ int nft_data_init(const struct nft_ctx *ctx, ...@@ -5464,7 +5560,7 @@ int nft_data_init(const struct nft_ctx *ctx,
EXPORT_SYMBOL_GPL(nft_data_init); EXPORT_SYMBOL_GPL(nft_data_init);
/** /**
* nft_data_uninit - release a nft_data item * nft_data_release - release a nft_data item
* *
* @data: struct nft_data to release * @data: struct nft_data to release
* @type: type of data * @type: type of data
...@@ -5472,7 +5568,7 @@ EXPORT_SYMBOL_GPL(nft_data_init); ...@@ -5472,7 +5568,7 @@ EXPORT_SYMBOL_GPL(nft_data_init);
* Release a nft_data item. NFT_DATA_VALUE types can be silently discarded, * Release a nft_data item. NFT_DATA_VALUE types can be silently discarded,
* all others need to be released by calling this function. * all others need to be released by calling this function.
*/ */
void nft_data_uninit(const struct nft_data *data, enum nft_data_types type) void nft_data_release(const struct nft_data *data, enum nft_data_types type)
{ {
if (type < NFT_DATA_VERDICT) if (type < NFT_DATA_VERDICT)
return; return;
...@@ -5483,7 +5579,7 @@ void nft_data_uninit(const struct nft_data *data, enum nft_data_types type) ...@@ -5483,7 +5579,7 @@ void nft_data_uninit(const struct nft_data *data, enum nft_data_types type)
WARN_ON(1); WARN_ON(1);
} }
} }
EXPORT_SYMBOL_GPL(nft_data_uninit); EXPORT_SYMBOL_GPL(nft_data_release);
int nft_data_dump(struct sk_buff *skb, int attr, const struct nft_data *data, int nft_data_dump(struct sk_buff *skb, int attr, const struct nft_data *data,
enum nft_data_types type, unsigned int len) enum nft_data_types type, unsigned int len)
......
...@@ -686,6 +686,7 @@ static int nfnl_cthelper_del(struct net *net, struct sock *nfnl, ...@@ -686,6 +686,7 @@ static int nfnl_cthelper_del(struct net *net, struct sock *nfnl,
tuple_set = true; tuple_set = true;
} }
ret = -ENOENT;
list_for_each_entry_safe(nlcth, n, &nfnl_cthelper_list, list) { list_for_each_entry_safe(nlcth, n, &nfnl_cthelper_list, list) {
cur = &nlcth->helper; cur = &nlcth->helper;
j++; j++;
...@@ -699,16 +700,20 @@ static int nfnl_cthelper_del(struct net *net, struct sock *nfnl, ...@@ -699,16 +700,20 @@ static int nfnl_cthelper_del(struct net *net, struct sock *nfnl,
tuple.dst.protonum != cur->tuple.dst.protonum)) tuple.dst.protonum != cur->tuple.dst.protonum))
continue; continue;
found = true; if (refcount_dec_if_one(&cur->refcnt)) {
nf_conntrack_helper_unregister(cur); found = true;
kfree(cur->expect_policy); nf_conntrack_helper_unregister(cur);
kfree(cur->expect_policy);
list_del(&nlcth->list); list_del(&nlcth->list);
kfree(nlcth); kfree(nlcth);
} else {
ret = -EBUSY;
}
} }
/* Make sure we return success if we flush and there is no helpers */ /* Make sure we return success if we flush and there is no helpers */
return (found || j == 0) ? 0 : -ENOENT; return (found || j == 0) ? 0 : ret;
} }
static const struct nla_policy nfnl_cthelper_policy[NFCTH_MAX+1] = { static const struct nla_policy nfnl_cthelper_policy[NFCTH_MAX+1] = {
......
...@@ -83,17 +83,26 @@ static int nft_bitwise_init(const struct nft_ctx *ctx, ...@@ -83,17 +83,26 @@ static int nft_bitwise_init(const struct nft_ctx *ctx,
tb[NFTA_BITWISE_MASK]); tb[NFTA_BITWISE_MASK]);
if (err < 0) if (err < 0)
return err; return err;
if (d1.len != priv->len) if (d1.len != priv->len) {
return -EINVAL; err = -EINVAL;
goto err1;
}
err = nft_data_init(NULL, &priv->xor, sizeof(priv->xor), &d2, err = nft_data_init(NULL, &priv->xor, sizeof(priv->xor), &d2,
tb[NFTA_BITWISE_XOR]); tb[NFTA_BITWISE_XOR]);
if (err < 0) if (err < 0)
return err; goto err1;
if (d2.len != priv->len) if (d2.len != priv->len) {
return -EINVAL; err = -EINVAL;
goto err2;
}
return 0; return 0;
err2:
nft_data_release(&priv->xor, d2.type);
err1:
nft_data_release(&priv->mask, d1.type);
return err;
} }
static int nft_bitwise_dump(struct sk_buff *skb, const struct nft_expr *expr) static int nft_bitwise_dump(struct sk_buff *skb, const struct nft_expr *expr)
......
...@@ -201,10 +201,18 @@ nft_cmp_select_ops(const struct nft_ctx *ctx, const struct nlattr * const tb[]) ...@@ -201,10 +201,18 @@ nft_cmp_select_ops(const struct nft_ctx *ctx, const struct nlattr * const tb[])
if (err < 0) if (err < 0)
return ERR_PTR(err); return ERR_PTR(err);
if (desc.type != NFT_DATA_VALUE) {
err = -EINVAL;
goto err1;
}
if (desc.len <= sizeof(u32) && op == NFT_CMP_EQ) if (desc.len <= sizeof(u32) && op == NFT_CMP_EQ)
return &nft_cmp_fast_ops; return &nft_cmp_fast_ops;
else
return &nft_cmp_ops; return &nft_cmp_ops;
err1:
nft_data_release(&data, desc.type);
return ERR_PTR(-EINVAL);
} }
struct nft_expr_type nft_cmp_type __read_mostly = { struct nft_expr_type nft_cmp_type __read_mostly = {
......
...@@ -826,9 +826,9 @@ static void nft_ct_helper_obj_destroy(struct nft_object *obj) ...@@ -826,9 +826,9 @@ static void nft_ct_helper_obj_destroy(struct nft_object *obj)
struct nft_ct_helper_obj *priv = nft_obj_data(obj); struct nft_ct_helper_obj *priv = nft_obj_data(obj);
if (priv->helper4) if (priv->helper4)
module_put(priv->helper4->me); nf_conntrack_helper_put(priv->helper4);
if (priv->helper6) if (priv->helper6)
module_put(priv->helper6->me); nf_conntrack_helper_put(priv->helper6);
} }
static void nft_ct_helper_obj_eval(struct nft_object *obj, static void nft_ct_helper_obj_eval(struct nft_object *obj,
......
...@@ -65,7 +65,7 @@ static int nft_immediate_init(const struct nft_ctx *ctx, ...@@ -65,7 +65,7 @@ static int nft_immediate_init(const struct nft_ctx *ctx,
return 0; return 0;
err1: err1:
nft_data_uninit(&priv->data, desc.type); nft_data_release(&priv->data, desc.type);
return err; return err;
} }
...@@ -73,7 +73,8 @@ static void nft_immediate_destroy(const struct nft_ctx *ctx, ...@@ -73,7 +73,8 @@ static void nft_immediate_destroy(const struct nft_ctx *ctx,
const struct nft_expr *expr) const struct nft_expr *expr)
{ {
const struct nft_immediate_expr *priv = nft_expr_priv(expr); const struct nft_immediate_expr *priv = nft_expr_priv(expr);
return nft_data_uninit(&priv->data, nft_dreg_to_type(priv->dreg));
return nft_data_release(&priv->data, nft_dreg_to_type(priv->dreg));
} }
static int nft_immediate_dump(struct sk_buff *skb, const struct nft_expr *expr) static int nft_immediate_dump(struct sk_buff *skb, const struct nft_expr *expr)
......
...@@ -102,9 +102,9 @@ static int nft_range_init(const struct nft_ctx *ctx, const struct nft_expr *expr ...@@ -102,9 +102,9 @@ static int nft_range_init(const struct nft_ctx *ctx, const struct nft_expr *expr
priv->len = desc_from.len; priv->len = desc_from.len;
return 0; return 0;
err2: err2:
nft_data_uninit(&priv->data_to, desc_to.type); nft_data_release(&priv->data_to, desc_to.type);
err1: err1:
nft_data_uninit(&priv->data_from, desc_from.type); nft_data_release(&priv->data_from, desc_from.type);
return err; return err;
} }
......
...@@ -222,7 +222,7 @@ static void nft_hash_walk(const struct nft_ctx *ctx, struct nft_set *set, ...@@ -222,7 +222,7 @@ static void nft_hash_walk(const struct nft_ctx *ctx, struct nft_set *set,
struct nft_set_elem elem; struct nft_set_elem elem;
int err; int err;
err = rhashtable_walk_init(&priv->ht, &hti, GFP_KERNEL); err = rhashtable_walk_init(&priv->ht, &hti, GFP_ATOMIC);
iter->err = err; iter->err = err;
if (err) if (err)
return; return;
......
...@@ -283,28 +283,30 @@ static int xt_obj_to_user(u16 __user *psize, u16 size, ...@@ -283,28 +283,30 @@ static int xt_obj_to_user(u16 __user *psize, u16 size,
&U->u.user.revision, K->u.kernel.TYPE->revision) &U->u.user.revision, K->u.kernel.TYPE->revision)
int xt_data_to_user(void __user *dst, const void *src, int xt_data_to_user(void __user *dst, const void *src,
int usersize, int size) int usersize, int size, int aligned_size)
{ {
usersize = usersize ? : size; usersize = usersize ? : size;
if (copy_to_user(dst, src, usersize)) if (copy_to_user(dst, src, usersize))
return -EFAULT; return -EFAULT;
if (usersize != size && clear_user(dst + usersize, size - usersize)) if (usersize != aligned_size &&
clear_user(dst + usersize, aligned_size - usersize))
return -EFAULT; return -EFAULT;
return 0; return 0;
} }
EXPORT_SYMBOL_GPL(xt_data_to_user); EXPORT_SYMBOL_GPL(xt_data_to_user);
#define XT_DATA_TO_USER(U, K, TYPE, C_SIZE) \ #define XT_DATA_TO_USER(U, K, TYPE) \
xt_data_to_user(U->data, K->data, \ xt_data_to_user(U->data, K->data, \
K->u.kernel.TYPE->usersize, \ K->u.kernel.TYPE->usersize, \
C_SIZE ? : K->u.kernel.TYPE->TYPE##size) K->u.kernel.TYPE->TYPE##size, \
XT_ALIGN(K->u.kernel.TYPE->TYPE##size))
int xt_match_to_user(const struct xt_entry_match *m, int xt_match_to_user(const struct xt_entry_match *m,
struct xt_entry_match __user *u) struct xt_entry_match __user *u)
{ {
return XT_OBJ_TO_USER(u, m, match, 0) || return XT_OBJ_TO_USER(u, m, match, 0) ||
XT_DATA_TO_USER(u, m, match, 0); XT_DATA_TO_USER(u, m, match);
} }
EXPORT_SYMBOL_GPL(xt_match_to_user); EXPORT_SYMBOL_GPL(xt_match_to_user);
...@@ -312,7 +314,7 @@ int xt_target_to_user(const struct xt_entry_target *t, ...@@ -312,7 +314,7 @@ int xt_target_to_user(const struct xt_entry_target *t,
struct xt_entry_target __user *u) struct xt_entry_target __user *u)
{ {
return XT_OBJ_TO_USER(u, t, target, 0) || return XT_OBJ_TO_USER(u, t, target, 0) ||
XT_DATA_TO_USER(u, t, target, 0); XT_DATA_TO_USER(u, t, target);
} }
EXPORT_SYMBOL_GPL(xt_target_to_user); EXPORT_SYMBOL_GPL(xt_target_to_user);
...@@ -611,6 +613,12 @@ void xt_compat_match_from_user(struct xt_entry_match *m, void **dstptr, ...@@ -611,6 +613,12 @@ void xt_compat_match_from_user(struct xt_entry_match *m, void **dstptr,
} }
EXPORT_SYMBOL_GPL(xt_compat_match_from_user); EXPORT_SYMBOL_GPL(xt_compat_match_from_user);
#define COMPAT_XT_DATA_TO_USER(U, K, TYPE, C_SIZE) \
xt_data_to_user(U->data, K->data, \
K->u.kernel.TYPE->usersize, \
C_SIZE, \
COMPAT_XT_ALIGN(C_SIZE))
int xt_compat_match_to_user(const struct xt_entry_match *m, int xt_compat_match_to_user(const struct xt_entry_match *m,
void __user **dstptr, unsigned int *size) void __user **dstptr, unsigned int *size)
{ {
...@@ -626,7 +634,7 @@ int xt_compat_match_to_user(const struct xt_entry_match *m, ...@@ -626,7 +634,7 @@ int xt_compat_match_to_user(const struct xt_entry_match *m,
if (match->compat_to_user((void __user *)cm->data, m->data)) if (match->compat_to_user((void __user *)cm->data, m->data))
return -EFAULT; return -EFAULT;
} else { } else {
if (XT_DATA_TO_USER(cm, m, match, msize - sizeof(*cm))) if (COMPAT_XT_DATA_TO_USER(cm, m, match, msize - sizeof(*cm)))
return -EFAULT; return -EFAULT;
} }
...@@ -972,7 +980,7 @@ int xt_compat_target_to_user(const struct xt_entry_target *t, ...@@ -972,7 +980,7 @@ int xt_compat_target_to_user(const struct xt_entry_target *t,
if (target->compat_to_user((void __user *)ct->data, t->data)) if (target->compat_to_user((void __user *)ct->data, t->data))
return -EFAULT; return -EFAULT;
} else { } else {
if (XT_DATA_TO_USER(ct, t, target, tsize - sizeof(*ct))) if (COMPAT_XT_DATA_TO_USER(ct, t, target, tsize - sizeof(*ct)))
return -EFAULT; return -EFAULT;
} }
......
...@@ -96,7 +96,7 @@ xt_ct_set_helper(struct nf_conn *ct, const char *helper_name, ...@@ -96,7 +96,7 @@ xt_ct_set_helper(struct nf_conn *ct, const char *helper_name,
help = nf_ct_helper_ext_add(ct, helper, GFP_KERNEL); help = nf_ct_helper_ext_add(ct, helper, GFP_KERNEL);
if (help == NULL) { if (help == NULL) {
module_put(helper->me); nf_conntrack_helper_put(helper);
return -ENOMEM; return -ENOMEM;
} }
...@@ -263,7 +263,7 @@ static int xt_ct_tg_check(const struct xt_tgchk_param *par, ...@@ -263,7 +263,7 @@ static int xt_ct_tg_check(const struct xt_tgchk_param *par,
err4: err4:
help = nfct_help(ct); help = nfct_help(ct);
if (help) if (help)
module_put(help->helper->me); nf_conntrack_helper_put(help->helper);
err3: err3:
nf_ct_tmpl_free(ct); nf_ct_tmpl_free(ct);
err2: err2:
...@@ -346,7 +346,7 @@ static void xt_ct_tg_destroy(const struct xt_tgdtor_param *par, ...@@ -346,7 +346,7 @@ static void xt_ct_tg_destroy(const struct xt_tgdtor_param *par,
if (ct) { if (ct) {
help = nfct_help(ct); help = nfct_help(ct);
if (help) if (help)
module_put(help->helper->me); nf_conntrack_helper_put(help->helper);
nf_ct_netns_put(par->net, par->family); nf_ct_netns_put(par->net, par->family);
......
...@@ -1123,7 +1123,7 @@ static int ovs_ct_add_helper(struct ovs_conntrack_info *info, const char *name, ...@@ -1123,7 +1123,7 @@ static int ovs_ct_add_helper(struct ovs_conntrack_info *info, const char *name,
help = nf_ct_helper_ext_add(info->ct, helper, GFP_KERNEL); help = nf_ct_helper_ext_add(info->ct, helper, GFP_KERNEL);
if (!help) { if (!help) {
module_put(helper->me); nf_conntrack_helper_put(helper);
return -ENOMEM; return -ENOMEM;
} }
...@@ -1584,7 +1584,7 @@ void ovs_ct_free_action(const struct nlattr *a) ...@@ -1584,7 +1584,7 @@ void ovs_ct_free_action(const struct nlattr *a)
static void __ovs_ct_free_action(struct ovs_conntrack_info *ct_info) static void __ovs_ct_free_action(struct ovs_conntrack_info *ct_info)
{ {
if (ct_info->helper) if (ct_info->helper)
module_put(ct_info->helper->me); nf_conntrack_helper_put(ct_info->helper);
if (ct_info->ct) if (ct_info->ct)
nf_ct_tmpl_free(ct_info->ct); nf_ct_tmpl_free(ct_info->ct);
} }
......
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