Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit 8d623c13 authored by Sauvik Saha's avatar Sauvik Saha
Browse files

netfilter: Include alarm type timer in idletimer



This patch adds option for alarm type timer which can be used to
identify when interfaces have been idle for a certain period of time.

If timer_type is set for alarm from userspace, alarm type timer is
used. If more than one rule uses the same timer label, alarm will
be restarted whenever any of the rules get a hit. If rule with same
timer label is added for an alarm, it will continue with remaining
expiry time and will never reset.

With this change adding/replacing rule with same label and different
timer type is not allowed.

When the alarm expires, the target module sends a sysfs notification
to the userspace, which can then decide what to do.
Compared to current timer, alarm type timer can send notifications
when CPU is in suspend too, to notify the timer expiry.

Change-Id: Ibf3881c5e5979c535c946d208a9a0f0b6131dd11
Signed-off-by: default avatarManoj Basapathi <manojbm@codeaurora.org>
Signed-off-by: default avatarSauvik Saha <ssaha@codeaurora.org>
parent 834d2b56
Loading
Loading
Loading
Loading
+14 −0
Original line number Diff line number Diff line
@@ -38,6 +38,7 @@

#define NL_EVENT_TYPE_INACTIVE 0
#define NL_EVENT_TYPE_ACTIVE 1
#define XT_IDLETIMER_ALARM 0x01

struct idletimer_tg_info {
	__u32 timeout;
@@ -51,4 +52,17 @@ struct idletimer_tg_info {
	struct idletimer_tg *timer __attribute__((aligned(8)));
};

struct idletimer_tg_info_v1 {
	__u32 timeout;

	char label[MAX_IDLETIMER_LABEL_SIZE];

	/* Use netlink messages for notification in addition to sysfs */
	__u8 send_nl_msg;

	__u8 timer_type;

	/* for kernel module internal use only */
	struct idletimer_tg *timer __attribute__((aligned(8)));
};
#endif
+255 −15
Original line number Diff line number Diff line
@@ -19,6 +19,7 @@

#include <linux/module.h>
#include <linux/timer.h>
#include <linux/alarmtimer.h>
#include <linux/list.h>
#include <linux/mutex.h>
#include <linux/netfilter.h>
@@ -58,6 +59,9 @@ struct idletimer_tg {
	bool active;
	uid_t uid;
	bool suspend_time_valid;
	u8 timer_type;

	struct alarm alarm;
};

static LIST_HEAD(idletimer_tg_list);
@@ -171,24 +175,35 @@ static ssize_t idletimer_tg_show(struct device *dev,
	struct idletimer_tg *timer;
	unsigned long expires = 0;
	unsigned long now = jiffies;
	struct timespec64 ktimespec = {};
	long time_diff = 0;

	mutex_lock(&list_mutex);

	timer =	__idletimer_tg_find_by_label(attr->attr.name);
	if (timer)
	if (timer) {
		if (timer->timer_type & XT_IDLETIMER_ALARM) {
			ktime_t expires_alarm =
					alarm_expires_remaining(&timer->alarm);

			ktimespec = ktime_to_timespec64(expires_alarm);
			time_diff = ktimespec.tv_sec;
		} else {
			expires = timer->timer.expires;
			time_diff = jiffies_to_msecs(expires -
						     now) / 1000;
		}
	}

	mutex_unlock(&list_mutex);

	if (time_after(expires, now))
		return sprintf(buf, "%u\n",
			       jiffies_to_msecs(expires - now) / 1000);
	if (time_after(expires, jiffies) || ktimespec.tv_sec > 0)
		return scnprintf(buf, PAGE_SIZE, "%ld\n", time_diff);

	if (timer->send_nl_msg)
		return sprintf(buf, "0 %d\n",
			jiffies_to_msecs(now - expires) / 1000);
		return scnprintf(buf, PAGE_SIZE, "%ld\n", time_diff);
	else
		return sprintf(buf, "0\n");
		return scnprintf(buf, PAGE_SIZE, "0\n");
}

static void idletimer_tg_work(struct work_struct *work)
@@ -214,6 +229,16 @@ static void idletimer_tg_expired(struct timer_list *t)
	spin_unlock_bh(&timestamp_lock);
}

