From edfeb252037693b5e2fd45e8cb38610223f8e3a9 Mon Sep 17 00:00:00 2001 From: "xiaocong.ran" Date: Fri, 15 Jul 2022 10:47:39 +0800 Subject: [PATCH] quota Signed-off-by: xiaocong.ran Change-Id: I41ada3c0ac393d38247920eb484d349f15dd4ff9 --- include/linux/netfilter/xt_quota2.h | 26 ++ net/netfilter/Kconfig | 23 ++ net/netfilter/Makefile | 2 + net/netfilter/xt_quota2.c | 454 ++++++++++++++++++++++++++++ 4 files changed, 505 insertions(+) create mode 100644 include/linux/netfilter/xt_quota2.h create mode 100644 net/netfilter/xt_quota2.c diff --git a/include/linux/netfilter/xt_quota2.h b/include/linux/netfilter/xt_quota2.h new file mode 100644 index 000000000000..a3918718cbf8 --- /dev/null +++ b/include/linux/netfilter/xt_quota2.h @@ -0,0 +1,26 @@ +#ifndef _XT_QUOTA_H +#define _XT_QUOTA_H +#include + +enum xt_quota_flags { + XT_QUOTA_INVERT = 1 << 0, + XT_QUOTA_GROW = 1 << 1, + XT_QUOTA_PACKET = 1 << 2, + XT_QUOTA_NO_CHANGE = 1 << 3, + XT_QUOTA_MASK = 0x0F, +}; + +struct xt_quota_counter; + +struct xt_quota_mtinfo2 { + char name[15]; + u_int8_t flags; + + /* Comparison-invariant */ + aligned_u64 quota; + + /* Used internally by the kernel */ + struct xt_quota_counter *master __attribute__((aligned(8))); +}; + +#endif /* _XT_QUOTA_H */ diff --git a/net/netfilter/Kconfig b/net/netfilter/Kconfig index 6bafd3876aff..56c53318a107 100644 --- a/net/netfilter/Kconfig +++ b/net/netfilter/Kconfig @@ -1477,6 +1477,29 @@ config NETFILTER_XT_MATCH_QUOTA If you want to compile it as a module, say M here and read . If unsure, say `N'. +config NETFILTER_XT_MATCH_QUOTA2 + tristate '"quota2" match support' + depends on NETFILTER_ADVANCED + help + This option adds a `quota2' match, which allows to match on a + byte counter correctly and not per CPU. + It allows naming the quotas. + This is based on http://xtables-addons.git.sourceforge.net + + If you want to compile it as a module, say M here and read + . If unsure, say `N'. + +config NETFILTER_XT_MATCH_QUOTA2_LOG + bool '"quota2" Netfilter LOG support' + depends on NETFILTER_XT_MATCH_QUOTA2 + default n + help + This option allows `quota2' to log ONCE when a quota limit + is passed. It logs via NETLINK using the NETLINK_NFLOG family. + It logs similarly to how ipt_ULOG would without data. + + If unsure, say `N'. + config NETFILTER_XT_MATCH_RATEEST tristate '"rateest" match support' depends on NETFILTER_ADVANCED diff --git a/net/netfilter/Makefile b/net/netfilter/Makefile index 0e0ded87e27b..ba61932e3fb7 100644 --- a/net/netfilter/Makefile +++ b/net/netfilter/Makefile @@ -197,6 +197,8 @@ obj-$(CONFIG_NETFILTER_XT_MATCH_PHYSDEV) += xt_physdev.o obj-$(CONFIG_NETFILTER_XT_MATCH_PKTTYPE) += xt_pkttype.o obj-$(CONFIG_NETFILTER_XT_MATCH_POLICY) += xt_policy.o obj-$(CONFIG_NETFILTER_XT_MATCH_QUOTA) += xt_quota.o +obj-$(CONFIG_NETFILTER_XT_MATCH_QUOTA2) += xt_quota2.o +obj-$(CONFIG_NETFILTER_XT_MATCH_QUOTA2_LOG) += xt_quota2_log.o obj-$(CONFIG_NETFILTER_XT_MATCH_RATEEST) += xt_rateest.o obj-$(CONFIG_NETFILTER_XT_MATCH_REALM) += xt_realm.o obj-$(CONFIG_NETFILTER_XT_MATCH_RECENT) += xt_recent.o diff --git a/net/netfilter/xt_quota2.c b/net/netfilter/xt_quota2.c new file mode 100644 index 000000000000..4be0390a97c7 --- /dev/null +++ b/net/netfilter/xt_quota2.c @@ -0,0 +1,454 @@ +/* + * xt_quota2 - enhanced xt_quota that can count upwards and in packets + * as a minimal accounting match. + * by Jan Engelhardt , 2008 + * + * Originally based on xt_quota.c: + * netfilter module to enforce network quotas + * Sam Johnston + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License; either + * version 2 of the License, as published by the Free Software Foundation. + */ +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +#ifdef CONFIG_NETFILTER_XT_MATCH_QUOTA2_LOG +/* For compatibility, these definitions are copied from the + * deprecated header file */ +#define ULOG_MAC_LEN 80 +#define ULOG_PREFIX_LEN 32 + +/* Format of the ULOG packets passed through netlink */ +typedef struct ulog_packet_msg { + unsigned long mark; + long timestamp_sec; + long timestamp_usec; + unsigned int hook; + char indev_name[IFNAMSIZ]; + char outdev_name[IFNAMSIZ]; + size_t data_len; + char prefix[ULOG_PREFIX_LEN]; + unsigned char mac_len; + unsigned char mac[ULOG_MAC_LEN]; + unsigned char payload[0]; +} ulog_packet_msg_t; +#endif + +/** + * @lock: lock to protect quota writers from each other + */ +struct xt_quota_counter { + u_int64_t quota; + spinlock_t lock; + struct list_head list; + atomic_t ref; + char name[sizeof(((struct xt_quota_mtinfo2 *)NULL)->name)]; + struct proc_dir_entry *procfs_entry; +}; + +#ifdef CONFIG_NETFILTER_XT_MATCH_QUOTA2_LOG +/* Harald's favorite number +1 :D From ipt_ULOG.C */ +static int qlog_nl_event = 112; +module_param_named(event_num, qlog_nl_event, uint, S_IRUGO | S_IWUSR); +MODULE_PARM_DESC(event_num, + "Event number for NETLINK_NFLOG message. 0 disables log." + "111 is what ipt_ULOG uses."); +static struct sock *nflognl; +#endif + +static void quota2_log(unsigned int hooknum, + const struct sk_buff *skb, + const struct net_device *in, + const struct net_device *out, + const char *prefix) +{ + #ifdef CONFIG_NETFILTER_XT_MATCH_QUOTA2_LOG + ulog_packet_msg_t *pm; + struct sk_buff *log_skb; + size_t size; + struct nlmsghdr *nlh; + + if (!qlog_nl_event) + return; + + size = NLMSG_SPACE(sizeof(*pm)); + size = max(size, (size_t)NLMSG_GOODSIZE); + log_skb = alloc_skb(size, GFP_ATOMIC); + if (!log_skb) { + pr_err("xt_quota2: cannot alloc skb for logging\n"); + return; + } + + nlh = nlmsg_put(log_skb, /*pid*/0, /*seq*/0, qlog_nl_event, + sizeof(*pm), 0); + if (!nlh) { + pr_err("xt_quota2: nlmsg_put failed\n"); + kfree_skb(log_skb); + return; + } + pm = nlmsg_data(nlh); + if (skb->tstamp == 0) + __net_timestamp((struct sk_buff *)skb); + pm->data_len = 0; + pm->hook = hooknum; + if (prefix != NULL) + strlcpy(pm->prefix, prefix, sizeof(pm->prefix)); + else + *(pm->prefix) = '\0'; + if (in) + strlcpy(pm->indev_name, in->name, sizeof(pm->indev_name)); + else + pm->indev_name[0] = '\0'; + + if (out) + strlcpy(pm->outdev_name, out->name, sizeof(pm->outdev_name)); + else + pm->outdev_name[0] = '\0'; + + NETLINK_CB(log_skb).dst_group = 1; + pr_debug("throwing 1 packets to netlink group 1\n"); + netlink_broadcast(nflognl, log_skb, 0, 1, GFP_ATOMIC); + #endif /* CONFIG_NETFILTER_XT_MATCH_QUOTA2_LOG */ +} + +struct quota2_net { + struct list_head counter_list; + struct proc_dir_entry *proc_xt_quota; +}; + +static int quota2_net_id; +static inline struct quota2_net *quota2_pernet(struct net *net) +{ + return net_generic(net, quota2_net_id); +} + +static DEFINE_SPINLOCK(counter_list_lock); + +static unsigned int quota_list_perms = S_IRUGO | S_IWUSR; +static unsigned int quota_list_uid = 0; +static unsigned int quota_list_gid = 0; +module_param_named(perms, quota_list_perms, uint, S_IRUGO | S_IWUSR); +module_param_named(uid, quota_list_uid, uint, S_IRUGO | S_IWUSR); +module_param_named(gid, quota_list_gid, uint, S_IRUGO | S_IWUSR); + + +static int quota_proc_show(struct seq_file *m, void *data) +{ + struct xt_quota_counter *e = m->private; + + spin_lock_bh(&e->lock); + seq_printf(m, "%llu\n", e->quota); + spin_unlock_bh(&e->lock); + return 0; +} + +static int quota_proc_open(struct inode *inode, struct file *file) +{ + return single_open(file, quota_proc_show, PDE_DATA(inode)); +} + +static ssize_t +quota_proc_write(struct file *file, const char __user *input, + size_t size, loff_t *loff) +{ + struct xt_quota_counter *e = PDE_DATA(file_inode(file)); + char buf[sizeof("+-18446744073709551616")]; + + if (size > sizeof(buf)) + size = sizeof(buf); + if (copy_from_user(buf, input, size) != 0) + return -EFAULT; + buf[sizeof(buf)-1] = '\0'; + if (size < sizeof(buf)) + buf[size] = '\0'; + + if (*buf == '+') { + int64_t temp = simple_strtoll(buf + 1, NULL, 0); + spin_lock_bh(&e->lock); + /* Do not let quota become negative if @tmp is very negative */ + if (temp > 0 || -temp < e->quota) + e->quota += temp; + else + e->quota = 0; + spin_unlock_bh(&e->lock); + } else if (*buf == '-') { + int64_t temp = simple_strtoll(buf + 1, NULL, 0); + spin_lock_bh(&e->lock); + /* Do not let quota become negative if @tmp is very big */ + if (temp < 0 || temp < e->quota) + e->quota -= temp; + else + e->quota = 0; + spin_unlock_bh(&e->lock); + } else { + spin_lock_bh(&e->lock); + e->quota = simple_strtoull(buf, NULL, 0); + spin_unlock_bh(&e->lock); + } + return size; +} + +static const struct proc_ops quota_proc_fops = { + .proc_open = quota_proc_open, + .proc_read = seq_read, + .proc_write = quota_proc_write, + .proc_lseek = seq_lseek, + .proc_release = single_release, +}; + +static struct xt_quota_counter * +q2_new_counter(const struct xt_quota_mtinfo2 *q, bool anon) +{ + struct xt_quota_counter *e; + unsigned int size; + + /* Do not need all the procfs things for anonymous counters. */ + size = anon ? offsetof(typeof(*e), list) : sizeof(*e); + e = kmalloc(size, GFP_KERNEL); + if (e == NULL) + return NULL; + + e->quota = q->quota; + spin_lock_init(&e->lock); + if (!anon) { + INIT_LIST_HEAD(&e->list); + atomic_set(&e->ref, 1); + strncpy(e->name, q->name, sizeof(e->name)); + } + return e; +} + +/** + * q2_get_counter - get ref to counter or create new + * @name: name of counter + */ +static struct xt_quota_counter * +q2_get_counter(struct net *net, const struct xt_quota_mtinfo2 *q) +{ + struct proc_dir_entry *p; + struct xt_quota_counter *e; + struct quota2_net *quota2_net = quota2_pernet(net); + + if (*q->name == '\0') + return q2_new_counter(q, true); + + spin_lock_bh(&counter_list_lock); + list_for_each_entry(e, "a2_net->counter_list, list) + if (strcmp(e->name, q->name) == 0) { + atomic_inc(&e->ref); + spin_unlock_bh(&counter_list_lock); + return e; + } + + e = q2_new_counter(q, false); + if (e == NULL) + goto out; + + p = proc_create_data(e->name, quota_list_perms, + quota2_net->proc_xt_quota, + "a_proc_fops, e); + if (p == NULL || IS_ERR(p)) + goto out; + + e->procfs_entry = p; + proc_set_user(p, make_kuid(&init_user_ns, quota_list_uid), + make_kgid(&init_user_ns, quota_list_gid)); + list_add_tail(&e->list, "a2_net->counter_list); + spin_unlock_bh(&counter_list_lock); + return e; + + out: + spin_unlock_bh(&counter_list_lock); + kfree(e); + return NULL; +} + +static int quota_mt2_check(const struct xt_mtchk_param *par) +{ + struct xt_quota_mtinfo2 *q = par->matchinfo; + + if (q->flags & ~XT_QUOTA_MASK) + return -EINVAL; + + q->name[sizeof(q->name)-1] = '\0'; + if (*q->name == '.' || strchr(q->name, '/') != NULL) { + printk(KERN_ERR "xt_quota.3: illegal name\n"); + return -EINVAL; + } + + q->master = q2_get_counter(par->net, q); + if (q->master == NULL) { + printk(KERN_ERR "xt_quota.3: memory alloc failure\n"); + return -ENOMEM; + } + + return 0; +} + +static void quota_mt2_destroy(const struct xt_mtdtor_param *par) +{ + struct xt_quota_mtinfo2 *q = par->matchinfo; + struct xt_quota_counter *e = q->master; + struct quota2_net *quota2_net = quota2_pernet(par->net); + + if (*q->name == '\0') { + kfree(e); + return; + } + + spin_lock_bh(&counter_list_lock); + if (!atomic_dec_and_test(&e->ref)) { + spin_unlock_bh(&counter_list_lock); + return; + } + + list_del(&e->list); + remove_proc_entry(e->name, quota2_net->proc_xt_quota); + spin_unlock_bh(&counter_list_lock); + kfree(e); +} + +static bool +quota_mt2(const struct sk_buff *skb, struct xt_action_param *par) +{ + struct xt_quota_mtinfo2 *q = (void *)par->matchinfo; + struct xt_quota_counter *e = q->master; + bool ret = q->flags & XT_QUOTA_INVERT; + + spin_lock_bh(&e->lock); + if (q->flags & XT_QUOTA_GROW) { + /* + * While no_change is pointless in "grow" mode, we will + * implement it here simply to have a consistent behavior. + */ + if (!(q->flags & XT_QUOTA_NO_CHANGE)) { + e->quota += (q->flags & XT_QUOTA_PACKET) ? 1 : skb->len; + q->quota = e->quota; + } + ret = true; + } else { + if (e->quota >= ((q->flags & XT_QUOTA_PACKET) ? 1 : skb->len)) { + if (!(q->flags & XT_QUOTA_NO_CHANGE)) + e->quota -= (q->flags & XT_QUOTA_PACKET) ? 1 : skb->len; + ret = !ret; + } else { + /* we do not allow even small packets from now on */ + if (!(q->flags & XT_QUOTA_NO_CHANGE)) + e->quota = 0; + /* We are transitioning, log that fact. */ + if (e->quota) { + quota2_log(xt_hooknum(par), + skb, + xt_in(par), + xt_out(par), + q->name); + } + } + q->quota = e->quota; + } + spin_unlock_bh(&e->lock); + return ret; +} + +static struct xt_match quota_mt2_reg[] __read_mostly = { + { + .name = "quota2", + .revision = 3, + .family = NFPROTO_IPV4, + .checkentry = quota_mt2_check, + .match = quota_mt2, + .destroy = quota_mt2_destroy, + .matchsize = sizeof(struct xt_quota_mtinfo2), + .me = THIS_MODULE, + }, + { + .name = "quota2", + .revision = 3, + .family = NFPROTO_IPV6, + .checkentry = quota_mt2_check, + .match = quota_mt2, + .destroy = quota_mt2_destroy, + .matchsize = sizeof(struct xt_quota_mtinfo2), + .me = THIS_MODULE, + }, +}; + +static int __net_init quota2_net_init(struct net *net) +{ + struct quota2_net *quota2_net = quota2_pernet(net); + INIT_LIST_HEAD("a2_net->counter_list); + +#ifdef CONFIG_NETFILTER_XT_MATCH_QUOTA2_LOG + nflognl = netlink_kernel_create(&init_net, NETLINK_NFLOG, NULL); + if (!nflognl) + return -ENOMEM; +#endif + + quota2_net->proc_xt_quota = proc_mkdir("xt_quota", net->proc_net); + if (quota2_net->proc_xt_quota == NULL) + return -EACCES; + return 0; +} + +static void __net_exit quota2_net_exit(struct net *net) +{ + struct quota2_net *quota2_net = quota2_pernet(net); + struct xt_quota_counter *e = NULL; + struct list_head *pos, *q; + + remove_proc_entry("xt_quota", net->proc_net); + + /* destroy counter_list while freeing it's content */ + spin_lock_bh(&counter_list_lock); + list_for_each_safe(pos, q, "a2_net->counter_list) { + e = list_entry(pos, struct xt_quota_counter, list); + list_del(pos); + kfree(e); + } + spin_unlock_bh(&counter_list_lock); +} + +static struct pernet_operations quota2_net_ops = { + .init = quota2_net_init, + .exit = quota2_net_exit, + .id = "a2_net_id, + .size = sizeof(struct quota2_net), +}; + +static int __init quota_mt2_init(void) +{ + int ret; + ret = register_pernet_subsys("a2_net_ops); + if (ret < 0) + return ret; + + ret = xt_register_matches(quota_mt2_reg, ARRAY_SIZE(quota_mt2_reg)); + if (ret < 0) + unregister_pernet_subsys("a2_net_ops); + + return ret; +} + +static void __exit quota_mt2_exit(void) +{ + xt_unregister_matches(quota_mt2_reg, ARRAY_SIZE(quota_mt2_reg)); + unregister_pernet_subsys("a2_net_ops); +} + +module_init(quota_mt2_init); +module_exit(quota_mt2_exit); +MODULE_DESCRIPTION("Xtables: countdown quota match; up counter"); +MODULE_AUTHOR("OpenHarmony development"); +MODULE_LICENSE("GPL"); +MODULE_ALIAS("ipt_quota2"); +MODULE_ALIAS("ip6t_quota2"); -- Gitee