netfilter: xt_LED: add refcounts to LED target
authorAdam Nielsen <a.nielsen@shikadi.net>
Fri, 9 Apr 2010 14:51:40 +0000 (16:51 +0200)
committerPatrick McHardy <kaber@trash.net>
Fri, 9 Apr 2010 14:51:40 +0000 (16:51 +0200)
Add reference counting to the netfilter LED target, to fix errors when
multiple rules point to the same target ("LED trigger already exists").

Signed-off-by: Adam Nielsen <a.nielsen@shikadi.net>
Signed-off-by: Patrick McHardy <kaber@trash.net>
net/netfilter/xt_LED.c

index efcf56d..bd102c7 100644 (file)
@@ -31,12 +31,18 @@ MODULE_LICENSE("GPL");
 MODULE_AUTHOR("Adam Nielsen <a.nielsen@shikadi.net>");
 MODULE_DESCRIPTION("Xtables: trigger LED devices on packet match");
 
+static LIST_HEAD(xt_led_triggers);
+static DEFINE_MUTEX(xt_led_mutex);
+
 /*
  * This is declared in here (the kernel module) only, to avoid having these
  * dependencies in userspace code.  This is what xt_led_info.internal_data
  * points to.
  */
 struct xt_led_info_internal {
+       struct list_head list;
+       int refcnt;
+       char *trigger_id;
        struct led_trigger netfilter_led_trigger;
        struct timer_list timer;
 };
@@ -53,7 +59,7 @@ led_tg(struct sk_buff *skb, const struct xt_target_param *par)
         */
        if ((ledinfo->delay > 0) && ledinfo->always_blink &&
            timer_pending(&ledinternal->timer))
-               led_trigger_event(&ledinternal->netfilter_led_trigger,LED_OFF);
+               led_trigger_event(&ledinternal->netfilter_led_trigger, LED_OFF);
 
        led_trigger_event(&ledinternal->netfilter_led_trigger, LED_FULL);
 
@@ -74,12 +80,23 @@ led_tg(struct sk_buff *skb, const struct xt_target_param *par)
 
 static void led_timeout_callback(unsigned long data)
 {
-       struct xt_led_info *ledinfo = (struct xt_led_info *)data;
-       struct xt_led_info_internal *ledinternal = ledinfo->internal_data;
+       struct xt_led_info_internal *ledinternal = (struct xt_led_info_internal *)data;
 
        led_trigger_event(&ledinternal->netfilter_led_trigger, LED_OFF);
 }
 
+static struct xt_led_info_internal *led_trigger_lookup(const char *name)
+{
+       struct xt_led_info_internal *ledinternal;
+
+       list_for_each_entry(ledinternal, &xt_led_triggers, list) {
+               if (!strcmp(name, ledinternal->netfilter_led_trigger.name)) {
+                       return ledinternal;
+               }
+       }
+       return NULL;
+}
+
 static int led_tg_check(const struct xt_tgchk_param *par)
 {
        struct xt_led_info *ledinfo = par->targinfo;
@@ -91,11 +108,25 @@ static int led_tg_check(const struct xt_tgchk_param *par)
                return -EINVAL;
        }
 
+       mutex_lock(&xt_led_mutex);
+
+       ledinternal = led_trigger_lookup(ledinfo->id);
+       if (ledinternal) {
+               ledinternal->refcnt++;
+               goto out;
+       }
+
+       err = -ENOMEM;
        ledinternal = kzalloc(sizeof(struct xt_led_info_internal), GFP_KERNEL);
        if (!ledinternal)
-               return -ENOMEM;
+               goto exit_mutex_only;
+
+       ledinternal->trigger_id = kstrdup(ledinfo->id, GFP_KERNEL);
+       if (!ledinternal->trigger_id)
+               goto exit_internal_alloc;
 
-       ledinternal->netfilter_led_trigger.name = ledinfo->id;
+       ledinternal->refcnt = 1;
+       ledinternal->netfilter_led_trigger.name = ledinternal->trigger_id;
 
        err = led_trigger_register(&ledinternal->netfilter_led_trigger);
        if (err) {
@@ -108,13 +139,26 @@ static int led_tg_check(const struct xt_tgchk_param *par)
        /* See if we need to set up a timer */
        if (ledinfo->delay > 0)
                setup_timer(&ledinternal->timer, led_timeout_callback,
-                           (unsigned long)ledinfo);
+                           (unsigned long)ledinternal);
+
+       list_add_tail(&ledinternal->list, &xt_led_triggers);
+
+out:
+       mutex_unlock(&xt_led_mutex);
 
        ledinfo->internal_data = ledinternal;
+
        return 0;
 
 exit_alloc:
+       kfree(ledinternal->trigger_id);
+
+exit_internal_alloc:
        kfree(ledinternal);
+
+exit_mutex_only:
+       mutex_unlock(&xt_led_mutex);
+
        return err;
 }
 
@@ -123,10 +167,23 @@ static void led_tg_destroy(const struct xt_tgdtor_param *par)
        const struct xt_led_info *ledinfo = par->targinfo;
        struct xt_led_info_internal *ledinternal = ledinfo->internal_data;
 
+       mutex_lock(&xt_led_mutex);
+
+       if (--ledinternal->refcnt) {
+               mutex_unlock(&xt_led_mutex);
+               return;
+       }
+
+       list_del(&ledinternal->list);
+
        if (ledinfo->delay > 0)
                del_timer_sync(&ledinternal->timer);
 
        led_trigger_unregister(&ledinternal->netfilter_led_trigger);
+
+       mutex_unlock(&xt_led_mutex);
+
+       kfree(ledinternal->trigger_id);
        kfree(ledinternal);
 }