ipv6: Fix bug in ipv6_chk_same_addr().
[safe/jmp/linux-2.6] / net / ipv6 / addrconf.c
index 36ebb4a..68e5809 100644 (file)
 #define        INFINITY_LIFE_TIME      0xFFFFFFFF
 #define TIME_DELTA(a, b) ((unsigned long)((long)(a) - (long)(b)))
 
+#define ADDRCONF_TIMER_FUZZ_MINUS      (HZ > 50 ? HZ/50 : 1)
+#define ADDRCONF_TIMER_FUZZ            (HZ / 4)
+#define ADDRCONF_TIMER_FUZZ_MAX                (HZ)
+
 #ifdef CONFIG_SYSCTL
 static void addrconf_sysctl_register(struct inet6_dev *idev);
 static void addrconf_sysctl_unregister(struct inet6_dev *idev);
@@ -151,8 +155,8 @@ static void ipv6_ifa_notify(int event, struct inet6_ifaddr *ifa);
 
 static void inet6_prefix_notify(int event, struct inet6_dev *idev,
                                struct prefix_info *pinfo);
-static int ipv6_chk_same_addr(struct net *net, const struct in6_addr *addr,
-                             struct net_device *dev);
+static bool ipv6_chk_same_addr(struct net *net, const struct in6_addr *addr,
+                              struct net_device *dev);
 
 static ATOMIC_NOTIFIER_HEAD(inet6addr_chain);
 
@@ -1291,23 +1295,22 @@ int ipv6_chk_addr(struct net *net, struct in6_addr *addr,
 }
 EXPORT_SYMBOL(ipv6_chk_addr);
 
-static
-int ipv6_chk_same_addr(struct net *net, const struct in6_addr *addr,
-                      struct net_device *dev)
+static bool ipv6_chk_same_addr(struct net *net, const struct in6_addr *addr,
+                              struct net_device *dev)
 {
+       unsigned int hash = ipv6_addr_hash(addr);
        struct inet6_ifaddr *ifp;
        struct hlist_node *node;
-       unsigned int hash = ipv6_addr_hash(addr);
 
        hlist_for_each_entry(ifp, node, &inet6_addr_lst[hash], addr_lst) {
                if (!net_eq(dev_net(ifp->idev->dev), net))
                        continue;
                if (ipv6_addr_equal(&ifp->addr, addr)) {
                        if (dev == NULL || ifp->idev->dev == dev)
-                               break;
+                               return true;
                }
        }
-       return ifp != NULL;
+       return false;
 }
 
 int ipv6_chk_prefix(struct in6_addr *addr, struct net_device *dev)
@@ -3107,15 +3110,15 @@ int ipv6_chk_home_addr(struct net *net, struct in6_addr *addr)
 
 static void addrconf_verify(unsigned long foo)
 {
+       unsigned long now, next, next_sec, next_sched;
        struct inet6_ifaddr *ifp;
        struct hlist_node *node;
-       unsigned long now, next;
        int i;
 
        rcu_read_lock_bh();
        spin_lock(&addrconf_verify_lock);
        now = jiffies;
-       next = now + ADDR_CHECK_FREQUENCY;
+       next = round_jiffies_up(now + ADDR_CHECK_FREQUENCY);
 
        del_timer(&addr_chk_timer);
 
@@ -3129,7 +3132,8 @@ restart:
                                continue;
 
                        spin_lock(&ifp->lock);
-                       age = (now - ifp->tstamp) / HZ;
+                       /* We try to batch several events at once. */
+                       age = (now - ifp->tstamp + ADDRCONF_TIMER_FUZZ_MINUS) / HZ;
 
                        if (ifp->valid_lft != INFINITY_LIFE_TIME &&
                            age >= ifp->valid_lft) {
@@ -3199,7 +3203,21 @@ restart:
                }
        }
 
-       addr_chk_timer.expires = time_before(next, jiffies + HZ) ? jiffies + HZ : next;
+       next_sec = round_jiffies_up(next);
+       next_sched = next;
+
+       /* If rounded timeout is accurate enough, accept it. */
+       if (time_before(next_sec, next + ADDRCONF_TIMER_FUZZ))
+               next_sched = next_sec;
+
+       /* And minimum interval is ADDRCONF_TIMER_FUZZ_MAX. */
+       if (time_before(next_sched, jiffies + ADDRCONF_TIMER_FUZZ_MAX))
+               next_sched = jiffies + ADDRCONF_TIMER_FUZZ_MAX;
+
+       ADBG((KERN_DEBUG "now = %lu, schedule = %lu, rounded schedule = %lu => %lu\n",
+             now, next, next_sec, next_sched));
+
+       addr_chk_timer.expires = next_sched;
        add_timer(&addr_chk_timer);
        spin_unlock(&addrconf_verify_lock);
        rcu_read_unlock_bh();