mirror of
https://git.kernel.org/pub/scm/linux/kernel/git/stable/linux.git
synced 2024-09-04 01:49:36 +00:00
![Thomas Gleixner](/assets/img/avatar_default.png)
Based on 1 normalized pattern(s): this program is free software you can redistribute it and or modify it under the terms of the gnu general public license version 2 as published by the free software foundation this program is distributed in the hope that it will be useful but without any warranty without even the implied warranty of merchantability or fitness for a particular purpose see the gnu general public license for more details you should have received a copy of the gnu general public license along with this program if not write to the free software foundation inc 51 franklin st fifth floor boston ma 02110 1301 usa extracted by the scancode license scanner the SPDX license identifier GPL-2.0-only has been chosen to replace the boilerplate/reference in 246 file(s). Signed-off-by: Thomas Gleixner <tglx@linutronix.de> Reviewed-by: Alexios Zavras <alexios.zavras@intel.com> Reviewed-by: Allison Randal <allison@lohutok.net> Cc: linux-spdx@vger.kernel.org Link: https://lkml.kernel.org/r/20190530000436.674189849@linutronix.de Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
317 lines
7.4 KiB
C
317 lines
7.4 KiB
C
// SPDX-License-Identifier: GPL-2.0-only
|
|
/*
|
|
* linux/net/netfilter/xt_IDLETIMER.c
|
|
*
|
|
* Netfilter module to trigger a timer when packet matches.
|
|
* After timer expires a kevent will be sent.
|
|
*
|
|
* Copyright (C) 2004, 2010 Nokia Corporation
|
|
* Written by Timo Teras <ext-timo.teras@nokia.com>
|
|
*
|
|
* Converted to x_tables and reworked for upstream inclusion
|
|
* by Luciano Coelho <luciano.coelho@nokia.com>
|
|
*
|
|
* Contact: Luciano Coelho <luciano.coelho@nokia.com>
|
|
*/
|
|
|
|
#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
|
|
|
|
#include <linux/module.h>
|
|
#include <linux/timer.h>
|
|
#include <linux/list.h>
|
|
#include <linux/mutex.h>
|
|
#include <linux/netfilter.h>
|
|
#include <linux/netfilter/x_tables.h>
|
|
#include <linux/netfilter/xt_IDLETIMER.h>
|
|
#include <linux/kdev_t.h>
|
|
#include <linux/kobject.h>
|
|
#include <linux/workqueue.h>
|
|
#include <linux/sysfs.h>
|
|
|
|
struct idletimer_tg {
|
|
struct list_head entry;
|
|
struct timer_list timer;
|
|
struct work_struct work;
|
|
|
|
struct kobject *kobj;
|
|
struct device_attribute attr;
|
|
|
|
unsigned int refcnt;
|
|
};
|
|
|
|
static LIST_HEAD(idletimer_tg_list);
|
|
static DEFINE_MUTEX(list_mutex);
|
|
|
|
static struct kobject *idletimer_tg_kobj;
|
|
|
|
static
|
|
struct idletimer_tg *__idletimer_tg_find_by_label(const char *label)
|
|
{
|
|
struct idletimer_tg *entry;
|
|
|
|
list_for_each_entry(entry, &idletimer_tg_list, entry) {
|
|
if (!strcmp(label, entry->attr.attr.name))
|
|
return entry;
|
|
}
|
|
|
|
return NULL;
|
|
}
|
|
|
|
static ssize_t idletimer_tg_show(struct device *dev,
|
|
struct device_attribute *attr, char *buf)
|
|
{
|
|
struct idletimer_tg *timer;
|
|
unsigned long expires = 0;
|
|
|
|
mutex_lock(&list_mutex);
|
|
|
|
timer = __idletimer_tg_find_by_label(attr->attr.name);
|
|
if (timer)
|
|
expires = timer->timer.expires;
|
|
|
|
mutex_unlock(&list_mutex);
|
|
|
|
if (time_after(expires, jiffies))
|
|
return sprintf(buf, "%u\n",
|
|
jiffies_to_msecs(expires - jiffies) / 1000);
|
|
|
|
return sprintf(buf, "0\n");
|
|
}
|
|
|
|
static void idletimer_tg_work(struct work_struct *work)
|
|
{
|
|
struct idletimer_tg *timer = container_of(work, struct idletimer_tg,
|
|
work);
|
|
|
|
sysfs_notify(idletimer_tg_kobj, NULL, timer->attr.attr.name);
|
|
}
|
|
|
|
static void idletimer_tg_expired(struct timer_list *t)
|
|
{
|
|
struct idletimer_tg *timer = from_timer(timer, t, timer);
|
|
|
|
pr_debug("timer %s expired\n", timer->attr.attr.name);
|
|
|
|
schedule_work(&timer->work);
|
|
}
|
|
|
|
static int idletimer_check_sysfs_name(const char *name, unsigned int size)
|
|
{
|
|
int ret;
|
|
|
|
ret = xt_check_proc_name(name, size);
|
|
if (ret < 0)
|
|
return ret;
|
|
|
|
if (!strcmp(name, "power") ||
|
|
!strcmp(name, "subsystem") ||
|
|
!strcmp(name, "uevent"))
|
|
return -EINVAL;
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int idletimer_tg_create(struct idletimer_tg_info *info)
|
|
{
|
|
int ret;
|
|
|
|
info->timer = kmalloc(sizeof(*info->timer), GFP_KERNEL);
|
|
if (!info->timer) {
|
|
ret = -ENOMEM;
|
|
goto out;
|
|
}
|
|
|
|
ret = idletimer_check_sysfs_name(info->label, sizeof(info->label));
|
|
if (ret < 0)
|
|
goto out_free_timer;
|
|
|
|
sysfs_attr_init(&info->timer->attr.attr);
|
|
info->timer->attr.attr.name = kstrdup(info->label, GFP_KERNEL);
|
|
if (!info->timer->attr.attr.name) {
|
|
ret = -ENOMEM;
|
|
goto out_free_timer;
|
|
}
|
|
info->timer->attr.attr.mode = 0444;
|
|
info->timer->attr.show = idletimer_tg_show;
|
|
|
|
ret = sysfs_create_file(idletimer_tg_kobj, &info->timer->attr.attr);
|
|
if (ret < 0) {
|
|
pr_debug("couldn't add file to sysfs");
|
|
goto out_free_attr;
|
|
}
|
|
|
|
list_add(&info->timer->entry, &idletimer_tg_list);
|
|
|
|
timer_setup(&info->timer->timer, idletimer_tg_expired, 0);
|
|
info->timer->refcnt = 1;
|
|
|
|
INIT_WORK(&info->timer->work, idletimer_tg_work);
|
|
|
|
mod_timer(&info->timer->timer,
|
|
msecs_to_jiffies(info->timeout * 1000) + jiffies);
|
|
|
|
return 0;
|
|
|
|
out_free_attr:
|
|
kfree(info->timer->attr.attr.name);
|
|
out_free_timer:
|
|
kfree(info->timer);
|
|
out:
|
|
return ret;
|
|
}
|
|
|
|
/*
|
|
* The actual xt_tables plugin.
|
|
*/
|
|
static unsigned int idletimer_tg_target(struct sk_buff *skb,
|
|
const struct xt_action_param *par)
|
|
{
|
|
const struct idletimer_tg_info *info = par->targinfo;
|
|
|
|
pr_debug("resetting timer %s, timeout period %u\n",
|
|
info->label, info->timeout);
|
|
|
|
mod_timer(&info->timer->timer,
|
|
msecs_to_jiffies(info->timeout * 1000) + jiffies);
|
|
|
|
return XT_CONTINUE;
|
|
}
|
|
|
|
static int idletimer_tg_checkentry(const struct xt_tgchk_param *par)
|
|
{
|
|
struct idletimer_tg_info *info = par->targinfo;
|
|
int ret;
|
|
|
|
pr_debug("checkentry targinfo%s\n", info->label);
|
|
|
|
if (info->timeout == 0) {
|
|
pr_debug("timeout value is zero\n");
|
|
return -EINVAL;
|
|
}
|
|
if (info->timeout >= INT_MAX / 1000) {
|
|
pr_debug("timeout value is too big\n");
|
|
return -EINVAL;
|
|
}
|
|
if (info->label[0] == '\0' ||
|
|
strnlen(info->label,
|
|
MAX_IDLETIMER_LABEL_SIZE) == MAX_IDLETIMER_LABEL_SIZE) {
|
|
pr_debug("label is empty or not nul-terminated\n");
|
|
return -EINVAL;
|
|
}
|
|
|
|
mutex_lock(&list_mutex);
|
|
|
|
info->timer = __idletimer_tg_find_by_label(info->label);
|
|
if (info->timer) {
|
|
info->timer->refcnt++;
|
|
mod_timer(&info->timer->timer,
|
|
msecs_to_jiffies(info->timeout * 1000) + jiffies);
|
|
|
|
pr_debug("increased refcnt of timer %s to %u\n",
|
|
info->label, info->timer->refcnt);
|
|
} else {
|
|
ret = idletimer_tg_create(info);
|
|
if (ret < 0) {
|
|
pr_debug("failed to create timer\n");
|
|
mutex_unlock(&list_mutex);
|
|
return ret;
|
|
}
|
|
}
|
|
|
|
mutex_unlock(&list_mutex);
|
|
return 0;
|
|
}
|
|
|
|
static void idletimer_tg_destroy(const struct xt_tgdtor_param *par)
|
|
{
|
|
const struct idletimer_tg_info *info = par->targinfo;
|
|
|
|
pr_debug("destroy targinfo %s\n", info->label);
|
|
|
|
mutex_lock(&list_mutex);
|
|
|
|
if (--info->timer->refcnt == 0) {
|
|
pr_debug("deleting timer %s\n", info->label);
|
|
|
|
list_del(&info->timer->entry);
|
|
del_timer_sync(&info->timer->timer);
|
|
cancel_work_sync(&info->timer->work);
|
|
sysfs_remove_file(idletimer_tg_kobj, &info->timer->attr.attr);
|
|
kfree(info->timer->attr.attr.name);
|
|
kfree(info->timer);
|
|
} else {
|
|
pr_debug("decreased refcnt of timer %s to %u\n",
|
|
info->label, info->timer->refcnt);
|
|
}
|
|
|
|
mutex_unlock(&list_mutex);
|
|
}
|
|
|
|
static struct xt_target idletimer_tg __read_mostly = {
|
|
.name = "IDLETIMER",
|
|
.family = NFPROTO_UNSPEC,
|
|
.target = idletimer_tg_target,
|
|
.targetsize = sizeof(struct idletimer_tg_info),
|
|
.usersize = offsetof(struct idletimer_tg_info, timer),
|
|
.checkentry = idletimer_tg_checkentry,
|
|
.destroy = idletimer_tg_destroy,
|
|
.me = THIS_MODULE,
|
|
};
|
|
|
|
static struct class *idletimer_tg_class;
|
|
|
|
static struct device *idletimer_tg_device;
|
|
|
|
static int __init idletimer_tg_init(void)
|
|
{
|
|
int err;
|
|
|
|
idletimer_tg_class = class_create(THIS_MODULE, "xt_idletimer");
|
|
err = PTR_ERR(idletimer_tg_class);
|
|
if (IS_ERR(idletimer_tg_class)) {
|
|
pr_debug("couldn't register device class\n");
|
|
goto out;
|
|
}
|
|
|
|
idletimer_tg_device = device_create(idletimer_tg_class, NULL,
|
|
MKDEV(0, 0), NULL, "timers");
|
|
err = PTR_ERR(idletimer_tg_device);
|
|
if (IS_ERR(idletimer_tg_device)) {
|
|
pr_debug("couldn't register system device\n");
|
|
goto out_class;
|
|
}
|
|
|
|
idletimer_tg_kobj = &idletimer_tg_device->kobj;
|
|
|
|
err = xt_register_target(&idletimer_tg);
|
|
if (err < 0) {
|
|
pr_debug("couldn't register xt target\n");
|
|
goto out_dev;
|
|
}
|
|
|
|
return 0;
|
|
out_dev:
|
|
device_destroy(idletimer_tg_class, MKDEV(0, 0));
|
|
out_class:
|
|
class_destroy(idletimer_tg_class);
|
|
out:
|
|
return err;
|
|
}
|
|
|
|
static void __exit idletimer_tg_exit(void)
|
|
{
|
|
xt_unregister_target(&idletimer_tg);
|
|
|
|
device_destroy(idletimer_tg_class, MKDEV(0, 0));
|
|
class_destroy(idletimer_tg_class);
|
|
}
|
|
|
|
module_init(idletimer_tg_init);
|
|
module_exit(idletimer_tg_exit);
|
|
|
|
MODULE_AUTHOR("Timo Teras <ext-timo.teras@nokia.com>");
|
|
MODULE_AUTHOR("Luciano Coelho <luciano.coelho@nokia.com>");
|
|
MODULE_DESCRIPTION("Xtables: idle time monitor");
|
|
MODULE_LICENSE("GPL v2");
|
|
MODULE_ALIAS("ipt_IDLETIMER");
|
|
MODULE_ALIAS("ip6t_IDLETIMER");
|