1 | // SPDX-License-Identifier: GPL-2.0-only |
2 | /* |
3 | * linux/net/netfilter/xt_IDLETIMER.c |
4 | * |
5 | * Netfilter module to trigger a timer when packet matches. |
6 | * After timer expires a kevent will be sent. |
7 | * |
8 | * Copyright (C) 2004, 2010 Nokia Corporation |
9 | * Written by Timo Teras <ext-timo.teras@nokia.com> |
10 | * |
11 | * Converted to x_tables and reworked for upstream inclusion |
12 | * by Luciano Coelho <luciano.coelho@nokia.com> |
13 | * |
14 | * Contact: Luciano Coelho <luciano.coelho@nokia.com> |
15 | */ |
16 | |
17 | #define pr_fmt(fmt) KBUILD_MODNAME ": " fmt |
18 | |
19 | #include <linux/module.h> |
20 | #include <linux/timer.h> |
21 | #include <linux/alarmtimer.h> |
22 | #include <linux/list.h> |
23 | #include <linux/mutex.h> |
24 | #include <linux/netfilter.h> |
25 | #include <linux/netfilter/x_tables.h> |
26 | #include <linux/netfilter/xt_IDLETIMER.h> |
27 | #include <linux/kdev_t.h> |
28 | #include <linux/kobject.h> |
29 | #include <linux/workqueue.h> |
30 | #include <linux/sysfs.h> |
31 | |
32 | struct idletimer_tg { |
33 | struct list_head entry; |
34 | struct alarm alarm; |
35 | struct timer_list timer; |
36 | struct work_struct work; |
37 | |
38 | struct kobject *kobj; |
39 | struct device_attribute attr; |
40 | |
41 | unsigned int refcnt; |
42 | u8 timer_type; |
43 | }; |
44 | |
45 | static LIST_HEAD(idletimer_tg_list); |
46 | static DEFINE_MUTEX(list_mutex); |
47 | |
48 | static struct kobject *idletimer_tg_kobj; |
49 | |
50 | static |
51 | struct idletimer_tg *__idletimer_tg_find_by_label(const char *label) |
52 | { |
53 | struct idletimer_tg *entry; |
54 | |
55 | list_for_each_entry(entry, &idletimer_tg_list, entry) { |
56 | if (!strcmp(label, entry->attr.attr.name)) |
57 | return entry; |
58 | } |
59 | |
60 | return NULL; |
61 | } |
62 | |
63 | static ssize_t idletimer_tg_show(struct device *dev, |
64 | struct device_attribute *attr, char *buf) |
65 | { |
66 | struct idletimer_tg *timer; |
67 | unsigned long expires = 0; |
68 | struct timespec64 ktimespec = {}; |
69 | long time_diff = 0; |
70 | |
71 | mutex_lock(&list_mutex); |
72 | |
73 | timer = __idletimer_tg_find_by_label(label: attr->attr.name); |
74 | if (timer) { |
75 | if (timer->timer_type & XT_IDLETIMER_ALARM) { |
76 | ktime_t expires_alarm = alarm_expires_remaining(alarm: &timer->alarm); |
77 | ktimespec = ktime_to_timespec64(expires_alarm); |
78 | time_diff = ktimespec.tv_sec; |
79 | } else { |
80 | expires = timer->timer.expires; |
81 | time_diff = jiffies_to_msecs(j: expires - jiffies) / 1000; |
82 | } |
83 | } |
84 | |
85 | mutex_unlock(lock: &list_mutex); |
86 | |
87 | if (time_after(expires, jiffies) || ktimespec.tv_sec > 0) |
88 | return sysfs_emit(buf, fmt: "%ld\n" , time_diff); |
89 | |
90 | return sysfs_emit(buf, fmt: "0\n" ); |
91 | } |
92 | |
93 | static void idletimer_tg_work(struct work_struct *work) |
94 | { |
95 | struct idletimer_tg *timer = container_of(work, struct idletimer_tg, |
96 | work); |
97 | |
98 | sysfs_notify(kobj: idletimer_tg_kobj, NULL, attr: timer->attr.attr.name); |
99 | } |
100 | |
101 | static void idletimer_tg_expired(struct timer_list *t) |
102 | { |
103 | struct idletimer_tg *timer = from_timer(timer, t, timer); |
104 | |
105 | pr_debug("timer %s expired\n" , timer->attr.attr.name); |
106 | |
107 | schedule_work(work: &timer->work); |
108 | } |
109 | |
110 | static enum alarmtimer_restart idletimer_tg_alarmproc(struct alarm *alarm, |
111 | ktime_t now) |
112 | { |
113 | struct idletimer_tg *timer = alarm->data; |
114 | |
115 | pr_debug("alarm %s expired\n" , timer->attr.attr.name); |
116 | schedule_work(work: &timer->work); |
117 | return ALARMTIMER_NORESTART; |
118 | } |
119 | |
120 | static int idletimer_check_sysfs_name(const char *name, unsigned int size) |
121 | { |
122 | int ret; |
123 | |
124 | ret = xt_check_proc_name(name, size); |
125 | if (ret < 0) |
126 | return ret; |
127 | |
128 | if (!strcmp(name, "power" ) || |
129 | !strcmp(name, "subsystem" ) || |
130 | !strcmp(name, "uevent" )) |
131 | return -EINVAL; |
132 | |
133 | return 0; |
134 | } |
135 | |
136 | static int idletimer_tg_create(struct idletimer_tg_info *info) |
137 | { |
138 | int ret; |
139 | |
140 | info->timer = kzalloc(size: sizeof(*info->timer), GFP_KERNEL); |
141 | if (!info->timer) { |
142 | ret = -ENOMEM; |
143 | goto out; |
144 | } |
145 | |
146 | ret = idletimer_check_sysfs_name(name: info->label, size: sizeof(info->label)); |
147 | if (ret < 0) |
148 | goto out_free_timer; |
149 | |
150 | sysfs_attr_init(&info->timer->attr.attr); |
151 | info->timer->attr.attr.name = kstrdup(s: info->label, GFP_KERNEL); |
152 | if (!info->timer->attr.attr.name) { |
153 | ret = -ENOMEM; |
154 | goto out_free_timer; |
155 | } |
156 | info->timer->attr.attr.mode = 0444; |
157 | info->timer->attr.show = idletimer_tg_show; |
158 | |
159 | ret = sysfs_create_file(kobj: idletimer_tg_kobj, attr: &info->timer->attr.attr); |
160 | if (ret < 0) { |
161 | pr_debug("couldn't add file to sysfs" ); |
162 | goto out_free_attr; |
163 | } |
164 | |
165 | list_add(new: &info->timer->entry, head: &idletimer_tg_list); |
166 | |
167 | timer_setup(&info->timer->timer, idletimer_tg_expired, 0); |
168 | info->timer->refcnt = 1; |
169 | |
170 | INIT_WORK(&info->timer->work, idletimer_tg_work); |
171 | |
172 | mod_timer(timer: &info->timer->timer, |
173 | expires: msecs_to_jiffies(m: info->timeout * 1000) + jiffies); |
174 | |
175 | return 0; |
176 | |
177 | out_free_attr: |
178 | kfree(objp: info->timer->attr.attr.name); |
179 | out_free_timer: |
180 | kfree(objp: info->timer); |
181 | out: |
182 | return ret; |
183 | } |
184 | |
185 | static int idletimer_tg_create_v1(struct idletimer_tg_info_v1 *info) |
186 | { |
187 | int ret; |
188 | |
189 | info->timer = kmalloc(size: sizeof(*info->timer), GFP_KERNEL); |
190 | if (!info->timer) { |
191 | ret = -ENOMEM; |
192 | goto out; |
193 | } |
194 | |
195 | ret = idletimer_check_sysfs_name(name: info->label, size: sizeof(info->label)); |
196 | if (ret < 0) |
197 | goto out_free_timer; |
198 | |
199 | sysfs_attr_init(&info->timer->attr.attr); |
200 | info->timer->attr.attr.name = kstrdup(s: info->label, GFP_KERNEL); |
201 | if (!info->timer->attr.attr.name) { |
202 | ret = -ENOMEM; |
203 | goto out_free_timer; |
204 | } |
205 | info->timer->attr.attr.mode = 0444; |
206 | info->timer->attr.show = idletimer_tg_show; |
207 | |
208 | ret = sysfs_create_file(kobj: idletimer_tg_kobj, attr: &info->timer->attr.attr); |
209 | if (ret < 0) { |
210 | pr_debug("couldn't add file to sysfs" ); |
211 | goto out_free_attr; |
212 | } |
213 | |
214 | /* notify userspace */ |
215 | kobject_uevent(kobj: idletimer_tg_kobj,action: KOBJ_ADD); |
216 | |
217 | list_add(new: &info->timer->entry, head: &idletimer_tg_list); |
218 | pr_debug("timer type value is %u" , info->timer_type); |
219 | info->timer->timer_type = info->timer_type; |
220 | info->timer->refcnt = 1; |
221 | |
222 | INIT_WORK(&info->timer->work, idletimer_tg_work); |
223 | |
224 | if (info->timer->timer_type & XT_IDLETIMER_ALARM) { |
225 | ktime_t tout; |
226 | alarm_init(alarm: &info->timer->alarm, type: ALARM_BOOTTIME, |
227 | function: idletimer_tg_alarmproc); |
228 | info->timer->alarm.data = info->timer; |
229 | tout = ktime_set(secs: info->timeout, nsecs: 0); |
230 | alarm_start_relative(alarm: &info->timer->alarm, start: tout); |
231 | } else { |
232 | timer_setup(&info->timer->timer, idletimer_tg_expired, 0); |
233 | mod_timer(timer: &info->timer->timer, |
234 | expires: msecs_to_jiffies(m: info->timeout * 1000) + jiffies); |
235 | } |
236 | |
237 | return 0; |
238 | |
239 | out_free_attr: |
240 | kfree(objp: info->timer->attr.attr.name); |
241 | out_free_timer: |
242 | kfree(objp: info->timer); |
243 | out: |
244 | return ret; |
245 | } |
246 | |
247 | /* |
248 | * The actual xt_tables plugin. |
249 | */ |
250 | static unsigned int idletimer_tg_target(struct sk_buff *skb, |
251 | const struct xt_action_param *par) |
252 | { |
253 | const struct idletimer_tg_info *info = par->targinfo; |
254 | |
255 | pr_debug("resetting timer %s, timeout period %u\n" , |
256 | info->label, info->timeout); |
257 | |
258 | mod_timer(timer: &info->timer->timer, |
259 | expires: msecs_to_jiffies(m: info->timeout * 1000) + jiffies); |
260 | |
261 | return XT_CONTINUE; |
262 | } |
263 | |
264 | /* |
265 | * The actual xt_tables plugin. |
266 | */ |
267 | static unsigned int idletimer_tg_target_v1(struct sk_buff *skb, |
268 | const struct xt_action_param *par) |
269 | { |
270 | const struct idletimer_tg_info_v1 *info = par->targinfo; |
271 | |
272 | pr_debug("resetting timer %s, timeout period %u\n" , |
273 | info->label, info->timeout); |
274 | |
275 | if (info->timer->timer_type & XT_IDLETIMER_ALARM) { |
276 | ktime_t tout = ktime_set(secs: info->timeout, nsecs: 0); |
277 | alarm_start_relative(alarm: &info->timer->alarm, start: tout); |
278 | } else { |
279 | mod_timer(timer: &info->timer->timer, |
280 | expires: msecs_to_jiffies(m: info->timeout * 1000) + jiffies); |
281 | } |
282 | |
283 | return XT_CONTINUE; |
284 | } |
285 | |
286 | static int idletimer_tg_helper(struct idletimer_tg_info *info) |
287 | { |
288 | if (info->timeout == 0) { |
289 | pr_debug("timeout value is zero\n" ); |
290 | return -EINVAL; |
291 | } |
292 | if (info->timeout >= INT_MAX / 1000) { |
293 | pr_debug("timeout value is too big\n" ); |
294 | return -EINVAL; |
295 | } |
296 | if (info->label[0] == '\0' || |
297 | strnlen(p: info->label, |
298 | MAX_IDLETIMER_LABEL_SIZE) == MAX_IDLETIMER_LABEL_SIZE) { |
299 | pr_debug("label is empty or not nul-terminated\n" ); |
300 | return -EINVAL; |
301 | } |
302 | return 0; |
303 | } |
304 | |
305 | |
306 | static int idletimer_tg_checkentry(const struct xt_tgchk_param *par) |
307 | { |
308 | struct idletimer_tg_info *info = par->targinfo; |
309 | int ret; |
310 | |
311 | pr_debug("checkentry targinfo%s\n" , info->label); |
312 | |
313 | ret = idletimer_tg_helper(info); |
314 | if(ret < 0) |
315 | { |
316 | pr_debug("checkentry helper return invalid\n" ); |
317 | return -EINVAL; |
318 | } |
319 | mutex_lock(&list_mutex); |
320 | |
321 | info->timer = __idletimer_tg_find_by_label(label: info->label); |
322 | if (info->timer) { |
323 | info->timer->refcnt++; |
324 | mod_timer(timer: &info->timer->timer, |
325 | expires: msecs_to_jiffies(m: info->timeout * 1000) + jiffies); |
326 | |
327 | pr_debug("increased refcnt of timer %s to %u\n" , |
328 | info->label, info->timer->refcnt); |
329 | } else { |
330 | ret = idletimer_tg_create(info); |
331 | if (ret < 0) { |
332 | pr_debug("failed to create timer\n" ); |
333 | mutex_unlock(lock: &list_mutex); |
334 | return ret; |
335 | } |
336 | } |
337 | |
338 | mutex_unlock(lock: &list_mutex); |
339 | return 0; |
340 | } |
341 | |
342 | static int idletimer_tg_checkentry_v1(const struct xt_tgchk_param *par) |
343 | { |
344 | struct idletimer_tg_info_v1 *info = par->targinfo; |
345 | int ret; |
346 | |
347 | pr_debug("checkentry targinfo%s\n" , info->label); |
348 | |
349 | if (info->send_nl_msg) |
350 | return -EOPNOTSUPP; |
351 | |
352 | ret = idletimer_tg_helper(info: (struct idletimer_tg_info *)info); |
353 | if(ret < 0) |
354 | { |
355 | pr_debug("checkentry helper return invalid\n" ); |
356 | return -EINVAL; |
357 | } |
358 | |
359 | if (info->timer_type > XT_IDLETIMER_ALARM) { |
360 | pr_debug("invalid value for timer type\n" ); |
361 | return -EINVAL; |
362 | } |
363 | |
364 | mutex_lock(&list_mutex); |
365 | |
366 | info->timer = __idletimer_tg_find_by_label(label: info->label); |
367 | if (info->timer) { |
368 | if (info->timer->timer_type != info->timer_type) { |
369 | pr_debug("Adding/Replacing rule with same label and different timer type is not allowed\n" ); |
370 | mutex_unlock(lock: &list_mutex); |
371 | return -EINVAL; |
372 | } |
373 | |
374 | info->timer->refcnt++; |
375 | if (info->timer_type & XT_IDLETIMER_ALARM) { |
376 | /* calculate remaining expiry time */ |
377 | ktime_t tout = alarm_expires_remaining(alarm: &info->timer->alarm); |
378 | struct timespec64 ktimespec = ktime_to_timespec64(tout); |
379 | |
380 | if (ktimespec.tv_sec > 0) { |
381 | pr_debug("time_expiry_remaining %lld\n" , |
382 | ktimespec.tv_sec); |
383 | alarm_start_relative(alarm: &info->timer->alarm, start: tout); |
384 | } |
385 | } else { |
386 | mod_timer(timer: &info->timer->timer, |
387 | expires: msecs_to_jiffies(m: info->timeout * 1000) + jiffies); |
388 | } |
389 | pr_debug("increased refcnt of timer %s to %u\n" , |
390 | info->label, info->timer->refcnt); |
391 | } else { |
392 | ret = idletimer_tg_create_v1(info); |
393 | if (ret < 0) { |
394 | pr_debug("failed to create timer\n" ); |
395 | mutex_unlock(lock: &list_mutex); |
396 | return ret; |
397 | } |
398 | } |
399 | |
400 | mutex_unlock(lock: &list_mutex); |
401 | return 0; |
402 | } |
403 | |
404 | static void idletimer_tg_destroy(const struct xt_tgdtor_param *par) |
405 | { |
406 | const struct idletimer_tg_info *info = par->targinfo; |
407 | |
408 | pr_debug("destroy targinfo %s\n" , info->label); |
409 | |
410 | mutex_lock(&list_mutex); |
411 | |
412 | if (--info->timer->refcnt == 0) { |
413 | pr_debug("deleting timer %s\n" , info->label); |
414 | |
415 | list_del(entry: &info->timer->entry); |
416 | timer_shutdown_sync(timer: &info->timer->timer); |
417 | cancel_work_sync(work: &info->timer->work); |
418 | sysfs_remove_file(kobj: idletimer_tg_kobj, attr: &info->timer->attr.attr); |
419 | kfree(objp: info->timer->attr.attr.name); |
420 | kfree(objp: info->timer); |
421 | } else { |
422 | pr_debug("decreased refcnt of timer %s to %u\n" , |
423 | info->label, info->timer->refcnt); |
424 | } |
425 | |
426 | mutex_unlock(lock: &list_mutex); |
427 | } |
428 | |
429 | static void idletimer_tg_destroy_v1(const struct xt_tgdtor_param *par) |
430 | { |
431 | const struct idletimer_tg_info_v1 *info = par->targinfo; |
432 | |
433 | pr_debug("destroy targinfo %s\n" , info->label); |
434 | |
435 | mutex_lock(&list_mutex); |
436 | |
437 | if (--info->timer->refcnt == 0) { |
438 | pr_debug("deleting timer %s\n" , info->label); |
439 | |
440 | list_del(entry: &info->timer->entry); |
441 | if (info->timer->timer_type & XT_IDLETIMER_ALARM) { |
442 | alarm_cancel(alarm: &info->timer->alarm); |
443 | } else { |
444 | timer_shutdown_sync(timer: &info->timer->timer); |
445 | } |
446 | cancel_work_sync(work: &info->timer->work); |
447 | sysfs_remove_file(kobj: idletimer_tg_kobj, attr: &info->timer->attr.attr); |
448 | kfree(objp: info->timer->attr.attr.name); |
449 | kfree(objp: info->timer); |
450 | } else { |
451 | pr_debug("decreased refcnt of timer %s to %u\n" , |
452 | info->label, info->timer->refcnt); |
453 | } |
454 | |
455 | mutex_unlock(lock: &list_mutex); |
456 | } |
457 | |
458 | |
459 | static struct xt_target idletimer_tg[] __read_mostly = { |
460 | { |
461 | .name = "IDLETIMER" , |
462 | .family = NFPROTO_UNSPEC, |
463 | .target = idletimer_tg_target, |
464 | .targetsize = sizeof(struct idletimer_tg_info), |
465 | .usersize = offsetof(struct idletimer_tg_info, timer), |
466 | .checkentry = idletimer_tg_checkentry, |
467 | .destroy = idletimer_tg_destroy, |
468 | .me = THIS_MODULE, |
469 | }, |
470 | { |
471 | .name = "IDLETIMER" , |
472 | .family = NFPROTO_UNSPEC, |
473 | .revision = 1, |
474 | .target = idletimer_tg_target_v1, |
475 | .targetsize = sizeof(struct idletimer_tg_info_v1), |
476 | .usersize = offsetof(struct idletimer_tg_info_v1, timer), |
477 | .checkentry = idletimer_tg_checkentry_v1, |
478 | .destroy = idletimer_tg_destroy_v1, |
479 | .me = THIS_MODULE, |
480 | }, |
481 | |
482 | |
483 | }; |
484 | |
485 | static struct class *idletimer_tg_class; |
486 | |
487 | static struct device *idletimer_tg_device; |
488 | |
489 | static int __init idletimer_tg_init(void) |
490 | { |
491 | int err; |
492 | |
493 | idletimer_tg_class = class_create(name: "xt_idletimer" ); |
494 | err = PTR_ERR(ptr: idletimer_tg_class); |
495 | if (IS_ERR(ptr: idletimer_tg_class)) { |
496 | pr_debug("couldn't register device class\n" ); |
497 | goto out; |
498 | } |
499 | |
500 | idletimer_tg_device = device_create(cls: idletimer_tg_class, NULL, |
501 | MKDEV(0, 0), NULL, fmt: "timers" ); |
502 | err = PTR_ERR(ptr: idletimer_tg_device); |
503 | if (IS_ERR(ptr: idletimer_tg_device)) { |
504 | pr_debug("couldn't register system device\n" ); |
505 | goto out_class; |
506 | } |
507 | |
508 | idletimer_tg_kobj = &idletimer_tg_device->kobj; |
509 | |
510 | err = xt_register_targets(target: idletimer_tg, ARRAY_SIZE(idletimer_tg)); |
511 | |
512 | if (err < 0) { |
513 | pr_debug("couldn't register xt target\n" ); |
514 | goto out_dev; |
515 | } |
516 | |
517 | return 0; |
518 | out_dev: |
519 | device_destroy(cls: idletimer_tg_class, MKDEV(0, 0)); |
520 | out_class: |
521 | class_destroy(cls: idletimer_tg_class); |
522 | out: |
523 | return err; |
524 | } |
525 | |
526 | static void __exit idletimer_tg_exit(void) |
527 | { |
528 | xt_unregister_targets(target: idletimer_tg, ARRAY_SIZE(idletimer_tg)); |
529 | |
530 | device_destroy(cls: idletimer_tg_class, MKDEV(0, 0)); |
531 | class_destroy(cls: idletimer_tg_class); |
532 | } |
533 | |
534 | module_init(idletimer_tg_init); |
535 | module_exit(idletimer_tg_exit); |
536 | |
537 | MODULE_AUTHOR("Timo Teras <ext-timo.teras@nokia.com>" ); |
538 | MODULE_AUTHOR("Luciano Coelho <luciano.coelho@nokia.com>" ); |
539 | MODULE_DESCRIPTION("Xtables: idle time monitor" ); |
540 | MODULE_LICENSE("GPL v2" ); |
541 | MODULE_ALIAS("ipt_IDLETIMER" ); |
542 | MODULE_ALIAS("ip6t_IDLETIMER" ); |
543 | |