]> git.proxmox.com Git - mirror_ubuntu-jammy-kernel.git/blobdiff - net/netfilter/nft_ct.c
netfilter: Remove exceptional & on function name
[mirror_ubuntu-jammy-kernel.git] / net / netfilter / nft_ct.c
index e6baeaebe6537ea6337d001faa58631ebf6e41b2..6e23dbbedd7f4eb4d19300817ee2284e508fd827 100644 (file)
@@ -32,6 +32,17 @@ struct nft_ct {
        };
 };
 
+struct nft_ct_helper_obj  {
+       struct nf_conntrack_helper *helper4;
+       struct nf_conntrack_helper *helper6;
+       u8 l4proto;
+};
+
+#ifdef CONFIG_NF_CONNTRACK_ZONES
+static DEFINE_PER_CPU(struct nf_conn *, nft_ct_pcpu_template);
+static unsigned int nft_ct_pcpu_template_refcnt __read_mostly;
+#endif
+
 static u64 nft_ct_get_eval_counter(const struct nf_conn_counter *c,
                                   enum nft_ct_keys k,
                                   enum ip_conntrack_dir d)
@@ -78,7 +89,7 @@ static void nft_ct_get_eval(const struct nft_expr *expr,
 
        switch (priv->key) {
        case NFT_CT_DIRECTION:
-               *dest = CTINFO2DIR(ctinfo);
+               nft_reg_store8(dest, CTINFO2DIR(ctinfo));
                return;
        case NFT_CT_STATUS:
                *dest = ct->status;
@@ -129,12 +140,42 @@ static void nft_ct_get_eval(const struct nft_expr *expr,
                memcpy(dest, &count, sizeof(count));
                return;
        }
+       case NFT_CT_AVGPKT: {
+               const struct nf_conn_acct *acct = nf_conn_acct_find(ct);
+               u64 avgcnt = 0, bcnt = 0, pcnt = 0;
+
+               if (acct) {
+                       pcnt = nft_ct_get_eval_counter(acct->counter,
+                                                      NFT_CT_PKTS, priv->dir);
+                       bcnt = nft_ct_get_eval_counter(acct->counter,
+                                                      NFT_CT_BYTES, priv->dir);
+                       if (pcnt != 0)
+                               avgcnt = div64_u64(bcnt, pcnt);
+               }
+
+               memcpy(dest, &avgcnt, sizeof(avgcnt));
+               return;
+       }
        case NFT_CT_L3PROTOCOL:
-               *dest = nf_ct_l3num(ct);
+               nft_reg_store8(dest, nf_ct_l3num(ct));
                return;
        case NFT_CT_PROTOCOL:
-               *dest = nf_ct_protonum(ct);
+               nft_reg_store8(dest, nf_ct_protonum(ct));
+               return;
+#ifdef CONFIG_NF_CONNTRACK_ZONES
+       case NFT_CT_ZONE: {
+               const struct nf_conntrack_zone *zone = nf_ct_zone(ct);
+               u16 zoneid;
+
+               if (priv->dir < IP_CT_DIR_MAX)
+                       zoneid = nf_ct_zone_id(zone, priv->dir);
+               else
+                       zoneid = zone->id;
+
+               nft_reg_store16(dest, zoneid);
                return;
+       }
+#endif
        default:
                break;
        }
@@ -150,10 +191,10 @@ static void nft_ct_get_eval(const struct nft_expr *expr,
                       nf_ct_l3num(ct) == NFPROTO_IPV4 ? 4 : 16);
                return;
        case NFT_CT_PROTO_SRC:
-               *dest = (__force __u16)tuple->src.u.all;
+               nft_reg_store16(dest, (__force u16)tuple->src.u.all);
                return;
        case NFT_CT_PROTO_DST:
-               *dest = (__force __u16)tuple->dst.u.all;
+               nft_reg_store16(dest, (__force u16)tuple->dst.u.all);
                return;
        default:
                break;
@@ -163,6 +204,53 @@ err:
        regs->verdict.code = NFT_BREAK;
 }
 
