Merge branch 'perf-core-for-linus' of git://git.kernel.org/pub/scm/linux/kernel/git...
[firefly-linux-kernel-4.4.55.git] / net / netfilter / nfnetlink_acct.c
index c7b6d466a66247c3fa18b9a9a6f3e174a18d40da..2baa125c2e8dbcee194f8e8d8c98d6574a5b67b8 100644 (file)
@@ -32,18 +32,24 @@ static LIST_HEAD(nfnl_acct_list);
 struct nf_acct {
        atomic64_t              pkts;
        atomic64_t              bytes;
+       unsigned long           flags;
        struct list_head        head;
        atomic_t                refcnt;
        char                    name[NFACCT_NAME_MAX];
        struct rcu_head         rcu_head;
+       char                    data[0];
 };
 
+#define NFACCT_F_QUOTA (NFACCT_F_QUOTA_PKTS | NFACCT_F_QUOTA_BYTES)
+
 static int
 nfnl_acct_new(struct sock *nfnl, struct sk_buff *skb,
             const struct nlmsghdr *nlh, const struct nlattr * const tb[])
 {
        struct nf_acct *nfacct, *matching = NULL;
        char *acct_name;
+       unsigned int size = 0;
+       u32 flags = 0;
 
        if (!tb[NFACCT_NAME])
                return -EINVAL;
@@ -68,15 +74,38 @@ nfnl_acct_new(struct sock *nfnl, struct sk_buff *skb,
                        /* reset counters if you request a replacement. */
                        atomic64_set(&matching->pkts, 0);
                        atomic64_set(&matching->bytes, 0);
+                       smp_mb__before_atomic();
+                       /* reset overquota flag if quota is enabled. */
+                       if ((matching->flags & NFACCT_F_QUOTA))
+                               clear_bit(NFACCT_F_OVERQUOTA, &matching->flags);
                        return 0;
                }
                return -EBUSY;
        }
 
-       nfacct = kzalloc(sizeof(struct nf_acct), GFP_KERNEL);
+       if (tb[NFACCT_FLAGS]) {
+               flags = ntohl(nla_get_be32(tb[NFACCT_FLAGS]));
+               if (flags & ~NFACCT_F_QUOTA)
+                       return -EOPNOTSUPP;
+               if ((flags & NFACCT_F_QUOTA) == NFACCT_F_QUOTA)
+                       return -EINVAL;
+               if (flags & NFACCT_F_OVERQUOTA)
+                       return -EINVAL;
+
+               size += sizeof(u64);
+       }
+
+       nfacct = kzalloc(sizeof(struct nf_acct) + size, GFP_KERNEL);
        if (nfacct == NULL)
                return -ENOMEM;
 
+       if (flags & NFACCT_F_QUOTA) {
+               u64 *quota = (u64 *)nfacct->data;
+
+               *quota = be64_to_cpu(nla_get_be64(tb[NFACCT_QUOTA]));
+               nfacct->flags = flags;
+       }
+
        strncpy(nfacct->name, nla_data(tb[NFACCT_NAME]), NFACCT_NAME_MAX);
 
        if (tb[NFACCT_BYTES]) {
@@ -117,6 +146,9 @@ nfnl_acct_fill_info(struct sk_buff *skb, u32 portid, u32 seq, u32 type,
        if (type == NFNL_MSG_ACCT_GET_CTRZERO) {
                pkts = atomic64_xchg(&acct->pkts, 0);
                bytes = atomic64_xchg(&acct->bytes, 0);
+               smp_mb__before_atomic();
+               if (acct->flags & NFACCT_F_QUOTA)
+                       clear_bit(NFACCT_F_OVERQUOTA, &acct->flags);
        } else {
                pkts = atomic64_read(&acct->pkts);
                bytes = atomic64_read(&acct->bytes);
@@ -125,7 +157,13 @@ nfnl_acct_fill_info(struct sk_buff *skb, u32 portid, u32 seq, u32 type,
            nla_put_be64(skb, NFACCT_BYTES, cpu_to_be64(bytes)) ||
            nla_put_be32(skb, NFACCT_USE, htonl(atomic_read(&acct->refcnt))))
                goto nla_put_failure;
+       if (acct->flags & NFACCT_F_QUOTA) {
+               u64 *quota = (u64 *)acct->data;
 
+               if (nla_put_be32(skb, NFACCT_FLAGS, htonl(acct->flags)) ||
+                   nla_put_be64(skb, NFACCT_QUOTA, cpu_to_be64(*quota)))
+                       goto nla_put_failure;
+       }
        nlmsg_end(skb, nlh);
        return skb->len;
 
@@ -270,6 +308,8 @@ static const struct nla_policy nfnl_acct_policy[NFACCT_MAX+1] = {
        [NFACCT_NAME] = { .type = NLA_NUL_STRING, .len = NFACCT_NAME_MAX-1 },
        [NFACCT_BYTES] = { .type = NLA_U64 },
        [NFACCT_PKTS] = { .type = NLA_U64 },
+       [NFACCT_FLAGS] = { .type = NLA_U32 },
+       [NFACCT_QUOTA] = { .type = NLA_U64 },
 };
 
 static const struct nfnl_callback nfnl_acct_cb[NFNL_MSG_ACCT_MAX] = {
@@ -336,6 +376,50 @@ void nfnl_acct_update(const struct sk_buff *skb, struct nf_acct *nfacct)
 }
 EXPORT_SYMBOL_GPL(nfnl_acct_update);
 
+static void nfnl_overquota_report(struct nf_acct *nfacct)
+{
+       int ret;
+       struct sk_buff *skb;
+
+       skb = nlmsg_new(NLMSG_DEFAULT_SIZE, GFP_ATOMIC);
+       if (skb == NULL)
+               return;
+
+       ret = nfnl_acct_fill_info(skb, 0, 0, NFNL_MSG_ACCT_OVERQUOTA, 0,
+                                 nfacct);
+       if (ret <= 0) {
+               kfree_skb(skb);
+               return;
+       }
+       netlink_broadcast(init_net.nfnl, skb, 0, NFNLGRP_ACCT_QUOTA,
+                         GFP_ATOMIC);
+}
+
+int nfnl_acct_overquota(const struct sk_buff *skb, struct nf_acct *nfacct)
+{
+       u64 now;
+       u64 *quota;
+       int ret = NFACCT_UNDERQUOTA;
+
+       /* no place here if we don't have a quota */
+       if (!(nfacct->flags & NFACCT_F_QUOTA))
+               return NFACCT_NO_QUOTA;
+
+       quota = (u64 *)nfacct->data;
+       now = (nfacct->flags & NFACCT_F_QUOTA_PKTS) ?
+              atomic64_read(&nfacct->pkts) : atomic64_read(&nfacct->bytes);
+
+       ret = now > *quota;
+
+       if (now >= *quota &&
+           !test_and_set_bit(NFACCT_F_OVERQUOTA, &nfacct->flags)) {
+               nfnl_overquota_report(nfacct);
+       }
+
+       return ret;
+}
+EXPORT_SYMBOL_GPL(nfnl_acct_overquota);
+
 static int __init nfnl_acct_init(void)
 {
        int ret;