From 3871aa16fda772f9be298c8c6553f6e38be4ae5d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Maciej=20=C5=BBenczykowski?= Date: Thu, 13 May 2021 15:32:18 -0700 Subject: [PATCH] ANDROID: core of xt_IDLETIMER send_nl_msg support MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This was reverted in ee6918c6f7ef8697012f8cc5bd77e8983ce313b5 due to conflicts with upstream, and this attempts to reapply the majority of that change in an upstream compatible fashion. Test: builds, and kernel net tests passes, booted on phone, but no real testing Bug: 183485987 Signed-off-by: Maciej Żenczykowski Change-Id: I0fa1da3cb38d37c6888444dc36d49e6d1828a855 --- net/netfilter/xt_IDLETIMER.c | 291 ++++++++++++++++++++++++++++++++--- 1 file changed, 271 insertions(+), 20 deletions(-) diff --git a/net/netfilter/xt_IDLETIMER.c b/net/netfilter/xt_IDLETIMER.c index c63edb18e79b..3720825e5db4 100644 --- a/net/netfilter/xt_IDLETIMER.c +++ b/net/netfilter/xt_IDLETIMER.c @@ -28,6 +28,11 @@ #include #include #include +#include +#include +#include + +#define NLMSG_MAX_SIZE 64 struct idletimer_tg { struct list_head entry; @@ -38,15 +43,112 @@ struct idletimer_tg { struct kobject *kobj; struct device_attribute attr; + struct timespec64 delayed_timer_trigger; + struct timespec64 last_modified_timer; + struct timespec64 last_suspend_time; + struct notifier_block pm_nb; + + int timeout; unsigned int refcnt; u8 timer_type; + + bool work_pending; + bool send_nl_msg; + bool active; + uid_t uid; + bool suspend_time_valid; }; static LIST_HEAD(idletimer_tg_list); static DEFINE_MUTEX(list_mutex); +static DEFINE_SPINLOCK(timestamp_lock); static struct kobject *idletimer_tg_kobj; +static bool check_for_delayed_trigger(struct idletimer_tg *timer, + struct timespec64 *ts) +{ + bool state; + struct timespec64 temp; + spin_lock_bh(×tamp_lock); + timer->work_pending = false; + if ((ts->tv_sec - timer->last_modified_timer.tv_sec) > timer->timeout || + timer->delayed_timer_trigger.tv_sec != 0) { + state = false; + temp.tv_sec = timer->timeout; + temp.tv_nsec = 0; + if (timer->delayed_timer_trigger.tv_sec != 0) { + temp = timespec64_add(timer->delayed_timer_trigger, + temp); + ts->tv_sec = temp.tv_sec; + ts->tv_nsec = temp.tv_nsec; + timer->delayed_timer_trigger.tv_sec = 0; + timer->work_pending = true; + schedule_work(&timer->work); + } else { + temp = timespec64_add(timer->last_modified_timer, temp); + ts->tv_sec = temp.tv_sec; + ts->tv_nsec = temp.tv_nsec; + } + } else { + state = timer->active; + } + spin_unlock_bh(×tamp_lock); + return state; +} + +static void notify_netlink_uevent(const char *iface, struct idletimer_tg *timer) +{ + char iface_msg[NLMSG_MAX_SIZE]; + char state_msg[NLMSG_MAX_SIZE]; + char timestamp_msg[NLMSG_MAX_SIZE]; + char uid_msg[NLMSG_MAX_SIZE]; + char *envp[] = { iface_msg, state_msg, timestamp_msg, uid_msg, NULL }; + int res; + struct timespec64 ts; + u64 time_ns; + bool state; + + res = snprintf(iface_msg, NLMSG_MAX_SIZE, "INTERFACE=%s", + iface); + if (NLMSG_MAX_SIZE <= res) { + pr_err("message too long (%d)", res); + return; + } + + ts = ktime_to_timespec64(ktime_get_boottime()); + state = check_for_delayed_trigger(timer, &ts); + res = snprintf(state_msg, NLMSG_MAX_SIZE, "STATE=%s", + state ? "active" : "inactive"); + + if (NLMSG_MAX_SIZE <= res) { + pr_err("message too long (%d)", res); + return; + } + + if (state) { + res = snprintf(uid_msg, NLMSG_MAX_SIZE, "UID=%u", timer->uid); + if (NLMSG_MAX_SIZE <= res) + pr_err("message too long (%d)", res); + } else { + res = snprintf(uid_msg, NLMSG_MAX_SIZE, "UID="); + if (NLMSG_MAX_SIZE <= res) + pr_err("message too long (%d)", res); + } + + time_ns = timespec64_to_ns(&ts); + res = snprintf(timestamp_msg, NLMSG_MAX_SIZE, "TIME_NS=%llu", time_ns); + if (NLMSG_MAX_SIZE <= res) { + timestamp_msg[0] = '\0'; + pr_err("message too long (%d)", res); + } + + pr_debug("putting nlmsg: <%s> <%s> <%s> <%s>\n", iface_msg, state_msg, + timestamp_msg, uid_msg); + kobject_uevent_env(idletimer_tg_kobj, KOBJ_CHANGE, envp); + return; +} + static struct idletimer_tg *__idletimer_tg_find_by_label(const char *label) { @@ -67,6 +169,7 @@ static ssize_t idletimer_tg_show(struct device *dev, unsigned long expires = 0; struct timespec64 ktimespec = {}; long time_diff = 0; + unsigned long now = jiffies; mutex_lock(&list_mutex); @@ -78,16 +181,20 @@ static ssize_t idletimer_tg_show(struct device *dev, time_diff = ktimespec.tv_sec; } else { expires = timer->timer.expires; - time_diff = jiffies_to_msecs(expires - jiffies) / 1000; + time_diff = jiffies_to_msecs(expires - now) / 1000; } } mutex_unlock(&list_mutex); - if (time_after(expires, jiffies) || ktimespec.tv_sec > 0) - return snprintf(buf, PAGE_SIZE, "%ld\n", time_diff); + if (time_after(expires, now) || ktimespec.tv_sec > 0) + return scnprintf(buf, PAGE_SIZE, "%ld\n", time_diff); - return snprintf(buf, PAGE_SIZE, "0\n"); + if (timer->send_nl_msg) + return scnprintf(buf, PAGE_SIZE, "0 %d\n", + jiffies_to_msecs(now - expires) / 1000); + + return scnprintf(buf, PAGE_SIZE, "0\n"); } static void idletimer_tg_work(struct work_struct *work) @@ -96,6 +203,9 @@ static void idletimer_tg_work(struct work_struct *work) work); sysfs_notify(idletimer_tg_kobj, NULL, timer->attr.attr.name); + + if (timer->send_nl_msg) + notify_netlink_uevent(timer->attr.attr.name, timer); } static void idletimer_tg_expired(struct timer_list *t) @@ -104,7 +214,62 @@ static void idletimer_tg_expired(struct timer_list *t) pr_debug("timer %s expired\n", timer->attr.attr.name); + spin_lock_bh(×tamp_lock); + timer->active = false; + timer->work_pending = true; schedule_work(&timer->work); + spin_unlock_bh(×tamp_lock); +} + +static int idletimer_resume(struct notifier_block *notifier, + unsigned long pm_event, void *unused) +{ + struct timespec64 ts; + unsigned long time_diff, now = jiffies; + struct idletimer_tg *timer = container_of(notifier, + struct idletimer_tg, pm_nb); + if (!timer) + return NOTIFY_DONE; + + switch (pm_event) { + case PM_SUSPEND_PREPARE: + timer->last_suspend_time = + ktime_to_timespec64(ktime_get_boottime()); + timer->suspend_time_valid = true; + break; + case PM_POST_SUSPEND: + if (!timer->suspend_time_valid) + break; + timer->suspend_time_valid = false; + + spin_lock_bh(×tamp_lock); + if (!timer->active) { + spin_unlock_bh(×tamp_lock); + break; + } + /* since jiffies are not updated when suspended now represents + * the time it would have suspended */ + if (time_after(timer->timer.expires, now)) { + ts = ktime_to_timespec64(ktime_get_boottime()); + ts = timespec64_sub(ts, timer->last_suspend_time); + time_diff = timespec64_to_jiffies(&ts); + if (timer->timer.expires > (time_diff + now)) { + mod_timer_pending(&timer->timer, + (timer->timer.expires - time_diff)); + } else { + del_timer(&timer->timer); + timer->timer.expires = 0; + timer->active = false; + timer->work_pending = true; + schedule_work(&timer->work); + } + } + spin_unlock_bh(×tamp_lock); + break; + default: + break; + } + return NOTIFY_DONE; } static enum alarmtimer_restart idletimer_tg_alarmproc(struct alarm *alarm, @@ -137,7 +302,7 @@ static int idletimer_tg_create(struct idletimer_tg_info *info) { int ret; - info->timer = kmalloc(sizeof(*info->timer), GFP_KERNEL); + info->timer = kzalloc(sizeof(*info->timer), GFP_KERNEL); if (!info->timer) { ret = -ENOMEM; goto out; @@ -163,12 +328,29 @@ static int idletimer_tg_create(struct idletimer_tg_info *info) } list_add(&info->timer->entry, &idletimer_tg_list); - - timer_setup(&info->timer->timer, idletimer_tg_expired, 0); + pr_debug("timer type value is 0."); + info->timer->timer_type = 0; info->timer->refcnt = 1; + info->timer->send_nl_msg = false; + info->timer->active = true; + info->timer->timeout = info->timeout; + + 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) + printk(KERN_WARNING "[%s] Failed to register pm notifier %d\n", + __func__, ret); INIT_WORK(&info->timer->work, idletimer_tg_work); + timer_setup(&info->timer->timer, idletimer_tg_expired, 0); mod_timer(&info->timer->timer, msecs_to_jiffies(info->timeout * 1000) + jiffies); @@ -186,7 +368,7 @@ static int idletimer_tg_create_v1(struct idletimer_tg_info_v1 *info) { int ret; - info->timer = kmalloc(sizeof(*info->timer), GFP_KERNEL); + info->timer = kzalloc(sizeof(*info->timer), GFP_KERNEL); if (!info->timer) { ret = -ENOMEM; goto out; @@ -218,6 +400,22 @@ static int idletimer_tg_create_v1(struct idletimer_tg_info_v1 *info) pr_debug("timer type value is %u", info->timer_type); info->timer->timer_type = info->timer_type; info->timer->refcnt = 1; + info->timer->send_nl_msg = (info->send_nl_msg != 0); + info->timer->active = true; + info->timer->timeout = info->timeout; + + 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) + printk(KERN_WARNING "[%s] Failed to register pm notifier %d\n", + __func__, ret); INIT_WORK(&info->timer->work, idletimer_tg_work); @@ -231,7 +429,7 @@ static int idletimer_tg_create_v1(struct idletimer_tg_info_v1 *info) } else { timer_setup(&info->timer->timer, idletimer_tg_expired, 0); mod_timer(&info->timer->timer, - msecs_to_jiffies(info->timeout * 1000) + jiffies); + msecs_to_jiffies(info->timeout * 1000) + jiffies); } return 0; @@ -244,6 +442,41 @@ out: return ret; } +static void reset_timer(struct idletimer_tg * const info_timer, + const __u32 info_timeout, + struct sk_buff *skb) +{ + unsigned long now = jiffies; + bool timer_prev; + + spin_lock_bh(×tamp_lock); + timer_prev = info_timer->active; + info_timer->active = true; + /* timer_prev is used to guard overflow problem in time_before*/ + if (!timer_prev || time_before(info_timer->timer.expires, now)) { + pr_debug("Starting Checkentry timer (Expired, Jiffies): %lu, %lu\n", + info_timer->timer.expires, now); + + /* Stores the uid resposible for waking up the radio */ + if (skb && (skb->sk)) { + info_timer->uid = from_kuid_munged(current_user_ns(), + sock_i_uid(skb_to_full_sk(skb))); + } + + /* checks if there is a pending inactive notification*/ + if (info_timer->work_pending) + info_timer->delayed_timer_trigger = info_timer->last_modified_timer; + else { + info_timer->work_pending = true; + schedule_work(&info_timer->work); + } + } + + info_timer->last_modified_timer = ktime_to_timespec64(ktime_get_boottime()); + mod_timer(&info_timer->timer, msecs_to_jiffies(info_timeout * 1000) + now); + spin_unlock_bh(×tamp_lock); +} + /* * The actual xt_tables plugin. */ @@ -251,12 +484,21 @@ static unsigned int idletimer_tg_target(struct sk_buff *skb, const struct xt_action_param *par) { const struct idletimer_tg_info *info = par->targinfo; + unsigned long now = jiffies; 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); + info->timer->active = true; + + if (time_before(info->timer->timer.expires, now)) { + schedule_work(&info->timer->work); + pr_debug("Starting timer %s (Expired, Jiffies): %lu, %lu\n", + info->label, info->timer->timer.expires, now); + } + + /* TODO: Avoid modifying timers on each packet */ + reset_timer(info->timer, info->timeout, skb); return XT_CONTINUE; } @@ -268,6 +510,7 @@ static unsigned int idletimer_tg_target_v1(struct sk_buff *skb, const struct xt_action_param *par) { const struct idletimer_tg_info_v1 *info = par->targinfo; + unsigned long now = jiffies; pr_debug("resetting timer %s, timeout period %u\n", info->label, info->timeout); @@ -276,8 +519,16 @@ static unsigned int idletimer_tg_target_v1(struct sk_buff *skb, 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); + info->timer->active = true; + + if (time_before(info->timer->timer.expires, now)) { + schedule_work(&info->timer->work); + pr_debug("Starting timer %s (Expired, Jiffies): %lu, %lu\n", + info->label, info->timer->timer.expires, now); + } + + /* TODO: Avoid modifying timers on each packet */ + reset_timer(info->timer, info->timeout, skb); } return XT_CONTINUE; @@ -321,9 +572,7 @@ static int idletimer_tg_checkentry(const struct xt_tgchk_param *par) 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); - + reset_timer(info->timer, info->timeout, NULL); pr_debug("increased refcnt of timer %s to %u\n", info->label, info->timer->refcnt); } else { @@ -385,8 +634,7 @@ static int idletimer_tg_checkentry_v1(const struct xt_tgchk_param *par) alarm_start_relative(&info->timer->alarm, tout); } } else { - mod_timer(&info->timer->timer, - msecs_to_jiffies(info->timeout * 1000) + jiffies); + reset_timer(info->timer, info->timeout, NULL); } pr_debug("increased refcnt of timer %s to %u\n", info->label, info->timer->refcnt); @@ -416,8 +664,9 @@ static void idletimer_tg_destroy(const struct xt_tgdtor_param *par) 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); + unregister_pm_notifier(&info->timer->pm_nb); + cancel_work_sync(&info->timer->work); kfree(info->timer->attr.attr.name); kfree(info->timer); } else { @@ -445,8 +694,9 @@ static void idletimer_tg_destroy_v1(const struct xt_tgdtor_param *par) } else { del_timer_sync(&info->timer->timer); } - cancel_work_sync(&info->timer->work); sysfs_remove_file(idletimer_tg_kobj, &info->timer->attr.attr); + unregister_pm_notifier(&info->timer->pm_nb); + cancel_work_sync(&info->timer->work); kfree(info->timer->attr.attr.name); kfree(info->timer); } else { @@ -542,3 +792,4 @@ MODULE_DESCRIPTION("Xtables: idle time monitor"); MODULE_LICENSE("GPL v2"); MODULE_ALIAS("ipt_IDLETIMER"); MODULE_ALIAS("ip6t_IDLETIMER"); +MODULE_ALIAS("arpt_IDLETIMER");