[NEIGH] Fix add_timer race in neigh_add_timer
[linux-2.6-block.git] / net / core / neighbour.c
index 1beb782ac41b6fe91eb91ddab02ef738ab7ee1d4..37d8d8c295226b214c95de9134317391886e387e 100644 (file)
@@ -61,7 +61,9 @@ static int pneigh_ifdown(struct neigh_table *tbl, struct net_device *dev);
 void neigh_changeaddr(struct neigh_table *tbl, struct net_device *dev);
 
 static struct neigh_table *neigh_tables;
+#ifdef CONFIG_PROC_FS
 static struct file_operations neigh_stat_seq_fops;
+#endif
 
 /*
    Neighbour hash table buckets are protected with rwlock tbl->lock.
@@ -725,6 +727,14 @@ static __inline__ int neigh_max_probes(struct neighbour *n)
                p->ucast_probes + p->app_probes + p->mcast_probes);
 }
 
+static inline void neigh_add_timer(struct neighbour *n, unsigned long when)
+{
+       if (unlikely(mod_timer(&n->timer, when))) {
+               printk("NEIGH: BUG, double timer add, state is %x\n",
+                      n->nud_state);
+               dump_stack();
+       }
+}
 
 /* Called when a timer expires for a neighbour entry. */
 
@@ -806,11 +816,10 @@ static void neigh_timer_handler(unsigned long arg)
        }
 
        if (neigh->nud_state & NUD_IN_TIMER) {
-               neigh_hold(neigh);
                if (time_before(next, jiffies + HZ/2))
                        next = jiffies + HZ/2;
-               neigh->timer.expires = next;
-               add_timer(&neigh->timer);
+               if (!mod_timer(&neigh->timer, next))
+                       neigh_hold(neigh);
        }
        if (neigh->nud_state & (NUD_INCOMPLETE | NUD_PROBE)) {
                struct sk_buff *skb = skb_peek(&neigh->arp_queue);
@@ -852,8 +861,7 @@ int __neigh_event_send(struct neighbour *neigh, struct sk_buff *skb)
                        atomic_set(&neigh->probes, neigh->parms->ucast_probes);
                        neigh->nud_state     = NUD_INCOMPLETE;
                        neigh_hold(neigh);
-                       neigh->timer.expires = now + 1;
-                       add_timer(&neigh->timer);
+                       neigh_add_timer(neigh, now + 1);
                } else {
                        neigh->nud_state = NUD_FAILED;
                        write_unlock_bh(&neigh->lock);
@@ -866,8 +874,8 @@ int __neigh_event_send(struct neighbour *neigh, struct sk_buff *skb)
                NEIGH_PRINTK2("neigh %p is delayed.\n", neigh);
                neigh_hold(neigh);
                neigh->nud_state = NUD_DELAY;
-               neigh->timer.expires = jiffies + neigh->parms->delay_probe_time;
-               add_timer(&neigh->timer);
+               neigh_add_timer(neigh,
+                               jiffies + neigh->parms->delay_probe_time);
        }
 
        if (neigh->nud_state == NUD_INCOMPLETE) {
@@ -1013,10 +1021,10 @@ int neigh_update(struct neighbour *neigh, const u8 *lladdr, u8 new,
                neigh_del_timer(neigh);
                if (new & NUD_IN_TIMER) {
                        neigh_hold(neigh);
-                       neigh->timer.expires = jiffies + 
+                       neigh_add_timer(neigh, (jiffies + 
                                                ((new & NUD_REACHABLE) ? 
-                                                neigh->parms->reachable_time : 0);
-                       add_timer(&neigh->timer);
+                                                neigh->parms->reachable_time :
+                                                0)));
                }
                neigh->nud_state = new;
        }
@@ -1217,7 +1225,7 @@ static void neigh_proxy_process(unsigned long arg)
 
        while (skb != (struct sk_buff *)&tbl->proxy_queue) {
                struct sk_buff *back = skb;
-               long tdif = back->stamp.tv_usec - now;
+               long tdif = NEIGH_CB(back)->sched_next - now;
 
                skb = skb->next;
                if (tdif <= 0) {
@@ -1248,8 +1256,9 @@ void pneigh_enqueue(struct neigh_table *tbl, struct neigh_parms *p,
                kfree_skb(skb);
                return;
        }
-       skb->stamp.tv_sec  = LOCALLY_ENQUEUED;
-       skb->stamp.tv_usec = sched_next;
+
+       NEIGH_CB(skb)->sched_next = sched_next;
+       NEIGH_CB(skb)->flags |= LOCALLY_ENQUEUED;
 
        spin_lock(&tbl->proxy_queue.lock);
        if (del_timer(&tbl->proxy_timer)) {
@@ -2342,8 +2351,8 @@ void neigh_app_ns(struct neighbour *n)
        }
        nlh                        = (struct nlmsghdr *)skb->data;
        nlh->nlmsg_flags           = NLM_F_REQUEST;
-       NETLINK_CB(skb).dst_groups = RTMGRP_NEIGH;
-       netlink_broadcast(rtnl, skb, 0, RTMGRP_NEIGH, GFP_ATOMIC);
+       NETLINK_CB(skb).dst_group  = RTNLGRP_NEIGH;
+       netlink_broadcast(rtnl, skb, 0, RTNLGRP_NEIGH, GFP_ATOMIC);
 }
 
 static void neigh_app_notify(struct neighbour *n)
@@ -2360,8 +2369,8 @@ static void neigh_app_notify(struct neighbour *n)
                return;
        }
        nlh                        = (struct nlmsghdr *)skb->data;
-       NETLINK_CB(skb).dst_groups = RTMGRP_NEIGH;
-       netlink_broadcast(rtnl, skb, 0, RTMGRP_NEIGH, GFP_ATOMIC);
+       NETLINK_CB(skb).dst_group  = RTNLGRP_NEIGH;
+       netlink_broadcast(rtnl, skb, 0, RTNLGRP_NEIGH, GFP_ATOMIC);
 }
 
 #endif /* CONFIG_ARPD */