Merge git://git.kernel.org/pub/scm/linux/kernel/git/davem/net
[linux-2.6-block.git] / net / netfilter / nft_ct.c
index bf548a7a71ec9b49cf308af041811d2eb5f33c8c..640fe5a5865ef26ea71378535927f6bbd9a6aceb 100644 (file)
@@ -32,6 +32,12 @@ 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;
@@ -83,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;
@@ -151,20 +157,22 @@ static void nft_ct_get_eval(const struct nft_expr *expr,
                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)
-                       *dest = nf_ct_zone_id(zone, priv->dir);
+                       zoneid = nf_ct_zone_id(zone, priv->dir);
                else
-                       *dest = zone->id;
+                       zoneid = zone->id;
 
+               nft_reg_store16(dest, zoneid);
                return;
        }
 #endif
@@ -183,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;
@@ -205,7 +213,7 @@ static void nft_ct_set_zone_eval(const struct nft_expr *expr,
        const struct nft_ct *priv = nft_expr_priv(expr);
        struct sk_buff *skb = pkt->skb;
        enum ip_conntrack_info ctinfo;
-       u16 value = regs->data[priv->sreg];
+       u16 value = nft_reg_load16(&regs->data[priv->sreg]);
        struct nf_conn *ct;
 
        ct = nf_ct_get(skb, &ctinfo);
@@ -542,7 +550,8 @@ static int nft_ct_set_init(const struct nft_ctx *ctx,
                case IP_CT_DIR_REPLY:
                        break;
                default:
-                       return -EINVAL;
+                       err = -EINVAL;
+                       goto err1;
                }
        }
 
@@ -730,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;
@@ -744,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;
@@ -752,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);
 }
@@ -763,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);