vserver 1.9.3
[linux-2.6.git] / net / ipv6 / ndisc.c
index b278e5a..685a02d 100644 (file)
@@ -58,6 +58,7 @@
 #include <linux/in6.h>
 #include <linux/route.h>
 #include <linux/init.h>
+#include <linux/rcupdate.h>
 #ifdef CONFIG_SYSCTL
 #include <linux/sysctl.h>
 #endif
@@ -65,6 +66,7 @@
 #include <linux/if_arp.h>
 #include <linux/ipv6.h>
 #include <linux/icmpv6.h>
+#include <linux/jhash.h>
 
 #include <net/sock.h>
 #include <net/snmp.h>
@@ -269,29 +271,35 @@ int ndisc_mc_map(struct in6_addr *addr, char *buf, struct net_device *dev, int d
 
 static u32 ndisc_hash(const void *pkey, const struct net_device *dev)
 {
-       u32 hash_val;
+       const u32 *p32 = pkey;
+       u32 addr_hash, i;
 
-       hash_val = *(u32*)(pkey + sizeof(struct in6_addr) - 4);
-       hash_val ^= (hash_val>>16);
-       hash_val ^= hash_val>>8;
-       hash_val ^= hash_val>>3;
-       hash_val = (hash_val^dev->ifindex)&NEIGH_HASHMASK;
+       addr_hash = 0;
+       for (i = 0; i < (sizeof(struct in6_addr) / sizeof(u32)); i++)
+               addr_hash ^= *p32++;
 
-       return hash_val;
+       return jhash_2words(addr_hash, dev->ifindex, nd_tbl.hash_rnd);
 }
 
 static int ndisc_constructor(struct neighbour *neigh)
 {
        struct in6_addr *addr = (struct in6_addr*)&neigh->primary_key;
        struct net_device *dev = neigh->dev;
-       struct inet6_dev *in6_dev = in6_dev_get(dev);
+       struct inet6_dev *in6_dev;
+       struct neigh_parms *parms;
        int is_multicast = ipv6_addr_is_multicast(addr);
 
-       if (in6_dev == NULL)
+       rcu_read_lock();
+       in6_dev = in6_dev_get(dev);
+       if (in6_dev == NULL) {
+               rcu_read_unlock();
                return -EINVAL;
+       }
 
-       if (in6_dev->nd_parms)
-               neigh->parms = in6_dev->nd_parms;
+       parms = in6_dev->nd_parms;
+       __neigh_parms_put(neigh->parms);
+       neigh->parms = neigh_parms_clone(parms);
+       rcu_read_unlock();
 
        neigh->type = is_multicast ? RTN_MULTICAST : RTN_UNICAST;
        if (dev->hard_header == NULL) {
@@ -794,16 +802,20 @@ static void ndisc_recv_ns(struct sk_buff *skb)
        }
 
        if (inc)
-               nd_tbl.stats.rcv_probes_mcast++;
+               NEIGH_CACHE_STAT_INC(&nd_tbl, rcv_probes_mcast);
        else
-               nd_tbl.stats.rcv_probes_ucast++;
+               NEIGH_CACHE_STAT_INC(&nd_tbl, rcv_probes_ucast);
 
        /* 
         *      update / create cache entry
         *      for the source address
         */
-       neigh = neigh_event_ns(&nd_tbl, lladdr, saddr, dev);
-
+       neigh = __neigh_lookup(&nd_tbl, saddr, dev,
+                              !inc || lladdr || !dev->addr_len);
+       if (neigh)
+               neigh_update(neigh, lladdr, NUD_STALE, 
+                            NEIGH_UPDATE_F_WEAK_OVERRIDE|
+                            NEIGH_UPDATE_F_OVERRIDE);
        if (neigh || !dev->hard_header) {
                ndisc_send_na(dev, neigh, saddr, &msg->target,
                              idev->cnf.forwarding, 
@@ -886,24 +898,25 @@ static void ndisc_recv_na(struct sk_buff *skb)
        neigh = neigh_lookup(&nd_tbl, &msg->target, dev);
 
        if (neigh) {
-               if (neigh->flags & NTF_ROUTER) {
-                       if (msg->icmph.icmp6_router == 0) {
-                               /*
-                                *      Change: router to host
-                                */
-                               struct rt6_info *rt;
-                               rt = rt6_get_dflt_router(saddr, dev);
-                               if (rt)
-                                       ip6_del_rt(rt, NULL, NULL);
-                       }
-               } else {
-                       if (msg->icmph.icmp6_router)
-                               neigh->flags |= NTF_ROUTER;
-               }
+               u8 old_flags = neigh->flags;
 
                neigh_update(neigh, lladdr,
                             msg->icmph.icmp6_solicited ? NUD_REACHABLE : NUD_STALE,
-                            msg->icmph.icmp6_override, 1);
+                            NEIGH_UPDATE_F_WEAK_OVERRIDE|
+                            (msg->icmph.icmp6_override ? NEIGH_UPDATE_F_OVERRIDE : 0)|
+                            NEIGH_UPDATE_F_OVERRIDE_ISROUTER|
+                            (msg->icmph.icmp6_router ? NEIGH_UPDATE_F_ISROUTER : 0));
+
+               if ((old_flags & ~neigh->flags) & NTF_ROUTER) {
+                       /*
+                        * Change: router to host
+                        */
+                       struct rt6_info *rt;
+                       rt = rt6_get_dflt_router(saddr, dev);
+                       if (rt)
+                               ip6_del_rt(rt, NULL, NULL);
+               }
+
                neigh_release(neigh);
        }
 }
@@ -1071,7 +1084,11 @@ static void ndisc_router_discovery(struct sk_buff *skb)
                                goto out;
                        }
                }
-               neigh_update(neigh, lladdr, NUD_STALE, 1, 1);
+               neigh_update(neigh, lladdr, NUD_STALE,
+                            NEIGH_UPDATE_F_WEAK_OVERRIDE|
+                            NEIGH_UPDATE_F_OVERRIDE|
+                            NEIGH_UPDATE_F_OVERRIDE_ISROUTER|
+                            NEIGH_UPDATE_F_ISROUTER);
        }
 
        if (ndopts.nd_opts_pi) {
@@ -1188,19 +1205,11 @@ static void ndisc_redirect_rcv(struct sk_buff *skb)
                        return;
                }
        }
-       /* passed validation tests */
-
-       /*
-          We install redirect only if nexthop state is valid.
-        */
 
        neigh = __neigh_lookup(&nd_tbl, target, skb->dev, 1);
        if (neigh) {
-               neigh_update(neigh, lladdr, NUD_STALE, 1, 1);
-               if (neigh->nud_state&NUD_VALID)
-                       rt6_redirect(dest, &skb->nh.ipv6h->saddr, neigh, on_link);
-               else
-                       __neigh_event_send(neigh, NULL);
+               rt6_redirect(dest, &skb->nh.ipv6h->saddr, neigh, lladdr, 
+                            on_link);
                neigh_release(neigh);
        }
        in6_dev_put(in6_dev);