diff --git a/include/net/netfilter/nf_conntrack_helper.h b/include/net/netfilter/nf_conntrack_helper.h index bc5427d239f4..335b8c43694f 100644 --- a/include/net/netfilter/nf_conntrack_helper.h +++ b/include/net/netfilter/nf_conntrack_helper.h @@ -106,6 +106,7 @@ void nf_ct_helper_init(struct nf_conntrack_helper *helper, int nf_conntrack_helper_register(struct nf_conntrack_helper *, struct nf_conntrack_helper **); int __nf_conntrack_helper_register(struct nf_conntrack_helper *); void nf_conntrack_helper_unregister(struct nf_conntrack_helper *); +void nf_conntrack_helper_release(struct nf_conntrack_helper *); int nf_conntrack_helpers_register(struct nf_conntrack_helper *, unsigned int, struct nf_conntrack_helper **); diff --git a/net/netfilter/nf_conntrack_helper.c b/net/netfilter/nf_conntrack_helper.c index 506c58034761..c30ae3f203be 100644 --- a/net/netfilter/nf_conntrack_helper.c +++ b/net/netfilter/nf_conntrack_helper.c @@ -448,6 +448,15 @@ static bool expect_iter_me(struct nf_conntrack_expect *exp, void *data) return this == me; } +void nf_conntrack_helper_release(struct nf_conntrack_helper *me) +{ + nf_ct_expect_iterate_destroy(expect_iter_me, me); + + if (refcount_dec_and_test(&me->ct_refcnt)) + kfree_rcu(me, rcu); +} +EXPORT_SYMBOL_GPL(nf_conntrack_helper_release); + void nf_conntrack_helper_unregister(struct nf_conntrack_helper *me) { mutex_lock(&nf_ct_helper_mutex); @@ -463,10 +472,7 @@ void nf_conntrack_helper_unregister(struct nf_conntrack_helper *me) */ synchronize_rcu(); - nf_ct_expect_iterate_destroy(expect_iter_me, me); - - if (refcount_dec_and_test(&me->ct_refcnt)) - kfree_rcu(me, rcu); + nf_conntrack_helper_release(me); } EXPORT_SYMBOL_GPL(nf_conntrack_helper_unregister); diff --git a/net/netfilter/nft_ct.c b/net/netfilter/nft_ct.c index 358b9287e12e..9dbf127df9c8 100644 --- a/net/netfilter/nft_ct.c +++ b/net/netfilter/nft_ct.c @@ -1213,6 +1213,8 @@ struct nft_ct_expect_obj { u8 l4proto; u8 size; u32 timeout; + + struct nf_conntrack_helper *helper; }; static int nft_ct_expect_timeout_get(const struct nlattr *attr, u32 *val) @@ -1226,6 +1228,93 @@ static int nft_ct_expect_timeout_get(const struct nlattr *attr, u32 *val) return 0; } +#if IS_ENABLED(CONFIG_NF_NAT) +static void nft_ct_nat_follow_master(struct nf_conn *ct, struct nf_conntrack_expect *this) +{ + const struct nf_ct_helper_expectfn *expfn; + + expfn = nf_ct_helper_expectfn_find_by_name("nat-follow-master"); + if (expfn) + expfn->expectfn(ct, this); +} +#endif + +struct nft_ct_expect_data { + struct nft_ct_expect_obj obj; + enum ip_conntrack_dir dir; +}; + +static int ct_expect_help(struct sk_buff *skb, unsigned int protoff, + struct nf_conn *ct, enum ip_conntrack_info ctinfo) +{ + enum ip_conntrack_dir dir = CTINFO2DIR(ctinfo); + struct nft_ct_expect_data *expect_data; + struct nf_conntrack_expect *exp; + int ret = NF_ACCEPT; + u16 l3num; + + if (nf_ct_is_confirmed(ct)) + return NF_ACCEPT; + + expect_data = nfct_help_data(ct); + if (!expect_data) + return NF_ACCEPT; + + if (expect_data->dir != dir) + return NF_ACCEPT; + + exp = nf_ct_expect_alloc(ct); + if (!exp) + return NF_DROP; + + if (expect_data->obj.l3num == NFPROTO_INET) + l3num = nf_ct_l3num(ct); + else + l3num = expect_data->obj.l3num; + + nf_ct_expect_init(exp, NF_CT_EXPECT_CLASS_DEFAULT, l3num, + &ct->tuplehash[!dir].tuple.src.u3, + &ct->tuplehash[!dir].tuple.dst.u3, + expect_data->obj.l4proto, NULL, &expect_data->obj.dport); + exp->timeout += expect_data->obj.timeout; + +#if IS_ENABLED(CONFIG_NF_NAT) + if (ct->status & IPS_NAT_MASK) { + exp->saved_proto.tcp.port = expect_data->obj.dport; + exp->dir = !dir; + exp->expectfn = nft_ct_nat_follow_master; + } +#endif + if (nf_ct_expect_related(exp, 0) != 0) + ret = NF_ACCEPT; + + nf_ct_expect_put(exp); + + return ret; +} + +static int nft_ct_expect_helper_alloc(struct nft_ct_expect_obj *priv) +{ + struct nf_conntrack_helper *ct_expect_helper; + + ct_expect_helper = kzalloc_obj(struct nf_conntrack_helper, + GFP_KERNEL_ACCOUNT); + if (!ct_expect_helper) + return -ENOMEM; + + snprintf(ct_expect_helper->name, sizeof(ct_expect_helper->name), "%s", + "nft_ct_expect"); + ct_expect_helper->me = THIS_MODULE; + ct_expect_helper->expect_policy[NF_CT_EXPECT_CLASS_DEFAULT].max_expected = priv->size; + rcu_assign_pointer(ct_expect_helper->help, ct_expect_help); + refcount_set(&ct_expect_helper->ct_refcnt, 1); + + /* No need to register this helper, this is internal. */ + priv->helper = ct_expect_helper; + + return 0; +} + static int nft_ct_expect_obj_init(const struct nft_ctx *ctx, const struct nlattr * const tb[], struct nft_object *obj) @@ -1233,6 +1322,8 @@ static int nft_ct_expect_obj_init(const struct nft_ctx *ctx, struct nft_ct_expect_obj *priv = nft_obj_data(obj); int err; + NF_CT_HELPER_BUILD_BUG_ON(sizeof(struct nft_ct_expect_data)); + if (!tb[NFTA_CT_EXPECT_L4PROTO] || !tb[NFTA_CT_EXPECT_DPORT] || !tb[NFTA_CT_EXPECT_TIMEOUT] || @@ -1272,13 +1363,31 @@ static int nft_ct_expect_obj_init(const struct nft_ctx *ctx, priv->dport = nla_get_be16(tb[NFTA_CT_EXPECT_DPORT]); priv->size = nla_get_u8(tb[NFTA_CT_EXPECT_SIZE]); + if (!priv->size) + priv->size = NF_CT_EXPECT_MAX_CNT; - return nf_ct_netns_get(ctx->net, ctx->family); + err = nf_ct_netns_get(ctx->net, ctx->family); + if (err < 0) + return err; + + err = nft_ct_expect_helper_alloc(priv); + if (err < 0) { + nf_ct_netns_put(ctx->net, ctx->family); + return err; + } + + return err; } static void nft_ct_expect_obj_destroy(const struct nft_ctx *ctx, - struct nft_object *obj) + struct nft_object *obj) { + const struct nft_ct_expect_obj *priv = nft_obj_data(obj); + struct nf_conntrack_helper *me = priv->helper; + + /* This helper is going away, disable it. */ + rcu_assign_pointer(me->help, NULL); + nf_conntrack_helper_release(me); nf_ct_netns_put(ctx->net, ctx->family); } @@ -1297,27 +1406,14 @@ static int nft_ct_expect_obj_dump(struct sk_buff *skb, return 0; } -#if IS_ENABLED(CONFIG_NF_NAT) -static void nft_ct_nat_follow_master(struct nf_conn *ct, struct nf_conntrack_expect *this) -{ - const struct nf_ct_helper_expectfn *expfn; - - expfn = nf_ct_helper_expectfn_find_by_name("nat-follow-master"); - if (expfn) - expfn->expectfn(ct, this); -} -#endif - static void nft_ct_expect_obj_eval(struct nft_object *obj, struct nft_regs *regs, const struct nft_pktinfo *pkt) { const struct nft_ct_expect_obj *priv = nft_obj_data(obj); - struct nf_conntrack_expect *exp; + struct nft_ct_expect_data *expect_data; enum ip_conntrack_info ctinfo; struct nf_conn_help *help; - enum ip_conntrack_dir dir; - u16 l3num = priv->l3num; struct nf_conn *ct; ct = nf_ct_get(pkt->skb, &ctinfo); @@ -1325,45 +1421,30 @@ static void nft_ct_expect_obj_eval(struct nft_object *obj, regs->verdict.code = NFT_BREAK; return; } - dir = CTINFO2DIR(ctinfo); help = nfct_help(ct); - if (!help) - help = nf_ct_helper_ext_add(ct, GFP_ATOMIC); + if (help) { + regs->verdict.code = NFT_BREAK; + return; + } + + help = nf_ct_helper_ext_add(ct, GFP_ATOMIC); if (!help) { regs->verdict.code = NF_DROP; return; } - if (help->expecting[NF_CT_EXPECT_CLASS_DEFAULT] >= priv->size) { + expect_data = nfct_help_data(ct); + if (!expect_data) { regs->verdict.code = NFT_BREAK; return; } - if (l3num == NFPROTO_INET) - l3num = nf_ct_l3num(ct); + expect_data->obj = *priv; + expect_data->obj.helper = NULL; + expect_data->dir = CTINFO2DIR(ctinfo); - exp = nf_ct_expect_alloc(ct); - if (exp == NULL) { - regs->verdict.code = NF_DROP; - return; - } - nf_ct_expect_init(exp, NF_CT_EXPECT_CLASS_DEFAULT, l3num, - &ct->tuplehash[!dir].tuple.src.u3, - &ct->tuplehash[!dir].tuple.dst.u3, - priv->l4proto, NULL, &priv->dport); - exp->timeout += priv->timeout; - -#if IS_ENABLED(CONFIG_NF_NAT) - if (ct->status & IPS_NAT_MASK) { - exp->saved_proto.tcp.port = priv->dport; - exp->dir = !dir; - exp->expectfn = nft_ct_nat_follow_master; - } -#endif - if (nf_ct_expect_related(exp, 0) != 0) - regs->verdict.code = NF_DROP; - - nf_ct_expect_put(exp); + if (help && refcount_inc_not_zero(&priv->helper->ct_refcnt)) + rcu_assign_pointer(help->helper, priv->helper); } static const struct nla_policy nft_ct_expect_policy[NFTA_CT_EXPECT_MAX + 1] = {