+#ifdef CONFIG_NF_CONNTRACK_ZONES
+static void nft_ct_set_zone_eval(const struct nft_expr *expr,
+                                struct nft_regs *regs,
+                                const struct nft_pktinfo *pkt)
+{
+       struct nf_conntrack_zone zone = { .dir = NF_CT_DEFAULT_ZONE_DIR };
+       const struct nft_ct *priv = nft_expr_priv(expr);
+       struct sk_buff *skb = pkt->skb;
+       enum ip_conntrack_info ctinfo;
+       u16 value = nft_reg_load16(&regs->data[priv->sreg]);
+       struct nf_conn *ct;
+
+       ct = nf_ct_get(skb, &ctinfo);
+       if (ct) /* already tracked */
+               return;
+
+       zone.id = value;
+
+       switch (priv->dir) {
+       case IP_CT_DIR_ORIGINAL:
+               zone.dir = NF_CT_ZONE_DIR_ORIG;
+               break;
+       case IP_CT_DIR_REPLY:
+               zone.dir = NF_CT_ZONE_DIR_REPL;
+               break;
+       default:
+               break;
+       }
+
+       ct = this_cpu_read(nft_ct_pcpu_template);
+
+       if (likely(atomic_read(&ct->ct_general.use) == 1)) {
+               nf_ct_zone_add(ct, &zone);
+       } else {
+               /* previous skb got queued to userspace */
+               ct = nf_ct_tmpl_alloc(nft_net(pkt), &zone, GFP_ATOMIC);
+               if (!ct) {
+                       regs->verdict.code = NF_DROP;
+                       return;
+               }
+       }
+
+       atomic_inc(&ct->ct_general.use);
+       nf_ct_set(skb, ct, IP_CT_NEW);
+}
+#endif
+
 static void nft_ct_set_eval(const struct nft_expr *expr,
                            struct nft_regs *regs,
                            const struct nft_pktinfo *pkt)
@@ -241,6 +329,45 @@ static void nft_ct_netns_put(struct net *net, uint8_t family)
                nf_ct_netns_put(net, family);
 }
 
+#ifdef CONFIG_NF_CONNTRACK_ZONES
+static void nft_ct_tmpl_put_pcpu(void)
+{
+       struct nf_conn *ct;
+       int cpu;
+
+       for_each_possible_cpu(cpu) {
+               ct = per_cpu(nft_ct_pcpu_template, cpu);
+               if (!ct)
+                       break;
+               nf_ct_put(ct);
+               per_cpu(nft_ct_pcpu_template, cpu) = NULL;
+       }
+}
+
+static bool nft_ct_tmpl_alloc_pcpu(void)
+{
+       struct nf_conntrack_zone zone = { .id = 0 };
+       struct nf_conn *tmp;
+       int cpu;
+
+       if (nft_ct_pcpu_template_refcnt)
+               return true;
+
+       for_each_possible_cpu(cpu) {
+               tmp = nf_ct_tmpl_alloc(&init_net, &zone, GFP_KERNEL);
+               if (!tmp) {
+                       nft_ct_tmpl_put_pcpu();
+                       return false;
+               }
+
+               atomic_set(&tmp->ct_general.use, 1);
+               per_cpu(nft_ct_pcpu_template, cpu) = tmp;
+       }
+
+       return true;
+}
+#endif
+
 static int nft_ct_get_init(const struct nft_ctx *ctx,
                           const struct nft_expr *expr,
                           const struct nlattr * const tb[])
