xref: /linux-6.15/net/netfilter/xt_LED.c (revision 04317f4e)
1935912c5SThomas Gleixner // SPDX-License-Identifier: GPL-2.0-only
2268cb38eSAdam Nielsen /*
3268cb38eSAdam Nielsen  * xt_LED.c - netfilter target to make LEDs blink upon packet matches
4268cb38eSAdam Nielsen  *
5268cb38eSAdam Nielsen  * Copyright (C) 2008 Adam Nielsen <[email protected]>
6268cb38eSAdam Nielsen  */
78bee4badSJan Engelhardt #define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
8268cb38eSAdam Nielsen #include <linux/module.h>
9268cb38eSAdam Nielsen #include <linux/skbuff.h>
10268cb38eSAdam Nielsen #include <linux/netfilter/x_tables.h>
115a0e3ad6STejun Heo #include <linux/slab.h>
12268cb38eSAdam Nielsen #include <linux/leds.h>
13268cb38eSAdam Nielsen #include <linux/mutex.h>
14268cb38eSAdam Nielsen 
15268cb38eSAdam Nielsen #include <linux/netfilter/xt_LED.h>
16268cb38eSAdam Nielsen 
17268cb38eSAdam Nielsen MODULE_LICENSE("GPL");
18268cb38eSAdam Nielsen MODULE_AUTHOR("Adam Nielsen <[email protected]>");
19268cb38eSAdam Nielsen MODULE_DESCRIPTION("Xtables: trigger LED devices on packet match");
20f1e231a3SJan Engelhardt MODULE_ALIAS("ipt_LED");
21f1e231a3SJan Engelhardt MODULE_ALIAS("ip6t_LED");
22268cb38eSAdam Nielsen 
23b660d048SAdam Nielsen static LIST_HEAD(xt_led_triggers);
24b660d048SAdam Nielsen static DEFINE_MUTEX(xt_led_mutex);
25b660d048SAdam Nielsen 
26268cb38eSAdam Nielsen /*
27268cb38eSAdam Nielsen  * This is declared in here (the kernel module) only, to avoid having these
28268cb38eSAdam Nielsen  * dependencies in userspace code.  This is what xt_led_info.internal_data
29268cb38eSAdam Nielsen  * points to.
30268cb38eSAdam Nielsen  */
31268cb38eSAdam Nielsen struct xt_led_info_internal {
32b660d048SAdam Nielsen 	struct list_head list;
33b660d048SAdam Nielsen 	int refcnt;
34b660d048SAdam Nielsen 	char *trigger_id;
35268cb38eSAdam Nielsen 	struct led_trigger netfilter_led_trigger;
36268cb38eSAdam Nielsen 	struct timer_list timer;
37268cb38eSAdam Nielsen };
38268cb38eSAdam Nielsen 
398452e6ffSJiri Prchal #define XT_LED_BLINK_DELAY 50 /* ms */
408452e6ffSJiri Prchal 
41268cb38eSAdam Nielsen static unsigned int
led_tg(struct sk_buff * skb,const struct xt_action_param * par)424b560b44SJan Engelhardt led_tg(struct sk_buff *skb, const struct xt_action_param *par)
43268cb38eSAdam Nielsen {
44268cb38eSAdam Nielsen 	const struct xt_led_info *ledinfo = par->targinfo;
45268cb38eSAdam Nielsen 	struct xt_led_info_internal *ledinternal = ledinfo->internal_data;
46268cb38eSAdam Nielsen 
47268cb38eSAdam Nielsen 	/*
48268cb38eSAdam Nielsen 	 * If "always blink" is enabled, and there's still some time until the
49268cb38eSAdam Nielsen 	 * LED will switch off, briefly switch it off now.
50268cb38eSAdam Nielsen 	 */
51268cb38eSAdam Nielsen 	if ((ledinfo->delay > 0) && ledinfo->always_blink &&
52268cb38eSAdam Nielsen 	    timer_pending(&ledinternal->timer))
538452e6ffSJiri Prchal 		led_trigger_blink_oneshot(&ledinternal->netfilter_led_trigger,
54e298d8a3SHans de Goede 					  XT_LED_BLINK_DELAY, XT_LED_BLINK_DELAY, 1);
558452e6ffSJiri Prchal 	else
56268cb38eSAdam Nielsen 		led_trigger_event(&ledinternal->netfilter_led_trigger, LED_FULL);
57268cb38eSAdam Nielsen 
58268cb38eSAdam Nielsen 	/* If there's a positive delay, start/update the timer */
59268cb38eSAdam Nielsen 	if (ledinfo->delay > 0) {
60268cb38eSAdam Nielsen 		mod_timer(&ledinternal->timer,
61268cb38eSAdam Nielsen 			  jiffies + msecs_to_jiffies(ledinfo->delay));
62268cb38eSAdam Nielsen 
63268cb38eSAdam Nielsen 	/* Otherwise if there was no delay given, blink as fast as possible */
64268cb38eSAdam Nielsen 	} else if (ledinfo->delay == 0) {
65268cb38eSAdam Nielsen 		led_trigger_event(&ledinternal->netfilter_led_trigger, LED_OFF);
66268cb38eSAdam Nielsen 	}
67268cb38eSAdam Nielsen 
68268cb38eSAdam Nielsen 	/* else the delay is negative, which means switch on and stay on */
69268cb38eSAdam Nielsen 
70268cb38eSAdam Nielsen 	return XT_CONTINUE;
71268cb38eSAdam Nielsen }
72268cb38eSAdam Nielsen 
led_timeout_callback(struct timer_list * t)73e99e88a9SKees Cook static void led_timeout_callback(struct timer_list *t)
74268cb38eSAdam Nielsen {
75e99e88a9SKees Cook 	struct xt_led_info_internal *ledinternal = from_timer(ledinternal, t,
76e99e88a9SKees Cook 							      timer);
77268cb38eSAdam Nielsen 
78268cb38eSAdam Nielsen 	led_trigger_event(&ledinternal->netfilter_led_trigger, LED_OFF);
79268cb38eSAdam Nielsen }
80268cb38eSAdam Nielsen 
led_trigger_lookup(const char * name)81b660d048SAdam Nielsen static struct xt_led_info_internal *led_trigger_lookup(const char *name)
82b660d048SAdam Nielsen {
83b660d048SAdam Nielsen 	struct xt_led_info_internal *ledinternal;
84b660d048SAdam Nielsen 
85b660d048SAdam Nielsen 	list_for_each_entry(ledinternal, &xt_led_triggers, list) {
86b660d048SAdam Nielsen 		if (!strcmp(name, ledinternal->netfilter_led_trigger.name)) {
87b660d048SAdam Nielsen 			return ledinternal;
88b660d048SAdam Nielsen 		}
89b660d048SAdam Nielsen 	}
90b660d048SAdam Nielsen 	return NULL;
91b660d048SAdam Nielsen }
92b660d048SAdam Nielsen 
led_tg_check(const struct xt_tgchk_param * par)93135367b8SJan Engelhardt static int led_tg_check(const struct xt_tgchk_param *par)
94268cb38eSAdam Nielsen {
95268cb38eSAdam Nielsen 	struct xt_led_info *ledinfo = par->targinfo;
96268cb38eSAdam Nielsen 	struct xt_led_info_internal *ledinternal;
97268cb38eSAdam Nielsen 	int err;
98268cb38eSAdam Nielsen 
99*04317f4eSDmitry Antipov 	/* Bail out if empty string or not a string at all. */
100*04317f4eSDmitry Antipov 	if (ledinfo->id[0] == '\0' ||
101*04317f4eSDmitry Antipov 	    !memchr(ledinfo->id, '\0', sizeof(ledinfo->id)))
102d6b00a53SJan Engelhardt 		return -EINVAL;
103268cb38eSAdam Nielsen 
104b660d048SAdam Nielsen 	mutex_lock(&xt_led_mutex);
105b660d048SAdam Nielsen 
106b660d048SAdam Nielsen 	ledinternal = led_trigger_lookup(ledinfo->id);
107b660d048SAdam Nielsen 	if (ledinternal) {
108b660d048SAdam Nielsen 		ledinternal->refcnt++;
109b660d048SAdam Nielsen 		goto out;
110b660d048SAdam Nielsen 	}
111b660d048SAdam Nielsen 
112b660d048SAdam Nielsen 	err = -ENOMEM;
113268cb38eSAdam Nielsen 	ledinternal = kzalloc(sizeof(struct xt_led_info_internal), GFP_KERNEL);
11485bc3f38SJan Engelhardt 	if (!ledinternal)
115b660d048SAdam Nielsen 		goto exit_mutex_only;
116268cb38eSAdam Nielsen 
117b660d048SAdam Nielsen 	ledinternal->trigger_id = kstrdup(ledinfo->id, GFP_KERNEL);
118b660d048SAdam Nielsen 	if (!ledinternal->trigger_id)
119b660d048SAdam Nielsen 		goto exit_internal_alloc;
120b660d048SAdam Nielsen 
121b660d048SAdam Nielsen 	ledinternal->refcnt = 1;
122b660d048SAdam Nielsen 	ledinternal->netfilter_led_trigger.name = ledinternal->trigger_id;
123268cb38eSAdam Nielsen 
124268cb38eSAdam Nielsen 	err = led_trigger_register(&ledinternal->netfilter_led_trigger);
125268cb38eSAdam Nielsen 	if (err) {
126b2606644SFlorian Westphal 		pr_info_ratelimited("Trigger name is already in use.\n");
127268cb38eSAdam Nielsen 		goto exit_alloc;
128268cb38eSAdam Nielsen 	}
129268cb38eSAdam Nielsen 
13010414014SPaolo Abeni 	/* Since the letinternal timer can be shared between multiple targets,
13110414014SPaolo Abeni 	 * always set it up, even if the current target does not need it
13210414014SPaolo Abeni 	 */
133e99e88a9SKees Cook 	timer_setup(&ledinternal->timer, led_timeout_callback, 0);
134b660d048SAdam Nielsen 
135b660d048SAdam Nielsen 	list_add_tail(&ledinternal->list, &xt_led_triggers);
136b660d048SAdam Nielsen 
137b660d048SAdam Nielsen out:
138b660d048SAdam Nielsen 	mutex_unlock(&xt_led_mutex);
139268cb38eSAdam Nielsen 
140268cb38eSAdam Nielsen 	ledinfo->internal_data = ledinternal;
141b660d048SAdam Nielsen 
142d6b00a53SJan Engelhardt 	return 0;
143268cb38eSAdam Nielsen 
144268cb38eSAdam Nielsen exit_alloc:
145b660d048SAdam Nielsen 	kfree(ledinternal->trigger_id);
146b660d048SAdam Nielsen 
147b660d048SAdam Nielsen exit_internal_alloc:
148268cb38eSAdam Nielsen 	kfree(ledinternal);
149b660d048SAdam Nielsen 
150b660d048SAdam Nielsen exit_mutex_only:
151b660d048SAdam Nielsen 	mutex_unlock(&xt_led_mutex);
152b660d048SAdam Nielsen 
1534a5a5c73SJan Engelhardt 	return err;
154268cb38eSAdam Nielsen }
155268cb38eSAdam Nielsen 
led_tg_destroy(const struct xt_tgdtor_param * par)156268cb38eSAdam Nielsen static void led_tg_destroy(const struct xt_tgdtor_param *par)
157268cb38eSAdam Nielsen {
158268cb38eSAdam Nielsen 	const struct xt_led_info *ledinfo = par->targinfo;
159268cb38eSAdam Nielsen 	struct xt_led_info_internal *ledinternal = ledinfo->internal_data;
160268cb38eSAdam Nielsen 
161b660d048SAdam Nielsen 	mutex_lock(&xt_led_mutex);
162b660d048SAdam Nielsen 
163b660d048SAdam Nielsen 	if (--ledinternal->refcnt) {
164b660d048SAdam Nielsen 		mutex_unlock(&xt_led_mutex);
165b660d048SAdam Nielsen 		return;
166b660d048SAdam Nielsen 	}
167b660d048SAdam Nielsen 
168b660d048SAdam Nielsen 	list_del(&ledinternal->list);
169b660d048SAdam Nielsen 
170292a089dSSteven Rostedt (Google) 	timer_shutdown_sync(&ledinternal->timer);
171268cb38eSAdam Nielsen 
172268cb38eSAdam Nielsen 	led_trigger_unregister(&ledinternal->netfilter_led_trigger);
173b660d048SAdam Nielsen 
174b660d048SAdam Nielsen 	mutex_unlock(&xt_led_mutex);
175b660d048SAdam Nielsen 
176b660d048SAdam Nielsen 	kfree(ledinternal->trigger_id);
177268cb38eSAdam Nielsen 	kfree(ledinternal);
178268cb38eSAdam Nielsen }
179268cb38eSAdam Nielsen 
1800bfcb7b7SFlorian Westphal static struct xt_target led_tg_reg[] __read_mostly = {
1810bfcb7b7SFlorian Westphal 	{
182268cb38eSAdam Nielsen 		.name		= "LED",
183268cb38eSAdam Nielsen 		.revision	= 0,
1840bfcb7b7SFlorian Westphal 		.family		= NFPROTO_IPV4,
185268cb38eSAdam Nielsen 		.target		= led_tg,
1867d5f7ed8SJan Engelhardt 		.targetsize	= sizeof(struct xt_led_info),
1871e98ffeaSDmitry Vyukov 		.usersize	= offsetof(struct xt_led_info, internal_data),
188268cb38eSAdam Nielsen 		.checkentry	= led_tg_check,
189268cb38eSAdam Nielsen 		.destroy	= led_tg_destroy,
190268cb38eSAdam Nielsen 		.me		= THIS_MODULE,
1910bfcb7b7SFlorian Westphal 	},
1920bfcb7b7SFlorian Westphal #if IS_ENABLED(CONFIG_IP6_NF_IPTABLES)
1930bfcb7b7SFlorian Westphal 	{
1940bfcb7b7SFlorian Westphal 		.name		= "LED",
1950bfcb7b7SFlorian Westphal 		.revision	= 0,
1960bfcb7b7SFlorian Westphal 		.family		= NFPROTO_IPV6,
1970bfcb7b7SFlorian Westphal 		.target		= led_tg,
1980bfcb7b7SFlorian Westphal 		.targetsize	= sizeof(struct xt_led_info),
1990bfcb7b7SFlorian Westphal 		.usersize	= offsetof(struct xt_led_info, internal_data),
2000bfcb7b7SFlorian Westphal 		.checkentry	= led_tg_check,
2010bfcb7b7SFlorian Westphal 		.destroy	= led_tg_destroy,
2020bfcb7b7SFlorian Westphal 		.me		= THIS_MODULE,
2030bfcb7b7SFlorian Westphal 	},
2040bfcb7b7SFlorian Westphal #endif
205268cb38eSAdam Nielsen };
206268cb38eSAdam Nielsen 
led_tg_init(void)207268cb38eSAdam Nielsen static int __init led_tg_init(void)
208268cb38eSAdam Nielsen {
2090bfcb7b7SFlorian Westphal 	return xt_register_targets(led_tg_reg, ARRAY_SIZE(led_tg_reg));
210268cb38eSAdam Nielsen }
211268cb38eSAdam Nielsen 
led_tg_exit(void)212268cb38eSAdam Nielsen static void __exit led_tg_exit(void)
213268cb38eSAdam Nielsen {
2140bfcb7b7SFlorian Westphal 	xt_unregister_targets(led_tg_reg, ARRAY_SIZE(led_tg_reg));
215268cb38eSAdam Nielsen }
216268cb38eSAdam Nielsen 
217268cb38eSAdam Nielsen module_init(led_tg_init);
218268cb38eSAdam Nielsen module_exit(led_tg_exit);
219