static enum alarmtimer_restart idletimer_tg_alarmproc(struct alarm *alarm,
						      ktime_t now)
{
	struct idletimer_tg *timer = alarm->data;

	pr_debug("alarm %s expired\n", timer->attr.attr.name);
	schedule_work(&timer->work);
	return ALARMTIMER_NORESTART;
}

static int idletimer_resume(struct notifier_block *notifier,
		unsigned long pm_event, void *unused)
{
@@ -345,6 +370,85 @@ static int idletimer_tg_create(struct idletimer_tg_info *info)
	return ret;
}

static int idletimer_tg_create_v1(struct idletimer_tg_info_v1 *info)
{
	int ret;

	info->timer = kzalloc(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\n");
		goto out_free_attr;
	}

	/*  notify userspace  */
	kobject_uevent(idletimer_tg_kobj, KOBJ_ADD);

	list_add(&info->timer->entry, &idletimer_tg_list);
		pr_debug("timer type value is %u\n", info->timer_type);
	info->timer->timer_type = info->timer_type;
	info->timer->refcnt = 1;
	info->timer->send_nl_msg = (info->send_nl_msg == 0) ? false : true;
	info->timer->active = true;
	info->timer->timeout = info->timeout;

	INIT_WORK(&info->timer->work, idletimer_tg_work);

	if (info->timer->timer_type & XT_IDLETIMER_ALARM) {
		ktime_t tout;

		alarm_init(&info->timer->alarm, ALARM_BOOTTIME,
			   idletimer_tg_alarmproc);

		info->timer->alarm.data = info->timer;
		tout = ktime_set(info->timeout, 0);
		alarm_start_relative(&info->timer->alarm, tout);
	} else {
		info->timer->delayed_timer_trigger.tv_sec = 0;
		info->timer->delayed_timer_trigger.tv_nsec = 0;
		info->timer->work_pending = false;
		info->timer->uid = 0;
		info->timer->last_modified_timer =
		ktime_to_timespec64(ktime_get_boottime());

		info->timer->pm_nb.notifier_call = idletimer_resume;
		ret = register_pm_notifier(&info->timer->pm_nb);
		if (ret)
			pr_debug("[%s] Failed to register pm notifier %d\n",
				 __func__, ret);
		timer_setup(&info->timer->timer, idletimer_tg_expired, 0);
		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;
}

static void reset_timer(const struct idletimer_tg_info *info,
			struct sk_buff *skb)
{
@@ -408,13 +512,32 @@ static unsigned int idletimer_tg_target(struct sk_buff *skb,
	return XT_CONTINUE;
}

static int idletimer_tg_checkentry(const struct xt_tgchk_param *par)
/* The actual xt_tables plugin */
static unsigned int idletimer_tg_target_v1(struct sk_buff *skb,
					   const struct xt_action_param *par)
{
	struct idletimer_tg_info *info = par->targinfo;
	int ret;
	const struct idletimer_tg_info_v1 *info = par->targinfo;

	pr_debug("checkentry targinfo %s\n", info->label);
	pr_debug("resetting timer %s, timeout period %u\n",
		 info->label, info->timeout);

	BUG_ON(!info->timer);

	if (info->timer->timer_type & XT_IDLETIMER_ALARM) {
		ktime_t tout = ktime_set(info->timeout, 0);

		alarm_start_relative(&info->timer->alarm, tout);

	} else {
		mod_timer(&info->timer->timer,
			  msecs_to_jiffies(info->timeout * 1000) + jiffies);
	}

	return XT_CONTINUE;
}

static int idletimer_tg_helper(struct idletimer_tg_info *info)
{
	if (info->timeout == 0) {
		pr_debug("timeout value is zero\n");
		return -EINVAL;
@@ -429,7 +552,21 @@ static int idletimer_tg_checkentry(const struct xt_tgchk_param *par)
		pr_debug("label is empty or not nul-terminated\n");
		return -EINVAL;
	}
	return 0;
}

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);

	ret = idletimer_tg_helper(info);
	if (ret < 0) {
		pr_debug("checkentry helper return invalid\n");
		return -EINVAL;
	}
	mutex_lock(&list_mutex);

	info->timer = __idletimer_tg_find_by_label(info->label);
@@ -448,7 +585,66 @@ static int idletimer_tg_checkentry(const struct xt_tgchk_param *par)
	}

	mutex_unlock(&list_mutex);
	return 0;
}