@@ -250,6 +377,7 @@ static int nft_ct_get_init(const struct nft_ctx *ctx,
        int err;
 
        priv->key = ntohl(nla_get_be32(tb[NFTA_CT_KEY]));
+       priv->dir = IP_CT_DIR_MAX;
        switch (priv->key) {
        case NFT_CT_DIRECTION:
                if (tb[NFTA_CT_DIRECTION] != NULL)
@@ -316,11 +444,14 @@ static int nft_ct_get_init(const struct nft_ctx *ctx,
                break;
        case NFT_CT_BYTES:
        case NFT_CT_PKTS:
-               /* no direction? return sum of original + reply */
-               if (tb[NFTA_CT_DIRECTION] == NULL)
-                       priv->dir = IP_CT_DIR_MAX;
+       case NFT_CT_AVGPKT:
                len = sizeof(u64);
                break;
+#ifdef CONFIG_NF_CONNTRACK_ZONES
+       case NFT_CT_ZONE:
+               len = sizeof(u16);
+               break;
+#endif
        default:
                return -EOPNOTSUPP;
        }
@@ -346,21 +477,41 @@ static int nft_ct_get_init(const struct nft_ctx *ctx,
        if (err < 0)
                return err;
 
-       if (priv->key == NFT_CT_BYTES || priv->key == NFT_CT_PKTS)
+       if (priv->key == NFT_CT_BYTES ||
+           priv->key == NFT_CT_PKTS  ||
+           priv->key == NFT_CT_AVGPKT)
                nf_ct_set_acct(ctx->net, true);
 
        return 0;
 }
 
+static void __nft_ct_set_destroy(const struct nft_ctx *ctx, struct nft_ct *priv)
+{
+       switch (priv->key) {
+#ifdef CONFIG_NF_CONNTRACK_LABELS
+       case NFT_CT_LABELS:
+               nf_connlabels_put(ctx->net);
+               break;
+#endif
+#ifdef CONFIG_NF_CONNTRACK_ZONES
+       case NFT_CT_ZONE:
+               if (--nft_ct_pcpu_template_refcnt == 0)
+                       nft_ct_tmpl_put_pcpu();
+#endif
+       default:
+               break;
+       }
+}
+
 static int nft_ct_set_init(const struct nft_ctx *ctx,
                           const struct nft_expr *expr,
                           const struct nlattr * const tb[])
 {
        struct nft_ct *priv = nft_expr_priv(expr);
-       bool label_got = false;
        unsigned int len;
        int err;
 
+       priv->dir = IP_CT_DIR_MAX;
        priv->key = ntohl(nla_get_be32(tb[NFTA_CT_KEY]));
        switch (priv->key) {
 #ifdef CONFIG_NF_CONNTRACK_MARK
@@ -378,13 +529,32 @@ static int nft_ct_set_init(const struct nft_ctx *ctx,
                err = nf_connlabels_get(ctx->net, (len * BITS_PER_BYTE) - 1);
                if (err)
                        return err;
-               label_got = true;
+               break;
+#endif
+#ifdef CONFIG_NF_CONNTRACK_ZONES
+       case NFT_CT_ZONE:
+               if (!nft_ct_tmpl_alloc_pcpu())
+                       return -ENOMEM;
+               nft_ct_pcpu_template_refcnt++;
+               len = sizeof(u16);
                break;
 #endif
        default:
                return -EOPNOTSUPP;
        }
 
+       if (tb[NFTA_CT_DIRECTION]) {
+               priv->dir = nla_get_u8(tb[NFTA_CT_DIRECTION]);
+               switch (priv->dir) {
+               case IP_CT_DIR_ORIGINAL:
+               case IP_CT_DIR_REPLY:
+                       break;
+               default:
+                       err = -EINVAL;
+                       goto err1;
+               }
+       }
+
        priv->sreg = nft_parse_register(tb[NFTA_CT_SREG]);
        err = nft_validate_register_load(priv->sreg, len);
        if (err < 0)
@@ -397,8 +567,7 @@ static int nft_ct_set_init(const struct nft_ctx *ctx,
        return 0;
 
 err1:
-       if (label_got)
-               nf_connlabels_put(ctx->net);
+       __nft_ct_set_destroy(ctx, priv);
        return err;
 }
 
@@ -413,16 +582,7 @@ static void nft_ct_set_destroy(const struct nft_ctx *ctx,
 {
        struct nft_ct *priv = nft_expr_priv(expr);
 
-       switch (priv->key) {
-#ifdef CONFIG_NF_CONNTRACK_LABELS
-       case NFT_CT_LABELS:
-               nf_connlabels_put(ctx->net);
-               break;
-#endif
-       default:
-               break;
-       }
-
+       __nft_ct_set_destroy(ctx, priv);
        nft_ct_netns_put(ctx->net, ctx->afi->family);
 }
 
@@ -445,6 +605,8 @@ static int nft_ct_get_dump(struct sk_buff *skb, const struct nft_expr *expr)
                break;
        case NFT_CT_BYTES:
        case NFT_CT_PKTS:
+       case NFT_CT_AVGPKT:
+       case NFT_CT_ZONE:
                if (priv->dir < IP_CT_DIR_MAX &&
                    nla_put_u8(skb, NFTA_CT_DIRECTION, priv->dir))
                        goto nla_put_failure;
@@ -467,6 +629,17 @@ static int nft_ct_set_dump(struct sk_buff *skb, const struct nft_expr *expr)
                goto nla_put_failure;
        if (nla_put_be32(skb, NFTA_CT_KEY, htonl(priv->key)))
                goto nla_put_failure;
+
+       switch (priv->key) {
+       case NFT_CT_ZONE:
+               if (priv->dir < IP_CT_DIR_MAX &&
+                   nla_put_u8(skb, NFTA_CT_DIRECTION, priv->dir))
+                       goto nla_put_failure;
+               break;
+       default:
+               break;
+       }
+
        return 0;
 
 nla_put_failure:
@@ -492,6 +665,17 @@ static const struct nft_expr_ops nft_ct_set_ops = {
        .dump           = nft_ct_set_dump,
 };
 
+#ifdef CONFIG_NF_CONNTRACK_ZONES
+static const struct nft_expr_ops nft_ct_set_zone_ops = {
+       .type           = &nft_ct_type,
+       .size           = NFT_EXPR_SIZE(sizeof(struct nft_ct)),
+       .eval           = nft_ct_set_zone_eval,
+       .init           = nft_ct_set_init,
+       .destroy        = nft_ct_set_destroy,
+       .dump           = nft_ct_set_dump,
+};
+#endif
+
 static const struct nft_expr_ops *
 nft_ct_select_ops(const struct nft_ctx *ctx,
                    const struct nlattr * const tb[])
@@ -505,15 +689,20 @@ nft_ct_select_ops(const struct nft_ctx *ctx,
        if (tb[NFTA_CT_DREG])
                return &nft_ct_get_ops;
 
-       if (tb[NFTA_CT_SREG])
+       if (tb[NFTA_CT_SREG]) {
+#ifdef CONFIG_NF_CONNTRACK_ZONES
+               if (nla_get_be32(tb[NFTA_CT_KEY]) == htonl(NFT_CT_ZONE))
+                       return &nft_ct_set_zone_ops;
+#endif
                return &nft_ct_set_ops;
+       }
 
        return ERR_PTR(-EINVAL);
 }
 
 static struct nft_expr_type nft_ct_type __read_mostly = {
        .name           = "ct",
-       .select_ops     = &nft_ct_select_ops,
+       .select_ops     = nft_ct_select_ops,
        .policy         = nft_ct_policy,
        .maxattr        = NFTA_CT_MAX,
        .owner          = THIS_MODULE,
@@ -534,8 +723,7 @@ static void nft_notrack_eval(const struct nft_expr *expr,
 
        ct = nf_ct_untracked_get();
        atomic_inc(&ct->ct_general.use);
-       skb->nfct = &ct->ct_general;
-       skb->nfctinfo = IP_CT_NEW;
+       nf_ct_set(skb, ct, IP_CT_NEW);
 }
 
 static struct nft_expr_type nft_notrack_type;
@@ -551,6 +739,162 @@ static struct nft_expr_type nft_notrack_type __read_mostly = {
        .owner          = THIS_MODULE,
 };
 
+static int nft_ct_helper_obj_init(const struct nft_ctx *ctx,
+                                 const struct nlattr * const tb[],
+                                 struct nft_object *obj)
+{
+       struct nft_ct_helper_obj *priv = nft_obj_data(obj);
+       struct nf_conntrack_helper *help4, *help6;
+       char name[NF_CT_HELPER_NAME_LEN];
+       int family = ctx->afi->family;
+
+       if (!tb[NFTA_CT_HELPER_NAME] || !tb[NFTA_CT_HELPER_L4PROTO])
+               return -EINVAL;
+
+       priv->l4proto = nla_get_u8(tb[NFTA_CT_HELPER_L4PROTO]);
+       if (!priv->l4proto)
+               return -ENOENT;
+
+       nla_strlcpy(name, tb[NFTA_CT_HELPER_NAME], sizeof(name));
+
+       if (tb[NFTA_CT_HELPER_L3PROTO])
+               family = ntohs(nla_get_be16(tb[NFTA_CT_HELPER_L3PROTO]));
+
+       help4 = NULL;
+       help6 = NULL;
+
+       switch (family) {
+       case NFPROTO_IPV4:
+               if (ctx->afi->family == NFPROTO_IPV6)
+                       return -EINVAL;
+
+               help4 = nf_conntrack_helper_try_module_get(name, family,
+                                                          priv->l4proto);
+               break;
+       case NFPROTO_IPV6:
+               if (ctx->afi->family == NFPROTO_IPV4)
+                       return -EINVAL;
+
+               help6 = nf_conntrack_helper_try_module_get(name, family,
+                                                          priv->l4proto);
+               break;
+       case NFPROTO_NETDEV: /* fallthrough */
+       case NFPROTO_BRIDGE: /* same */
+       case NFPROTO_INET:
+               help4 = nf_conntrack_helper_try_module_get(name, NFPROTO_IPV4,
+                                                          priv->l4proto);
+               help6 = nf_conntrack_helper_try_module_get(name, NFPROTO_IPV6,
+                                                          priv->l4proto);
+               break;
+       default:
+               return -EAFNOSUPPORT;
+       }
+
+       /* && is intentional; only error if INET found neither ipv4 or ipv6 */
+       if (!help4 && !help6)
+               return -ENOENT;
+
+       priv->helper4 = help4;
+       priv->helper6 = help6;
+
+       return 0;
+}
+
+static void nft_ct_helper_obj_destroy(struct nft_object *obj)
+{
+       struct nft_ct_helper_obj *priv = nft_obj_data(obj);
+
+       if (priv->helper4)
+               module_put(priv->helper4->me);
+       if (priv->helper6)
+               module_put(priv->helper6->me);
+}
+
+static void nft_ct_helper_obj_eval(struct nft_object *obj,
+                                  struct nft_regs *regs,
+                                  const struct nft_pktinfo *pkt)
+{
+       const struct nft_ct_helper_obj *priv = nft_obj_data(obj);
+       struct nf_conn *ct = (struct nf_conn *)skb_nfct(pkt->skb);
+       struct nf_conntrack_helper *to_assign = NULL;
+       struct nf_conn_help *help;
+
+       if (!ct ||
+           nf_ct_is_confirmed(ct) ||
+           nf_ct_is_template(ct) ||
+           priv->l4proto != nf_ct_protonum(ct))
+               return;
+
+       switch (nf_ct_l3num(ct)) {
+       case NFPROTO_IPV4:
+               to_assign = priv->helper4;
+               break;
+       case NFPROTO_IPV6:
+               to_assign = priv->helper6;
+               break;
+       default:
+               WARN_ON_ONCE(1);
+               return;
+       }
+
+       if (!to_assign)
+               return;
+
+       if (test_bit(IPS_HELPER_BIT, &ct->status))
+               return;
+
+       help = nf_ct_helper_ext_add(ct, to_assign, GFP_ATOMIC);
+       if (help) {
+               rcu_assign_pointer(help->helper, to_assign);
+               set_bit(IPS_HELPER_BIT, &ct->status);
+       }
+}
+
+static int nft_ct_helper_obj_dump(struct sk_buff *skb,
+                                 struct nft_object *obj, bool reset)
+{
+       const struct nft_ct_helper_obj *priv = nft_obj_data(obj);
+       const struct nf_conntrack_helper *helper = priv->helper4;
+       u16 family;
+
+       if (nla_put_string(skb, NFTA_CT_HELPER_NAME, helper->name))
+               return -1;
+
+       if (nla_put_u8(skb, NFTA_CT_HELPER_L4PROTO, priv->l4proto))
+               return -1;
+
+       if (priv->helper4 && priv->helper6)
+               family = NFPROTO_INET;
+       else if (priv->helper6)
+               family = NFPROTO_IPV6;
+       else
+               family = NFPROTO_IPV4;
+
+       if (nla_put_be16(skb, NFTA_CT_HELPER_L3PROTO, htons(family)))
+               return -1;
+
+       return 0;
+}
+
+static const struct nla_policy nft_ct_helper_policy[NFTA_CT_HELPER_MAX + 1] = {
+       [NFTA_CT_HELPER_NAME] = { .type = NLA_STRING,
+                                 .len = NF_CT_HELPER_NAME_LEN - 1 },
+       [NFTA_CT_HELPER_L3PROTO] = { .type = NLA_U16 },
+       [NFTA_CT_HELPER_L4PROTO] = { .type = NLA_U8 },
+};
+
+static struct nft_object_type nft_ct_helper_obj __read_mostly = {
+       .type           = NFT_OBJECT_CT_HELPER,
+       .size           = sizeof(struct nft_ct_helper_obj),
+       .maxattr        = NFTA_CT_HELPER_MAX,
+       .policy         = nft_ct_helper_policy,
+       .eval           = nft_ct_helper_obj_eval,
+       .init           = nft_ct_helper_obj_init,
+       .destroy        = nft_ct_helper_obj_destroy,
+       .dump           = nft_ct_helper_obj_dump,
+       .owner          = THIS_MODULE,
+};
+
 static int __init nft_ct_module_init(void)
 {
        int err;
@@ -565,7 +909,14 @@ static int __init nft_ct_module_init(void)
        if (err < 0)
                goto err1;
 
+       err = nft_register_obj(&nft_ct_helper_obj);
+       if (err < 0)
+               goto err2;
+
        return 0;
+
+err2:
+       nft_unregister_expr(&nft_notrack_type);
 err1:
        nft_unregister_expr(&nft_ct_type);
        return err;
@@ -573,6 +924,7 @@ err1:
 
 static void __exit nft_ct_module_exit(void)
 {
+       nft_unregister_obj(&nft_ct_helper_obj);
        nft_unregister_expr(&nft_notrack_type);
        nft_unregister_expr(&nft_ct_type);
 }
@@ -584,3 +936,4 @@ MODULE_LICENSE("GPL");
 MODULE_AUTHOR("Patrick McHardy <kaber@trash.net>");
 MODULE_ALIAS_NFT_EXPR("ct");
 MODULE_ALIAS_NFT_EXPR("notrack");
+MODULE_ALIAS_NFT_OBJ(NFT_OBJECT_CT_HELPER);