1 | // SPDX-License-Identifier: GPL-2.0-only |
2 | /* |
3 | * xt_LED.c - netfilter target to make LEDs blink upon packet matches |
4 | * |
5 | * Copyright (C) 2008 Adam Nielsen <a.nielsen@shikadi.net> |
6 | */ |
7 | #define pr_fmt(fmt) KBUILD_MODNAME ": " fmt |
8 | #include <linux/module.h> |
9 | #include <linux/skbuff.h> |
10 | #include <linux/netfilter/x_tables.h> |
11 | #include <linux/slab.h> |
12 | #include <linux/leds.h> |
13 | #include <linux/mutex.h> |
14 | |
15 | #include <linux/netfilter/xt_LED.h> |
16 | |
17 | MODULE_LICENSE("GPL" ); |
18 | MODULE_AUTHOR("Adam Nielsen <a.nielsen@shikadi.net>" ); |
19 | MODULE_DESCRIPTION("Xtables: trigger LED devices on packet match" ); |
20 | MODULE_ALIAS("ipt_LED" ); |
21 | MODULE_ALIAS("ip6t_LED" ); |
22 | |
23 | static LIST_HEAD(xt_led_triggers); |
24 | static DEFINE_MUTEX(xt_led_mutex); |
25 | |
26 | /* |
27 | * This is declared in here (the kernel module) only, to avoid having these |
28 | * dependencies in userspace code. This is what xt_led_info.internal_data |
29 | * points to. |
30 | */ |
31 | struct xt_led_info_internal { |
32 | struct list_head list; |
33 | int refcnt; |
34 | char *trigger_id; |
35 | struct led_trigger netfilter_led_trigger; |
36 | struct timer_list timer; |
37 | }; |
38 | |
39 | #define XT_LED_BLINK_DELAY 50 /* ms */ |
40 | |
41 | static unsigned int |
42 | led_tg(struct sk_buff *skb, const struct xt_action_param *par) |
43 | { |
44 | const struct xt_led_info *ledinfo = par->targinfo; |
45 | struct xt_led_info_internal *ledinternal = ledinfo->internal_data; |
46 | |
47 | /* |
48 | * If "always blink" is enabled, and there's still some time until the |
49 | * LED will switch off, briefly switch it off now. |
50 | */ |
51 | if ((ledinfo->delay > 0) && ledinfo->always_blink && |
52 | timer_pending(timer: &ledinternal->timer)) |
53 | led_trigger_blink_oneshot(trigger: &ledinternal->netfilter_led_trigger, |
54 | XT_LED_BLINK_DELAY, XT_LED_BLINK_DELAY, invert: 1); |
55 | else |
56 | led_trigger_event(trigger: &ledinternal->netfilter_led_trigger, event: LED_FULL); |
57 | |
58 | /* If there's a positive delay, start/update the timer */ |
59 | if (ledinfo->delay > 0) { |
60 | mod_timer(timer: &ledinternal->timer, |
61 | expires: jiffies + msecs_to_jiffies(m: ledinfo->delay)); |
62 | |
63 | /* Otherwise if there was no delay given, blink as fast as possible */ |
64 | } else if (ledinfo->delay == 0) { |
65 | led_trigger_event(trigger: &ledinternal->netfilter_led_trigger, event: LED_OFF); |
66 | } |
67 | |
68 | /* else the delay is negative, which means switch on and stay on */ |
69 | |
70 | return XT_CONTINUE; |
71 | } |
72 | |
73 | static void led_timeout_callback(struct timer_list *t) |
74 | { |
75 | struct xt_led_info_internal *ledinternal = from_timer(ledinternal, t, |
76 | timer); |
77 | |
78 | led_trigger_event(trigger: &ledinternal->netfilter_led_trigger, event: LED_OFF); |
79 | } |
80 | |
81 | static struct xt_led_info_internal *led_trigger_lookup(const char *name) |
82 | { |
83 | struct xt_led_info_internal *ledinternal; |
84 | |
85 | list_for_each_entry(ledinternal, &xt_led_triggers, list) { |
86 | if (!strcmp(name, ledinternal->netfilter_led_trigger.name)) { |
87 | return ledinternal; |
88 | } |
89 | } |
90 | return NULL; |
91 | } |
92 | |
93 | static int led_tg_check(const struct xt_tgchk_param *par) |
94 | { |
95 | struct xt_led_info *ledinfo = par->targinfo; |
96 | struct xt_led_info_internal *ledinternal; |
97 | int err; |
98 | |
99 | if (ledinfo->id[0] == '\0') |
100 | return -EINVAL; |
101 | |
102 | mutex_lock(&xt_led_mutex); |
103 | |
104 | ledinternal = led_trigger_lookup(name: ledinfo->id); |
105 | if (ledinternal) { |
106 | ledinternal->refcnt++; |
107 | goto out; |
108 | } |
109 | |
110 | err = -ENOMEM; |
111 | ledinternal = kzalloc(size: sizeof(struct xt_led_info_internal), GFP_KERNEL); |
112 | if (!ledinternal) |
113 | goto exit_mutex_only; |
114 | |
115 | ledinternal->trigger_id = kstrdup(s: ledinfo->id, GFP_KERNEL); |
116 | if (!ledinternal->trigger_id) |
117 | goto exit_internal_alloc; |
118 | |
119 | ledinternal->refcnt = 1; |
120 | ledinternal->netfilter_led_trigger.name = ledinternal->trigger_id; |
121 | |
122 | err = led_trigger_register(trigger: &ledinternal->netfilter_led_trigger); |
123 | if (err) { |
124 | pr_info_ratelimited("Trigger name is already in use.\n" ); |
125 | goto exit_alloc; |
126 | } |
127 | |
128 | /* Since the letinternal timer can be shared between multiple targets, |
129 | * always set it up, even if the current target does not need it |
130 | */ |
131 | timer_setup(&ledinternal->timer, led_timeout_callback, 0); |
132 | |
133 | list_add_tail(new: &ledinternal->list, head: &xt_led_triggers); |
134 | |
135 | out: |
136 | mutex_unlock(lock: &xt_led_mutex); |
137 | |
138 | ledinfo->internal_data = ledinternal; |
139 | |
140 | return 0; |
141 | |
142 | exit_alloc: |
143 | kfree(objp: ledinternal->trigger_id); |
144 | |
145 | exit_internal_alloc: |
146 | kfree(objp: ledinternal); |
147 | |
148 | exit_mutex_only: |
149 | mutex_unlock(lock: &xt_led_mutex); |
150 | |
151 | return err; |
152 | } |
153 | |
154 | static void led_tg_destroy(const struct xt_tgdtor_param *par) |
155 | { |
156 | const struct xt_led_info *ledinfo = par->targinfo; |
157 | struct xt_led_info_internal *ledinternal = ledinfo->internal_data; |
158 | |
159 | mutex_lock(&xt_led_mutex); |
160 | |
161 | if (--ledinternal->refcnt) { |
162 | mutex_unlock(lock: &xt_led_mutex); |
163 | return; |
164 | } |
165 | |
166 | list_del(entry: &ledinternal->list); |
167 | |
168 | timer_shutdown_sync(timer: &ledinternal->timer); |
169 | |
170 | led_trigger_unregister(trigger: &ledinternal->netfilter_led_trigger); |
171 | |
172 | mutex_unlock(lock: &xt_led_mutex); |
173 | |
174 | kfree(objp: ledinternal->trigger_id); |
175 | kfree(objp: ledinternal); |
176 | } |
177 | |
178 | static struct xt_target led_tg_reg __read_mostly = { |
179 | .name = "LED" , |
180 | .revision = 0, |
181 | .family = NFPROTO_UNSPEC, |
182 | .target = led_tg, |
183 | .targetsize = sizeof(struct xt_led_info), |
184 | .usersize = offsetof(struct xt_led_info, internal_data), |
185 | .checkentry = led_tg_check, |
186 | .destroy = led_tg_destroy, |
187 | .me = THIS_MODULE, |
188 | }; |
189 | |
190 | static int __init led_tg_init(void) |
191 | { |
192 | return xt_register_target(target: &led_tg_reg); |
193 | } |
194 | |
195 | static void __exit led_tg_exit(void) |
196 | { |
197 | xt_unregister_target(target: &led_tg_reg); |
198 | } |
199 | |
200 | module_init(led_tg_init); |
201 | module_exit(led_tg_exit); |
202 | |