static int idletimer_tg_checkentry_v1(const struct xt_tgchk_param *par)
{
	struct idletimer_tg_info_v1 *info = par->targinfo;
	int ret;

	pr_debug("checkentry targinfo%s\n", info->label);

	ret = idletimer_tg_helper((struct idletimer_tg_info *)info);
	if (ret < 0) {
		pr_debug("checkentry helper return invalid\n");
		return -EINVAL;
	}

	if (info->timer_type > XT_IDLETIMER_ALARM) {
		pr_debug("invalid value for timer type\n");
		return -EINVAL;
	}

	mutex_lock(&list_mutex);

	info->timer = __idletimer_tg_find_by_label(info->label);
	if (info->timer) {
		if (info->timer->timer_type != info->timer_type) {
			pr_debug("Adding/Replacing rule with same label and different timer type is not allowed\n");
			mutex_unlock(&list_mutex);
			return -EINVAL;
		}

		info->timer->refcnt++;
		if (info->timer_type & XT_IDLETIMER_ALARM) {
			/* calculate remaining expiry time */
			ktime_t tout =
				alarm_expires_remaining(&info->timer->alarm);
			struct timespec64 ktimespec = ktime_to_timespec64(tout);

			if (ktimespec.tv_sec > 0) {
				pr_debug("time_expiry_remaining %lld\n",
					 ktimespec.tv_sec);
				alarm_start_relative(&info->timer->alarm, tout);
			}
		} else {
			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_v1(info);
		if (ret < 0) {
			pr_debug("failed to create timer\n");
			mutex_unlock(&list_mutex);
			return ret;
		}
	}

	mutex_unlock(&list_mutex);
	return 0;
}

@@ -478,7 +674,38 @@ static void idletimer_tg_destroy(const struct xt_tgdtor_param *par)
	mutex_unlock(&list_mutex);
}

static struct xt_target idletimer_tg __read_mostly = {
static void idletimer_tg_destroy_v1(const struct xt_tgdtor_param *par)
{
	const struct idletimer_tg_info_v1 *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);
		if (info->timer->timer_type & XT_IDLETIMER_ALARM) {
			alarm_cancel(&info->timer->alarm);
		} else {
			del_timer_sync(&info->timer->timer);
			unregister_pm_notifier(&info->timer->pm_nb);
		}
		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",
	.revision	= 1,
	.family		= NFPROTO_UNSPEC,
@@ -488,6 +715,18 @@ static struct xt_target idletimer_tg __read_mostly = {
	.checkentry	= idletimer_tg_checkentry,
	.destroy        = idletimer_tg_destroy,
	.me		= THIS_MODULE,
	},
	{
	.name		= "IDLETIMER",
	.family		= NFPROTO_UNSPEC,
	.revision	= 2,
	.target		= idletimer_tg_target_v1,
	.targetsize     = sizeof(struct idletimer_tg_info_v1),
	.usersize	= offsetof(struct idletimer_tg_info_v1, timer),
	.checkentry	= idletimer_tg_checkentry_v1,
	.destroy        = idletimer_tg_destroy_v1,
	.me		= THIS_MODULE,
	},
};

static struct class *idletimer_tg_class;
@@ -515,7 +754,8 @@ static int __init idletimer_tg_init(void)

	idletimer_tg_kobj = &idletimer_tg_device->kobj;

	err = xt_register_target(&idletimer_tg);
	err = xt_register_targets(idletimer_tg, ARRAY_SIZE(idletimer_tg));

	if (err < 0) {
		pr_debug("couldn't register xt target\n");
		goto out_dev;
@@ -532,7 +772,7 @@ static int __init idletimer_tg_init(void)

static void __exit idletimer_tg_exit(void)
{
	xt_unregister_target(&idletimer_tg);
	xt_unregister_targets(idletimer_tg, ARRAY_SIZE(idletimer_tg));

	device_destroy(idletimer_tg_class, MKDEV(0, 0));
	class_destroy(idletimer_tg_class);