blob: fdf2649be5367e8f0a9e0c0c4c4dec392cdfdea8 [file] [log] [blame]
Subash Abhinov Kasiviswanathaneb5c1542017-02-13 12:00:42 -07001/*
2 * linux/net/netfilter/xt_HARDIDLETIMER.c
3 *
4 * Netfilter module to trigger a timer when packet matches.
5 * After timer expires a kevent will be sent.
6 *
7 * Copyright (c) 2014-2015, 2017 The Linux Foundation. All rights reserved.
8 *
9 * Copyright (C) 2004, 2010 Nokia Corporation
10 *
11 * Written by Timo Teras <ext-timo.teras@nokia.com>
12 *
13 * Converted to x_tables and reworked for upstream inclusion
14 * by Luciano Coelho <luciano.coelho@nokia.com>
15 *
16 * Contact: Luciano Coelho <luciano.coelho@nokia.com>
17 *
18 * This program is free software; you can redistribute it and/or
19 * modify it under the terms of the GNU General Public License
20 * version 2 as published by the Free Software Foundation.
21 *
22 * This program is distributed in the hope that it will be useful, but
23 * WITHOUT ANY WARRANTY; without even the implied warranty of
24 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
25 * General Public License for more details.
26 *
27 */
28
29#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
30
31#include <linux/module.h>
32#include <linux/timer.h>
33#include <linux/alarmtimer.h>
34#include <linux/list.h>
35#include <linux/mutex.h>
36#include <linux/netfilter.h>
37#include <linux/netfilter/x_tables.h>
38#include <linux/netfilter/xt_HARDIDLETIMER.h>
39#include <linux/kdev_t.h>
40#include <linux/kobject.h>
41#include <linux/skbuff.h>
42#include <linux/workqueue.h>
43#include <linux/sysfs.h>
44#include <net/net_namespace.h>
45
46struct hardidletimer_tg_attr {
47 struct attribute attr;
48 ssize_t (*show)(struct kobject *kobj,
49 struct attribute *attr, char *buf);
50};
51
52struct hardidletimer_tg {
53 struct list_head entry;
54 struct alarm alarm;
55 struct work_struct work;
56
57 struct kobject *kobj;
58 struct hardidletimer_tg_attr attr;
59
60 unsigned int refcnt;
61 bool send_nl_msg;
62 bool active;
63};
64
65static LIST_HEAD(hardidletimer_tg_list);
66static DEFINE_MUTEX(list_mutex);
67
68static struct kobject *hardidletimer_tg_kobj;
69
70static void notify_netlink_uevent(const char *iface,
71 struct hardidletimer_tg *timer)
72{
73 char iface_msg[NLMSG_MAX_SIZE];
74 char state_msg[NLMSG_MAX_SIZE];
Ashwanth Goli4d541ee2017-06-30 18:40:28 +053075 char *envp[] = { iface_msg, state_msg, NULL };
Subash Abhinov Kasiviswanathaneb5c1542017-02-13 12:00:42 -070076 int res;
77
78 res = snprintf(iface_msg, NLMSG_MAX_SIZE, "INTERFACE=%s",
79 iface);
80 if (res >= NLMSG_MAX_SIZE) {
81 pr_err("message too long (%d)", res);
82 return;
83 }
84 res = snprintf(state_msg, NLMSG_MAX_SIZE, "STATE=%s",
85 timer->active ? "active" : "inactive");
86 if (res >= NLMSG_MAX_SIZE) {
87 pr_err("message too long (%d)", res);
88 return;
89 }
90 pr_debug("putting nlmsg: <%s> <%s>\n", iface_msg, state_msg);
91 kobject_uevent_env(hardidletimer_tg_kobj, KOBJ_CHANGE, envp);
92}
93
94static
95struct hardidletimer_tg *__hardidletimer_tg_find_by_label(const char *label)
96{
97 struct hardidletimer_tg *entry;
98
99 WARN_ON(!label);
100
101 list_for_each_entry(entry, &hardidletimer_tg_list, entry) {
102 if (!strcmp(label, entry->attr.attr.name))
103 return entry;
104 }
105
106 return NULL;
107}
108
109static ssize_t hardidletimer_tg_show(struct kobject *kobj,
110 struct attribute *attr, char *buf)
111{
112 struct hardidletimer_tg *timer;
113 ktime_t expires;
114 struct timespec ktimespec;
115
116 memset(&ktimespec, 0, sizeof(struct timespec));
117 mutex_lock(&list_mutex);
118
119 timer = __hardidletimer_tg_find_by_label(attr->name);
120 if (timer) {
121 expires = alarm_expires_remaining(&timer->alarm);
122 ktimespec = ktime_to_timespec(expires);
123 }
124
125 mutex_unlock(&list_mutex);
126
127 if (ktimespec.tv_sec >= 0)
128 return snprintf(buf, PAGE_SIZE, "%ld\n", ktimespec.tv_sec);
129
130 if ((timer) && (timer->send_nl_msg))
131 return snprintf(buf, PAGE_SIZE, "0 %ld\n", ktimespec.tv_sec);
132 else
133 return snprintf(buf, PAGE_SIZE, "0\n");
134}
135
136static void hardidletimer_tg_work(struct work_struct *work)
137{
138 struct hardidletimer_tg *timer = container_of(work,
139 struct hardidletimer_tg, work);
140
141 sysfs_notify(hardidletimer_tg_kobj, NULL, timer->attr.attr.name);
142
143 if (timer->send_nl_msg)
144 notify_netlink_uevent(timer->attr.attr.name, timer);
145}
146
147static enum alarmtimer_restart hardidletimer_tg_alarmproc(struct alarm *alarm,
148 ktime_t now)
149{
150 struct hardidletimer_tg *timer = alarm->data;
151
152 pr_debug("alarm %s expired\n", timer->attr.attr.name);
153
154 timer->active = false;
155 schedule_work(&timer->work);
156 return ALARMTIMER_NORESTART;
157}
158
159static int hardidletimer_tg_create(struct hardidletimer_tg_info *info)
160{
161 int ret;
162 ktime_t tout;
163
164 info->timer = kmalloc(sizeof(*info->timer), GFP_KERNEL);
165 if (!info->timer) {
166 ret = -ENOMEM;
167 goto out;
168 }
169
170 info->timer->attr.attr.name = kstrdup(info->label, GFP_KERNEL);
171 if (!info->timer->attr.attr.name) {
172 ret = -ENOMEM;
173 goto out_free_timer;
174 }
175 info->timer->attr.attr.mode = 0444;
176 info->timer->attr.show = hardidletimer_tg_show;
177
178 ret = sysfs_create_file(hardidletimer_tg_kobj, &info->timer->attr.attr);
179 if (ret < 0) {
180 pr_debug("couldn't add file to sysfs");
181 goto out_free_attr;
182 }
Devi Sandeep Endluri V V3d6aa172017-06-05 16:34:11 +0530183 /* notify userspace */
184 kobject_uevent(hardidletimer_tg_kobj, KOBJ_ADD);
Subash Abhinov Kasiviswanathaneb5c1542017-02-13 12:00:42 -0700185
186 list_add(&info->timer->entry, &hardidletimer_tg_list);
187
188 alarm_init(&info->timer->alarm, ALARM_BOOTTIME,
189 hardidletimer_tg_alarmproc);
190 info->timer->alarm.data = info->timer;
191 info->timer->refcnt = 1;
192 info->timer->send_nl_msg = (info->send_nl_msg == 0) ? false : true;
193 info->timer->active = true;
194 tout = ktime_set(info->timeout, 0);
195 alarm_start_relative(&info->timer->alarm, tout);
196
197 INIT_WORK(&info->timer->work, hardidletimer_tg_work);
198
199 return 0;
200
201out_free_attr:
202 kfree(info->timer->attr.attr.name);
203out_free_timer:
204 kfree(info->timer);
205out:
206 return ret;
207}
208
209/* The actual xt_tables plugin. */
210static unsigned int hardidletimer_tg_target(struct sk_buff *skb,
211 const struct xt_action_param *par)
212{
213 const struct hardidletimer_tg_info *info = par->targinfo;
214 ktime_t tout;
215
216 pr_debug("resetting timer %s, timeout period %u\n",
217 info->label, info->timeout);
218
219 WARN_ON(!info->timer);
220
221 if (!info->timer->active) {
222 schedule_work(&info->timer->work);
223 pr_debug("Starting timer %s\n", info->label);
224 }
225
226 info->timer->active = true;
227 /* TODO: Avoid modifying timers on each packet */
228 tout = ktime_set(info->timeout, 0);
229 alarm_start_relative(&info->timer->alarm, tout);
230
231 return XT_CONTINUE;
232}
233
234static int hardidletimer_tg_checkentry(const struct xt_tgchk_param *par)
235{
236 struct hardidletimer_tg_info *info = par->targinfo;
237 int ret;
238 ktime_t tout;
Devi Sandeep Endluri V V2f539c42019-01-14 10:47:29 +0530239 struct timespec ktimespec;
240
241 memset(&ktimespec, 0, sizeof(struct timespec));
Subash Abhinov Kasiviswanathaneb5c1542017-02-13 12:00:42 -0700242
243 pr_debug("checkentry targinfo %s\n", info->label);
244
245 if (info->timeout == 0) {
246 pr_debug("timeout value is zero\n");
247 return -EINVAL;
248 }
249
250 if (info->label[0] == '\0' ||
251 strnlen(info->label, MAX_HARDIDLETIMER_LABEL_SIZE)
252 == MAX_HARDIDLETIMER_LABEL_SIZE) {
253 pr_debug("label is empty or not nul-terminated\n");
254 return -EINVAL;
255 }
256
257 mutex_lock(&list_mutex);
258
259 info->timer = __hardidletimer_tg_find_by_label(info->label);
260 if (info->timer) {
261 info->timer->refcnt++;
Devi Sandeep Endluri V V2f539c42019-01-14 10:47:29 +0530262 /* calculate remaining expiry time */
263 tout = alarm_expires_remaining(&info->timer->alarm);
264 ktimespec = ktime_to_timespec(tout);
Subash Abhinov Kasiviswanathaneb5c1542017-02-13 12:00:42 -0700265
Devi Sandeep Endluri V V2f539c42019-01-14 10:47:29 +0530266 if (ktimespec.tv_sec > 0) {
267 pr_debug("time_expiry_remaining %ld\n",
268 ktimespec.tv_sec);
269 alarm_start_relative(&info->timer->alarm, tout);
270 }
Subash Abhinov Kasiviswanathaneb5c1542017-02-13 12:00:42 -0700271
272 pr_debug("increased refcnt of timer %s to %u\n",
273 info->label, info->timer->refcnt);
274 } else {
275 ret = hardidletimer_tg_create(info);
276 if (ret < 0) {
277 pr_debug("failed to create timer\n");
278 mutex_unlock(&list_mutex);
279 return ret;
280 }
281 }
282
283 mutex_unlock(&list_mutex);
284
285 return 0;
286}
287
288static void hardidletimer_tg_destroy(const struct xt_tgdtor_param *par)
289{
290 const struct hardidletimer_tg_info *info = par->targinfo;
291
292 pr_debug("destroy targinfo %s\n", info->label);
293
294 mutex_lock(&list_mutex);
295
296 if (--info->timer->refcnt == 0) {
297 pr_debug("deleting timer %s\n", info->label);
298
299 list_del(&info->timer->entry);
300 alarm_cancel(&info->timer->alarm);
301 cancel_work_sync(&info->timer->work);
302 sysfs_remove_file(hardidletimer_tg_kobj,
303 &info->timer->attr.attr);
304 kfree(info->timer->attr.attr.name);
305 kfree(info->timer);
306 } else {
307 pr_debug("decreased refcnt of timer %s to %u\n",
308 info->label, info->timer->refcnt);
309 }
310
311 mutex_unlock(&list_mutex);
312}
313
314static struct xt_target hardidletimer_tg __read_mostly = {
315 .name = "HARDIDLETIMER",
316 .revision = 1,
317 .family = NFPROTO_UNSPEC,
318 .target = hardidletimer_tg_target,
319 .targetsize = sizeof(struct hardidletimer_tg_info),
320 .checkentry = hardidletimer_tg_checkentry,
321 .destroy = hardidletimer_tg_destroy,
322 .me = THIS_MODULE,
323};
324
325static struct class *hardidletimer_tg_class;
326
327static struct device *hardidletimer_tg_device;
328
329static int __init hardidletimer_tg_init(void)
330{
331 int err;
332
333 hardidletimer_tg_class = class_create(THIS_MODULE, "xt_hardidletimer");
334 err = PTR_ERR(hardidletimer_tg_class);
335 if (IS_ERR(hardidletimer_tg_class)) {
336 pr_debug("couldn't register device class\n");
337 goto out;
338 }
339
340 hardidletimer_tg_device = device_create(hardidletimer_tg_class, NULL,
341 MKDEV(0, 0), NULL, "timers");
342 err = PTR_ERR(hardidletimer_tg_device);
343 if (IS_ERR(hardidletimer_tg_device)) {
344 pr_debug("couldn't register system device\n");
345 goto out_class;
346 }
347
348 hardidletimer_tg_kobj = &hardidletimer_tg_device->kobj;
349
350 err = xt_register_target(&hardidletimer_tg);
351 if (err < 0) {
352 pr_debug("couldn't register xt target\n");
353 goto out_dev;
354 }
355
356 return 0;
357out_dev:
358 device_destroy(hardidletimer_tg_class, MKDEV(0, 0));
359out_class:
360 class_destroy(hardidletimer_tg_class);
361out:
362 return err;
363}
364
365static void __exit hardidletimer_tg_exit(void)
366{
367 xt_unregister_target(&hardidletimer_tg);
368
369 device_destroy(hardidletimer_tg_class, MKDEV(0, 0));
370 class_destroy(hardidletimer_tg_class);
371}
372
373module_init(hardidletimer_tg_init);
374module_exit(hardidletimer_tg_exit);
375
376MODULE_AUTHOR("Timo Teras <ext-timo.teras@nokia.com>");
377MODULE_AUTHOR("Luciano Coelho <luciano.coelho@nokia.com>");
378MODULE_DESCRIPTION("Xtables: idle time monitor");
379MODULE_LICENSE("GPL v2");
380