fedora core 6 1.2949 + vserver 2.2.0
[linux-2.6.git] / net / ipv6 / ndisc.c
index 245dac7..39bb658 100644 (file)
@@ -48,7 +48,6 @@
 #endif
 
 #include <linux/module.h>
-#include <linux/config.h>
 #include <linux/errno.h>
 #include <linux/types.h>
 #include <linux/socket.h>
 #include <linux/in6.h>
 #include <linux/route.h>
 #include <linux/init.h>
+#include <linux/rcupdate.h>
 #ifdef CONFIG_SYSCTL
 #include <linux/sysctl.h>
 #endif
 
+#include <linux/if_addr.h>
 #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>
@@ -77,7 +79,7 @@
 #include <net/icmp.h>
 
 #include <net/flow.h>
-#include <net/checksum.h>
+#include <net/ip6_checksum.h>
 #include <linux/proc_fs.h>
 
 #include <linux/netfilter.h>
@@ -154,25 +156,54 @@ struct neigh_table nd_tbl = {
 
 /* ND options */
 struct ndisc_options {
-       struct nd_opt_hdr *nd_opt_array[7];
-       struct nd_opt_hdr *nd_opt_piend;
+       struct nd_opt_hdr *nd_opt_array[__ND_OPT_ARRAY_MAX];
+#ifdef CONFIG_IPV6_ROUTE_INFO
+       struct nd_opt_hdr *nd_opts_ri;
+       struct nd_opt_hdr *nd_opts_ri_end;
+#endif
 };
 
 #define nd_opts_src_lladdr     nd_opt_array[ND_OPT_SOURCE_LL_ADDR]
 #define nd_opts_tgt_lladdr     nd_opt_array[ND_OPT_TARGET_LL_ADDR]
 #define nd_opts_pi             nd_opt_array[ND_OPT_PREFIX_INFO]
-#define nd_opts_pi_end         nd_opt_piend
+#define nd_opts_pi_end         nd_opt_array[__ND_OPT_PREFIX_INFO_END]
 #define nd_opts_rh             nd_opt_array[ND_OPT_REDIRECT_HDR]
 #define nd_opts_mtu            nd_opt_array[ND_OPT_MTU]
 
 #define NDISC_OPT_SPACE(len) (((len)+2+7)&~7)
 
-static u8 *ndisc_fill_option(u8 *opt, int type, void *data, int data_len)
+/*
+ * Return the padding between the option length and the start of the
+ * link addr.  Currently only IP-over-InfiniBand needs this, although
+ * if RFC 3831 IPv6-over-Fibre Channel is ever implemented it may
+ * also need a pad of 2.
+ */
+static int ndisc_addr_option_pad(unsigned short type)
+{
+       switch (type) {
+       case ARPHRD_INFINIBAND: return 2;
+       default:                return 0;
+       }
+}
+
+static inline int ndisc_opt_addr_space(struct net_device *dev)
+{
+       return NDISC_OPT_SPACE(dev->addr_len + ndisc_addr_option_pad(dev->type));
+}
+
+static u8 *ndisc_fill_addr_option(u8 *opt, int type, void *data, int data_len,
+                                 unsigned short addr_type)
 {
        int space = NDISC_OPT_SPACE(data_len);
+       int pad   = ndisc_addr_option_pad(addr_type);
 
        opt[0] = type;
        opt[1] = space>>3;
+
+       memset(opt + 2, 0, pad);
+       opt   += pad;
+       space -= pad;
+
        memcpy(opt+2, data, data_len);
        data_len += 2;
        opt += data_len;
@@ -228,6 +259,13 @@ static struct ndisc_options *ndisc_parse_options(u8 *opt, int opt_len,
                        if (ndopts->nd_opt_array[nd_opt->nd_opt_type] == 0)
                                ndopts->nd_opt_array[nd_opt->nd_opt_type] = nd_opt;
                        break;
+#ifdef CONFIG_IPV6_ROUTE_INFO
+               case ND_OPT_ROUTE_INFO:
+                       ndopts->nd_opts_ri_end = nd_opt;
+                       if (!ndopts->nd_opts_ri)
+                               ndopts->nd_opts_ri = nd_opt;
+                       break;
+#endif
                default:
                        /*
                         * Unknown options must be silently ignored,
@@ -244,6 +282,17 @@ static struct ndisc_options *ndisc_parse_options(u8 *opt, int opt_len,
        return ndopts;
 }
 
+static inline u8 *ndisc_opt_addr_data(struct nd_opt_hdr *p,
+                                     struct net_device *dev)
+{
+       u8 *lladdr = (u8 *)(p + 1);
+       int lladdrlen = p->nd_opt_len << 3;
+       int prepad = ndisc_addr_option_pad(dev->type);
+       if (lladdrlen != NDISC_OPT_SPACE(dev->addr_len + prepad))
+               return NULL;
+       return (lladdr + prepad);
+}
+
 int ndisc_mc_map(struct in6_addr *addr, char *buf, struct net_device *dev, int dir)
 {
        switch (dev->type) {
@@ -258,6 +307,9 @@ int ndisc_mc_map(struct in6_addr *addr, char *buf, struct net_device *dev, int d
        case ARPHRD_ARCNET:
                ipv6_arcnet_mc_map(addr, buf);
                return 0;
+       case ARPHRD_INFINIBAND:
+               ipv6_ib_mc_map(addr, buf);
+               return 0;
        default:
                if (dir) {
                        memcpy(buf, dev->broadcast, dev->addr_len);
@@ -269,29 +321,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) {
@@ -354,7 +412,8 @@ static void pndisc_destructor(struct pneigh_entry *n)
  */
 
 static inline void ndisc_flow_init(struct flowi *fl, u8 type,
-                           struct in6_addr *saddr, struct in6_addr *daddr)
+                           struct in6_addr *saddr, struct in6_addr *daddr,
+                           int oif)
 {
        memset(fl, 0, sizeof(*fl));
        ipv6_addr_copy(&fl->fl6_src, saddr);
@@ -362,6 +421,8 @@ static inline void ndisc_flow_init(struct flowi *fl, u8 type,
        fl->proto               = IPPROTO_ICMPV6;
        fl->fl_icmp_type        = type;
        fl->fl_icmp_code        = 0;
+       fl->oif                 = oif;
+       security_sk_classify_flow(ndisc_socket->sk, fl);
 }
 
 static void ndisc_send_na(struct net_device *dev, struct neighbour *neigh,
@@ -388,31 +449,32 @@ static void ndisc_send_na(struct net_device *dev, struct neighbour *neigh,
                src_addr = solicited_addr;
                in6_ifa_put(ifp);
        } else {
-               if (ipv6_dev_get_saddr(dev, daddr, &tmpaddr, 0))
+               if (ipv6_dev_get_saddr(dev, daddr, &tmpaddr))
                        return;
                src_addr = &tmpaddr;
        }
 
-       ndisc_flow_init(&fl, NDISC_NEIGHBOUR_ADVERTISEMENT, src_addr, daddr);
+       ndisc_flow_init(&fl, NDISC_NEIGHBOUR_ADVERTISEMENT, src_addr, daddr,
+                       dev->ifindex);
 
        dst = ndisc_dst_alloc(dev, neigh, daddr, ip6_output);
        if (!dst)
                return;
 
        err = xfrm_lookup(&dst, &fl, NULL, 0);
-       if (err < 0) {
-               dst_release(dst);
+       if (err < 0)
                return;
-       }
 
        if (inc_opt) {
                if (dev->addr_len)
-                       len += NDISC_OPT_SPACE(dev->addr_len);
+                       len += ndisc_opt_addr_space(dev);
                else
                        inc_opt = 0;
        }
 
-       skb = sock_alloc_send_skb(sk, MAX_HEADER + len + LL_RESERVED_SPACE(dev),
+       skb = sock_alloc_send_skb(sk,
+                                 (MAX_HEADER + sizeof(struct ipv6hdr) +
+                                  len + LL_RESERVED_SPACE(dev)),
                                  1, &err);
 
        if (skb == NULL) {
@@ -436,13 +498,14 @@ static void ndisc_send_na(struct net_device *dev, struct neighbour *neigh,
         msg->icmph.icmp6_unused = 0;
         msg->icmph.icmp6_router    = router;
         msg->icmph.icmp6_solicited = solicited;
-        msg->icmph.icmp6_override  = !!override;
+        msg->icmph.icmp6_override  = override;
 
         /* Set the target address. */
        ipv6_addr_copy(&msg->target, solicited_addr);
 
        if (inc_opt)
-               ndisc_fill_option(msg->opt, ND_OPT_TARGET_LL_ADDR, dev->dev_addr, dev->addr_len);
+               ndisc_fill_addr_option(msg->opt, ND_OPT_TARGET_LL_ADDR, dev->dev_addr,
+                                      dev->addr_len, dev->type);
 
        /* checksum */
        msg->icmph.icmp6_cksum = csum_ipv6_magic(src_addr, daddr, len, 
@@ -452,11 +515,11 @@ static void ndisc_send_na(struct net_device *dev, struct neighbour *neigh,
 
        skb->dst = dst;
        idev = in6_dev_get(dst->dev);
-       IP6_INC_STATS(OutRequests);
+       IP6_INC_STATS(idev, IPSTATS_MIB_OUTREQUESTS);
        err = NF_HOOK(PF_INET6, NF_IP6_LOCAL_OUT, skb, NULL, dst->dev, dst_output);
        if (!err) {
-               ICMP6_INC_STATS(idev, Icmp6OutNeighborAdvertisements);
-               ICMP6_INC_STATS(idev, Icmp6OutMsgs);
+               ICMP6_INC_STATS(idev, ICMP6_MIB_OUTNEIGHBORADVERTISEMENTS);
+               ICMP6_INC_STATS(idev, ICMP6_MIB_OUTMSGS);
        }
 
        if (likely(idev != NULL))
@@ -484,24 +547,25 @@ void ndisc_send_ns(struct net_device *dev, struct neighbour *neigh,
                saddr = &addr_buf;
        }
 
-       ndisc_flow_init(&fl, NDISC_NEIGHBOUR_SOLICITATION, saddr, daddr);
+       ndisc_flow_init(&fl, NDISC_NEIGHBOUR_SOLICITATION, saddr, daddr,
+                       dev->ifindex);
 
        dst = ndisc_dst_alloc(dev, neigh, daddr, ip6_output);
        if (!dst)
                return;
 
        err = xfrm_lookup(&dst, &fl, NULL, 0);
-       if (err < 0) {
-               dst_release(dst);
+       if (err < 0)
                return;
-       }
 
        len = sizeof(struct icmp6hdr) + sizeof(struct in6_addr);
        send_llinfo = dev->addr_len && !ipv6_addr_any(saddr);
        if (send_llinfo)
-               len += NDISC_OPT_SPACE(dev->addr_len);
+               len += ndisc_opt_addr_space(dev);
 
-       skb = sock_alloc_send_skb(sk, MAX_HEADER + len + LL_RESERVED_SPACE(dev),
+       skb = sock_alloc_send_skb(sk,
+                                 (MAX_HEADER + sizeof(struct ipv6hdr) +
+                                  len + LL_RESERVED_SPACE(dev)),
                                  1, &err);
        if (skb == NULL) {
                ND_PRINTK0(KERN_ERR
@@ -525,7 +589,8 @@ void ndisc_send_ns(struct net_device *dev, struct neighbour *neigh,
        ipv6_addr_copy(&msg->target, solicit);
 
        if (send_llinfo)
-               ndisc_fill_option(msg->opt, ND_OPT_SOURCE_LL_ADDR, dev->dev_addr, dev->addr_len);
+               ndisc_fill_addr_option(msg->opt, ND_OPT_SOURCE_LL_ADDR, dev->dev_addr,
+                                      dev->addr_len, dev->type);
 
        /* checksum */
        msg->icmph.icmp6_cksum = csum_ipv6_magic(&skb->nh.ipv6h->saddr,
@@ -536,11 +601,11 @@ void ndisc_send_ns(struct net_device *dev, struct neighbour *neigh,
        /* send it! */
        skb->dst = dst;
        idev = in6_dev_get(dst->dev);
-       IP6_INC_STATS(OutRequests);
+       IP6_INC_STATS(idev, IPSTATS_MIB_OUTREQUESTS);
        err = NF_HOOK(PF_INET6, NF_IP6_LOCAL_OUT, skb, NULL, dst->dev, dst_output);
        if (!err) {
-               ICMP6_INC_STATS(idev, Icmp6OutNeighborSolicits);
-               ICMP6_INC_STATS(idev, Icmp6OutMsgs);
+               ICMP6_INC_STATS(idev, ICMP6_MIB_OUTNEIGHBORSOLICITS);
+               ICMP6_INC_STATS(idev, ICMP6_MIB_OUTMSGS);
        }
 
        if (likely(idev != NULL))
@@ -560,23 +625,24 @@ void ndisc_send_rs(struct net_device *dev, struct in6_addr *saddr,
         int len;
        int err;
 
-       ndisc_flow_init(&fl, NDISC_ROUTER_SOLICITATION, saddr, daddr);
+       ndisc_flow_init(&fl, NDISC_ROUTER_SOLICITATION, saddr, daddr,
+                       dev->ifindex);
 
        dst = ndisc_dst_alloc(dev, NULL, daddr, ip6_output);
        if (!dst)
                return;
 
        err = xfrm_lookup(&dst, &fl, NULL, 0);
-       if (err < 0) {
-               dst_release(dst);
+       if (err < 0)
                return;
-       }
 
        len = sizeof(struct icmp6hdr);
        if (dev->addr_len)
-               len += NDISC_OPT_SPACE(dev->addr_len);
+               len += ndisc_opt_addr_space(dev);
 
-        skb = sock_alloc_send_skb(sk, MAX_HEADER + len + LL_RESERVED_SPACE(dev),
+        skb = sock_alloc_send_skb(sk,
+                                 (MAX_HEADER + sizeof(struct ipv6hdr) +
+                                  len + LL_RESERVED_SPACE(dev)),
                                  1, &err);
        if (skb == NULL) {
                ND_PRINTK0(KERN_ERR
@@ -599,7 +665,8 @@ void ndisc_send_rs(struct net_device *dev, struct in6_addr *saddr,
        opt = (u8*) (hdr + 1);
 
        if (dev->addr_len)
-               ndisc_fill_option(opt, ND_OPT_SOURCE_LL_ADDR, dev->dev_addr, dev->addr_len);
+               ndisc_fill_addr_option(opt, ND_OPT_SOURCE_LL_ADDR, dev->dev_addr,
+                                      dev->addr_len, dev->type);
 
        /* checksum */
        hdr->icmp6_cksum = csum_ipv6_magic(&skb->nh.ipv6h->saddr, daddr, len,
@@ -609,11 +676,11 @@ void ndisc_send_rs(struct net_device *dev, struct in6_addr *saddr,
        /* send it! */
        skb->dst = dst;
        idev = in6_dev_get(dst->dev);
-       IP6_INC_STATS(OutRequests);     
+       IP6_INC_STATS(idev, IPSTATS_MIB_OUTREQUESTS);
        err = NF_HOOK(PF_INET6, NF_IP6_LOCAL_OUT, skb, NULL, dst->dev, dst_output);
        if (!err) {
-               ICMP6_INC_STATS(idev, Icmp6OutRouterSolicits);
-               ICMP6_INC_STATS(idev, Icmp6OutMsgs);
+               ICMP6_INC_STATS(idev, ICMP6_MIB_OUTROUTERSOLICITS);
+               ICMP6_INC_STATS(idev, ICMP6_MIB_OUTMSGS);
        }
 
        if (likely(idev != NULL))
@@ -648,7 +715,7 @@ static void ndisc_solicit(struct neighbour *neigh, struct sk_buff *skb)
                if (!(neigh->nud_state & NUD_VALID)) {
                        ND_PRINTK1(KERN_DEBUG
                                   "%s(): trying to ucast probe in NUD_INVALID: "
-                                  "%04x:%04x:%04x:%04x:%04x:%04x:%04x:%04x\n",
+                                  NIP6_FMT "\n",
                                   __FUNCTION__,
                                   NIP6(*target));
                }
@@ -669,15 +736,16 @@ static void ndisc_recv_ns(struct sk_buff *skb)
        struct in6_addr *saddr = &skb->nh.ipv6h->saddr;
        struct in6_addr *daddr = &skb->nh.ipv6h->daddr;
        u8 *lladdr = NULL;
-       int lladdrlen = 0;
        u32 ndoptlen = skb->tail - msg->opt;
        struct ndisc_options ndopts;
        struct net_device *dev = skb->dev;
        struct inet6_ifaddr *ifp;
        struct inet6_dev *idev = NULL;
        struct neighbour *neigh;
+       struct pneigh_entry *pneigh = NULL;
        int dad = ipv6_addr_any(saddr);
        int inc;
+       int is_router;
 
        if (ipv6_addr_is_multicast(&msg->target)) {
                ND_PRINTK2(KERN_WARNING 
@@ -706,9 +774,8 @@ static void ndisc_recv_ns(struct sk_buff *skb)
        }
 
        if (ndopts.nd_opts_src_lladdr) {
-               lladdr = (u8*)(ndopts.nd_opts_src_lladdr + 1);
-               lladdrlen = ndopts.nd_opts_src_lladdr->nd_opt_len << 3;
-               if (lladdrlen != NDISC_OPT_SPACE(dev->addr_len)) {
+               lladdr = ndisc_opt_addr_data(ndopts.nd_opts_src_lladdr, dev);
+               if (!lladdr) {
                        ND_PRINTK2(KERN_WARNING
                                   "ICMPv6 NS: invalid link-layer address length\n");
                        return;
@@ -763,8 +830,10 @@ static void ndisc_recv_ns(struct sk_buff *skb)
 
                if (ipv6_chk_acast_addr(dev, &msg->target) ||
                    (idev->cnf.forwarding && 
-                    pneigh_lookup(&nd_tbl, &msg->target, dev, 0))) {
-                       if (skb->stamp.tv_sec != LOCALLY_ENQUEUED &&
+                    (ipv6_devconf.proxy_ndp || idev->cnf.proxy_ndp) &&
+                    (pneigh = pneigh_lookup(&nd_tbl,
+                                            &msg->target, dev, 0)) != NULL)) {
+                       if (!(NEIGH_CB(skb)->flags & LOCALLY_ENQUEUED) &&
                            skb->pkt_type != PACKET_HOST &&
                            inc != 0 &&
                            idev->nd_parms->proxy_delay != 0) {
@@ -784,29 +853,35 @@ static void ndisc_recv_ns(struct sk_buff *skb)
                        goto out;
        }
 
+       is_router = !!(pneigh ? pneigh->flags & NTF_ROUTER : idev->cnf.forwarding);
+
        if (dad) {
                struct in6_addr maddr;
 
                ipv6_addr_all_nodes(&maddr);
                ndisc_send_na(dev, NULL, &maddr, &msg->target,
-                             idev->cnf.forwarding, 0, (ifp != NULL), 1);
+                             is_router, 0, (ifp != NULL), 1);
                goto out;
        }
 
        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, 
+                             is_router,
                              1, (ifp != NULL && inc), inc);
                if (neigh)
                        neigh_release(neigh);
@@ -827,7 +902,6 @@ static void ndisc_recv_na(struct sk_buff *skb)
        struct in6_addr *saddr = &skb->nh.ipv6h->saddr;
        struct in6_addr *daddr = &skb->nh.ipv6h->daddr;
        u8 *lladdr = NULL;
-       int lladdrlen = 0;
        u32 ndoptlen = skb->tail - msg->opt;
        struct ndisc_options ndopts;
        struct net_device *dev = skb->dev;
@@ -859,9 +933,8 @@ static void ndisc_recv_na(struct sk_buff *skb)
                return;
        }
        if (ndopts.nd_opts_tgt_lladdr) {
-               lladdr = (u8*)(ndopts.nd_opts_tgt_lladdr + 1);
-               lladdrlen = ndopts.nd_opts_tgt_lladdr->nd_opt_len << 3;
-               if (lladdrlen != NDISC_OPT_SPACE(dev->addr_len)) {
+               lladdr = ndisc_opt_addr_data(ndopts.nd_opts_tgt_lladdr, dev);
+               if (!lladdr) {
                        ND_PRINTK2(KERN_WARNING
                                   "ICMPv6 NA: invalid link-layer address length\n");
                        return;
@@ -886,37 +959,112 @@ 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;
+
+               if (neigh->nud_state & NUD_FAILED)
+                       goto out;
+
+               /*
+                * Don't update the neighbor cache entry on a proxy NA from
+                * ourselves because either the proxied node is off link or it
+                * has already sent a NA to us.
+                */
+               if (lladdr && !memcmp(lladdr, dev->dev_addr, dev->addr_len) &&
+                   ipv6_devconf.forwarding && ipv6_devconf.proxy_ndp &&
+                   pneigh_lookup(&nd_tbl, &msg->target, dev, 0)) {
+                       /* XXX: idev->cnf.prixy_ndp */
+                       goto out;
                }
 
                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);
+               }
+
+out:
                neigh_release(neigh);
        }
 }
 
+static void ndisc_recv_rs(struct sk_buff *skb)
+{
+       struct rs_msg *rs_msg = (struct rs_msg *) skb->h.raw;
+       unsigned long ndoptlen = skb->len - sizeof(*rs_msg);
+       struct neighbour *neigh;
+       struct inet6_dev *idev;
+       struct in6_addr *saddr = &skb->nh.ipv6h->saddr;
+       struct ndisc_options ndopts;
+       u8 *lladdr = NULL;
+
+       if (skb->len < sizeof(*rs_msg))
+               return;
+
+       idev = in6_dev_get(skb->dev);
+       if (!idev) {
+               if (net_ratelimit())
+                       ND_PRINTK1("ICMP6 RS: can't find in6 device\n");
+               return;
+       }
+
+       /* Don't accept RS if we're not in router mode */
+       if (!idev->cnf.forwarding)
+               goto out;
+
+       /*
+        * Don't update NCE if src = ::;
+        * this implies that the source node has no ip address assigned yet.
+        */
+       if (ipv6_addr_any(saddr))
+               goto out;
+
+       /* Parse ND options */
+       if (!ndisc_parse_options(rs_msg->opt, ndoptlen, &ndopts)) {
+               if (net_ratelimit())
+                       ND_PRINTK2("ICMP6 NS: invalid ND option, ignored\n");
+               goto out;
+       }
+
+       if (ndopts.nd_opts_src_lladdr) {
+               lladdr = ndisc_opt_addr_data(ndopts.nd_opts_src_lladdr,
+                                            skb->dev);
+               if (!lladdr)
+                       goto out;
+       }
+
+       neigh = __neigh_lookup(&nd_tbl, saddr, skb->dev, 1);
+       if (neigh) {
+               neigh_update(neigh, lladdr, NUD_STALE,
+                            NEIGH_UPDATE_F_WEAK_OVERRIDE|
+                            NEIGH_UPDATE_F_OVERRIDE|
+                            NEIGH_UPDATE_F_OVERRIDE_ISROUTER);
+               neigh_release(neigh);
+       }
+out:
+       in6_dev_put(idev);
+}
+
 static void ndisc_router_discovery(struct sk_buff *skb)
 {
         struct ra_msg *ra_msg = (struct ra_msg *) skb->h.raw;
-       struct neighbour *neigh;
+       struct neighbour *neigh = NULL;
        struct inet6_dev *in6_dev;
-       struct rt6_info *rt;
+       struct rt6_info *rt = NULL;
        int lifetime;
        struct ndisc_options ndopts;
        int optlen;
+       unsigned int pref = 0;
 
        __u8 * opt = (__u8 *)(ra_msg + 1);
 
@@ -975,12 +1123,27 @@ static void ndisc_router_discovery(struct sk_buff *skb)
                                (ra_msg->icmph.icmp6_addrconf_other ?
                                        IF_RA_OTHERCONF : 0);
 
+       if (!in6_dev->cnf.accept_ra_defrtr)
+               goto skip_defrtr;
+
        lifetime = ntohs(ra_msg->icmph.icmp6_rt_lifetime);
 
+#ifdef CONFIG_IPV6_ROUTER_PREF
+       pref = ra_msg->icmph.icmp6_router_pref;
+       /* 10b is handled as if it were 00b (medium) */
+       if (pref == ICMPV6_ROUTER_PREF_INVALID ||
+           in6_dev->cnf.accept_ra_rtr_pref)
+               pref = ICMPV6_ROUTER_PREF_MEDIUM;
+#endif
+
        rt = rt6_get_dflt_router(&skb->nh.ipv6h->saddr, skb->dev);
 
+       if (rt)
+               neigh = rt->rt6i_nexthop;
+
        if (rt && lifetime == 0) {
-               ip6_del_rt(rt, NULL, NULL);
+               neigh_clone(neigh);
+               ip6_del_rt(rt);
                rt = NULL;
        }
 
@@ -988,7 +1151,7 @@ static void ndisc_router_discovery(struct sk_buff *skb)
                ND_PRINTK3(KERN_DEBUG
                           "ICMPv6 RA: adding default router.\n");
 
-               rt = rt6_add_dflt_router(&skb->nh.ipv6h->saddr, skb->dev);
+               rt = rt6_add_dflt_router(&skb->nh.ipv6h->saddr, skb->dev, pref);
                if (rt == NULL) {
                        ND_PRINTK0(KERN_ERR
                                   "ICMPv6 RA: %s() failed to add default route.\n",
@@ -1007,20 +1170,20 @@ static void ndisc_router_discovery(struct sk_buff *skb)
                        return;
                }
                neigh->flags |= NTF_ROUTER;
-
-               /*
-                *      If we where using an "all destinations on link" route
-                *      delete it
-                */
-
-               rt6_purge_dflt_routers(RTF_ALLONLINK);
+       } else if (rt) {
+               rt->rt6i_flags |= (rt->rt6i_flags & ~RTF_PREF_MASK) | RTF_PREF(pref);
        }
 
        if (rt)
                rt->rt6i_expires = jiffies + (HZ * lifetime);
 
-       if (ra_msg->icmph.icmp6_hop_limit)
+       if (ra_msg->icmph.icmp6_hop_limit) {
                in6_dev->cnf.hop_limit = ra_msg->icmph.icmp6_hop_limit;
+               if (rt)
+                       rt->u.dst.metrics[RTAX_HOPLIMIT-1] = ra_msg->icmph.icmp6_hop_limit;
+       }
+
+skip_defrtr:
 
        /*
         *      Update Reachable Time and Retrans Timer
@@ -1059,22 +1222,42 @@ static void ndisc_router_discovery(struct sk_buff *skb)
         *      Process options.
         */
 
-       if (rt && (neigh = rt->rt6i_nexthop) != NULL) {
+       if (!neigh)
+               neigh = __neigh_lookup(&nd_tbl, &skb->nh.ipv6h->saddr,
+                                      skb->dev, 1);
+       if (neigh) {
                u8 *lladdr = NULL;
-               int lladdrlen;
                if (ndopts.nd_opts_src_lladdr) {
-                       lladdr = (u8*)((ndopts.nd_opts_src_lladdr)+1);
-                       lladdrlen = ndopts.nd_opts_src_lladdr->nd_opt_len << 3;
-                       if (lladdrlen != NDISC_OPT_SPACE(skb->dev->addr_len)) {
+                       lladdr = ndisc_opt_addr_data(ndopts.nd_opts_src_lladdr,
+                                                    skb->dev);
+                       if (!lladdr) {
                                ND_PRINTK2(KERN_WARNING
                                           "ICMPv6 RA: invalid link-layer address length\n");
                                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) {
+#ifdef CONFIG_IPV6_ROUTE_INFO
+       if (in6_dev->cnf.accept_ra_rtr_pref && ndopts.nd_opts_ri) {
+               struct nd_opt_hdr *p;
+               for (p = ndopts.nd_opts_ri;
+                    p;
+                    p = ndisc_next_option(p, ndopts.nd_opts_ri_end)) {
+                       if (((struct route_info *)p)->prefix_len > in6_dev->cnf.accept_ra_rt_info_max_plen)
+                               continue;
+                       rt6_route_rcv(skb->dev, (u8*)p, (p->nd_opt_len) << 3,
+                                     &skb->nh.ipv6h->saddr);
+               }
+       }
+#endif
+
+       if (in6_dev->cnf.accept_ra_pinfo && ndopts.nd_opts_pi) {
                struct nd_opt_hdr *p;
                for (p = ndopts.nd_opts_pi;
                     p;
@@ -1084,10 +1267,11 @@ static void ndisc_router_discovery(struct sk_buff *skb)
        }
 
        if (ndopts.nd_opts_mtu) {
+               __be32 n;
                u32 mtu;
 
-               memcpy(&mtu, ((u8*)(ndopts.nd_opts_mtu+1))+2, sizeof(mtu));
-               mtu = ntohl(mtu);
+               memcpy(&n, ((u8*)(ndopts.nd_opts_mtu+1))+2, sizeof(mtu));
+               mtu = ntohl(n);
 
                if (mtu < IPV6_MIN_MTU || mtu > skb->dev->mtu) {
                        ND_PRINTK2(KERN_WARNING
@@ -1110,6 +1294,8 @@ static void ndisc_router_discovery(struct sk_buff *skb)
 out:
        if (rt)
                dst_release(&rt->u.dst);
+       else if (neigh)
+               neigh_release(neigh);
        in6_dev_put(in6_dev);
 }
 
@@ -1124,7 +1310,6 @@ static void ndisc_redirect_rcv(struct sk_buff *skb)
        struct ndisc_options ndopts;
        int optlen;
        u8 *lladdr = NULL;
-       int lladdrlen;
 
        if (!(ipv6_addr_type(&skb->nh.ipv6h->saddr) & IPV6_ADDR_LINKLOCAL)) {
                ND_PRINTK2(KERN_WARNING
@@ -1151,7 +1336,7 @@ static void ndisc_redirect_rcv(struct sk_buff *skb)
                return;
        }
 
-       if (ipv6_addr_cmp(dest, target) == 0) {
+       if (ipv6_addr_equal(dest, target)) {
                on_link = 1;
        } else if (!(ipv6_addr_type(target) & IPV6_ADDR_LINKLOCAL)) {
                ND_PRINTK2(KERN_WARNING 
@@ -1179,28 +1364,21 @@ static void ndisc_redirect_rcv(struct sk_buff *skb)
                return;
        }
        if (ndopts.nd_opts_tgt_lladdr) {
-               lladdr = (u8*)(ndopts.nd_opts_tgt_lladdr + 1);
-               lladdrlen = ndopts.nd_opts_tgt_lladdr->nd_opt_len << 3;
-               if (lladdrlen != NDISC_OPT_SPACE(skb->dev->addr_len)) {
+               lladdr = ndisc_opt_addr_data(ndopts.nd_opts_tgt_lladdr,
+                                            skb->dev);
+               if (!lladdr) {
                        ND_PRINTK2(KERN_WARNING
                                   "ICMPv6 Redirect: invalid link-layer address length\n");
                        in6_dev_put(in6_dev);
                        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->daddr,
+                            &skb->nh.ipv6h->saddr, neigh, lladdr,
+                            on_link);
                neigh_release(neigh);
        }
        in6_dev_put(in6_dev);
@@ -1224,6 +1402,7 @@ void ndisc_send_redirect(struct sk_buff *skb, struct neighbour *neigh,
        int rd_len;
        int err;
        int hlen;
+       u8 ha_buf[MAX_ADDR_LEN], *ha = NULL;
 
        dev = skb->dev;
 
@@ -1234,18 +1413,23 @@ void ndisc_send_redirect(struct sk_buff *skb, struct neighbour *neigh,
                return;
        }
 
-       ndisc_flow_init(&fl, NDISC_REDIRECT, &saddr_buf, &skb->nh.ipv6h->saddr);
+       if (!ipv6_addr_equal(&skb->nh.ipv6h->daddr, target) &&
+           !(ipv6_addr_type(target) & IPV6_ADDR_LINKLOCAL)) {
+               ND_PRINTK2(KERN_WARNING
+                       "ICMPv6 Redirect: target address is not link-local.\n");
+               return;
+       }
 
-       rt = rt6_lookup(&skb->nh.ipv6h->saddr, NULL, dev->ifindex, 1);
-       if (rt == NULL)
+       ndisc_flow_init(&fl, NDISC_REDIRECT, &saddr_buf, &skb->nh.ipv6h->saddr,
+                       dev->ifindex);
+
+       dst = ip6_route_output(NULL, &fl);
+       if (dst == NULL)
                return;
-       dst = &rt->u.dst;
 
        err = xfrm_lookup(&dst, &fl, NULL, 0);
-       if (err) {
-               dst_release(dst);
+       if (err)
                return;
-       }
 
        rt = (struct rt6_info *) dst;
 
@@ -1261,16 +1445,14 @@ void ndisc_send_redirect(struct sk_buff *skb, struct neighbour *neigh,
        }
 
        if (dev->addr_len) {
-               if (neigh->nud_state&NUD_VALID) {
-                       len  += NDISC_OPT_SPACE(dev->addr_len);
-               } else {
-                       /* If nexthop is not valid, do not redirect!
-                          We will make it later, when will be sure,
-                          that it is alive.
-                        */
-                       dst_release(dst);
-                       return;
-               }
+               read_lock_bh(&neigh->lock);
+               if (neigh->nud_state & NUD_VALID) {
+                       memcpy(ha_buf, neigh->ha, dev->addr_len);
+                       read_unlock_bh(&neigh->lock);
+                       ha = ha_buf;
+                       len += ndisc_opt_addr_space(dev);
+               } else
+                       read_unlock_bh(&neigh->lock);
        }
 
        rd_len = min_t(unsigned int,
@@ -1278,7 +1460,9 @@ void ndisc_send_redirect(struct sk_buff *skb, struct neighbour *neigh,
        rd_len &= ~0x7;
        len += rd_len;
 
-       buff = sock_alloc_send_skb(sk, MAX_HEADER + len + LL_RESERVED_SPACE(dev),
+       buff = sock_alloc_send_skb(sk,
+                                  (MAX_HEADER + sizeof(struct ipv6hdr) +
+                                   len + LL_RESERVED_SPACE(dev)),
                                   1, &err);
        if (buff == NULL) {
                ND_PRINTK0(KERN_ERR
@@ -1315,8 +1499,9 @@ void ndisc_send_redirect(struct sk_buff *skb, struct neighbour *neigh,
         *      include target_address option
         */
 
-       if (dev->addr_len)
-               opt = ndisc_fill_option(opt, ND_OPT_TARGET_LL_ADDR, neigh->ha, dev->addr_len);
+       if (ha)
+               opt = ndisc_fill_addr_option(opt, ND_OPT_TARGET_LL_ADDR, ha,
+                                            dev->addr_len, dev->type);
 
        /*
         *      build redirect option and copy skb over to the new packet.
@@ -1335,11 +1520,11 @@ void ndisc_send_redirect(struct sk_buff *skb, struct neighbour *neigh,
 
        buff->dst = dst;
        idev = in6_dev_get(dst->dev);
-       IP6_INC_STATS(OutRequests);
+       IP6_INC_STATS(idev, IPSTATS_MIB_OUTREQUESTS);
        err = NF_HOOK(PF_INET6, NF_IP6_LOCAL_OUT, buff, NULL, dst->dev, dst_output);
        if (!err) {
-               ICMP6_INC_STATS(idev, Icmp6OutRedirects);
-               ICMP6_INC_STATS(idev, Icmp6OutMsgs);
+               ICMP6_INC_STATS(idev, ICMP6_MIB_OUTREDIRECTS);
+               ICMP6_INC_STATS(idev, ICMP6_MIB_OUTMSGS);
        }
 
        if (likely(idev != NULL))
@@ -1348,7 +1533,7 @@ void ndisc_send_redirect(struct sk_buff *skb, struct neighbour *neigh,
 
 static void pndisc_redo(struct sk_buff *skb)
 {
-       ndisc_rcv(skb);
+       ndisc_recv_ns(skb);
        kfree_skb(skb);
 }
 
@@ -1377,6 +1562,8 @@ int ndisc_rcv(struct sk_buff *skb)
                return 0;
        }
 
+       memset(NEIGH_CB(skb), 0, sizeof(struct neighbour_cb));
+
        switch (msg->icmph.icmp6_type) {
        case NDISC_NEIGHBOUR_SOLICITATION:
                ndisc_recv_ns(skb);
@@ -1386,6 +1573,10 @@ int ndisc_rcv(struct sk_buff *skb)
                ndisc_recv_na(skb);
                break;
 
+       case NDISC_ROUTER_SOLICITATION:
+               ndisc_recv_rs(skb);
+               break;
+
        case NDISC_ROUTER_ADVERTISEMENT:
                ndisc_router_discovery(skb);
                break;
@@ -1405,11 +1596,11 @@ static int ndisc_netdev_event(struct notifier_block *this, unsigned long event,
        switch (event) {
        case NETDEV_CHANGEADDR:
                neigh_changeaddr(&nd_tbl, dev);
-               fib6_run_gc(0);
+               fib6_run_gc(~0UL);
                break;
        case NETDEV_DOWN:
                neigh_ifdown(&nd_tbl, dev);
-               fib6_run_gc(0);
+               fib6_run_gc(~0UL);
                break;
        default:
                break;
@@ -1423,18 +1614,103 @@ static struct notifier_block ndisc_netdev_notifier = {
 };
 
 #ifdef CONFIG_SYSCTL
-int ndisc_ifinfo_sysctl_change(struct ctl_table *ctl, int write, struct file * filp, void __user *buffer, size_t *lenp)
+static void ndisc_warn_deprecated_sysctl(struct ctl_table *ctl,
+                                        const char *func, const char *dev_name)
+{
+       static char warncomm[TASK_COMM_LEN];
+       static int warned;
+       if (strcmp(warncomm, current->comm) && warned < 5) {
+               strcpy(warncomm, current->comm);
+               printk(KERN_WARNING
+                       "process `%s' is using deprecated sysctl (%s) "
+                       "net.ipv6.neigh.%s.%s; "
+                       "Use net.ipv6.neigh.%s.%s_ms "
+                       "instead.\n",
+                       warncomm, func,
+                       dev_name, ctl->procname,
+                       dev_name, ctl->procname);
+               warned++;
+       }
+}
+
+int ndisc_ifinfo_sysctl_change(struct ctl_table *ctl, int write, struct file * filp, void __user *buffer, size_t *lenp, loff_t *ppos)
 {
        struct net_device *dev = ctl->extra1;
        struct inet6_dev *idev;
+       int ret;
 
-       if (write && dev && (idev = in6_dev_get(dev)) != NULL) {
+       if (ctl->ctl_name == NET_NEIGH_RETRANS_TIME ||
+           ctl->ctl_name == NET_NEIGH_REACHABLE_TIME)
+               ndisc_warn_deprecated_sysctl(ctl, "syscall", dev ? dev->name : "default");
+
+       switch (ctl->ctl_name) {
+       case NET_NEIGH_RETRANS_TIME:
+               ret = proc_dointvec(ctl, write, filp, buffer, lenp, ppos);
+               break;
+       case NET_NEIGH_REACHABLE_TIME:
+               ret = proc_dointvec_jiffies(ctl, write,
+                                           filp, buffer, lenp, ppos);
+               break;
+       case NET_NEIGH_RETRANS_TIME_MS:
+       case NET_NEIGH_REACHABLE_TIME_MS:
+               ret = proc_dointvec_ms_jiffies(ctl, write,
+                                              filp, buffer, lenp, ppos);
+               break;
+       default:
+               ret = -1;
+       }
+
+       if (write && ret == 0 && dev && (idev = in6_dev_get(dev)) != NULL) {
+               if (ctl->ctl_name == NET_NEIGH_REACHABLE_TIME ||
+                   ctl->ctl_name == NET_NEIGH_REACHABLE_TIME_MS)
+                       idev->nd_parms->reachable_time = neigh_rand_reach_time(idev->nd_parms->base_reachable_time);
+               idev->tstamp = jiffies;
+               inet6_ifinfo_notify(RTM_NEWLINK, idev);
+               in6_dev_put(idev);
+       }
+       return ret;
+}
+
+static int ndisc_ifinfo_sysctl_strategy(ctl_table *ctl, int __user *name,
+                                       int nlen, void __user *oldval,
+                                       size_t __user *oldlenp,
+                                       void __user *newval, size_t newlen)
+{
+       struct net_device *dev = ctl->extra1;
+       struct inet6_dev *idev;
+       int ret;
+
+       if (ctl->ctl_name == NET_NEIGH_RETRANS_TIME ||
+           ctl->ctl_name == NET_NEIGH_REACHABLE_TIME)
+               ndisc_warn_deprecated_sysctl(ctl, "procfs", dev ? dev->name : "default");
+
+       switch (ctl->ctl_name) {
+       case NET_NEIGH_REACHABLE_TIME:
+               ret = sysctl_jiffies(ctl, name, nlen,
+                                    oldval, oldlenp, newval, newlen);
+               break;
+       case NET_NEIGH_RETRANS_TIME_MS:
+       case NET_NEIGH_REACHABLE_TIME_MS:
+                ret = sysctl_ms_jiffies(ctl, name, nlen,
+                                        oldval, oldlenp, newval, newlen);
+                break;
+       default:
+               ret = 0;
+       }
+
+       if (newval && newlen && ret > 0 &&
+           dev && (idev = in6_dev_get(dev)) != NULL) {
+               if (ctl->ctl_name == NET_NEIGH_REACHABLE_TIME ||
+                   ctl->ctl_name == NET_NEIGH_REACHABLE_TIME_MS)
+                       idev->nd_parms->reachable_time = neigh_rand_reach_time(idev->nd_parms->base_reachable_time);
                idev->tstamp = jiffies;
                inet6_ifinfo_notify(RTM_NEWLINK, idev);
                in6_dev_put(idev);
        }
-       return proc_dointvec(ctl, write, filp, buffer, lenp);
+
+       return ret;
 }
+
 #endif
 
 int __init ndisc_init(struct net_proto_family *ops)
@@ -1468,7 +1744,9 @@ int __init ndisc_init(struct net_proto_family *ops)
 
 #ifdef CONFIG_SYSCTL
        neigh_sysctl_register(NULL, &nd_tbl.parms, NET_IPV6, NET_IPV6_NEIGH, 
-                             "ipv6", &ndisc_ifinfo_sysctl_change);
+                             "ipv6",
+                             &ndisc_ifinfo_sysctl_change,
+                             &ndisc_ifinfo_sysctl_strategy);
 #endif
 
        register_netdevice_notifier(&ndisc_netdev_notifier);
@@ -1477,6 +1755,7 @@ int __init ndisc_init(struct net_proto_family *ops)
 
 void ndisc_cleanup(void)
 {
+       unregister_netdevice_notifier(&ndisc_netdev_notifier);
 #ifdef CONFIG_SYSCTL
        neigh_sysctl_unregister(&nd_tbl.parms);
 #endif