ipv6: fix problem with expired dst cache
[linux-flexiantxendom0-3.2.10.git] / net / ipv6 / ndisc.c
index 2a6752d..176b469 100644 (file)
@@ -59,6 +59,7 @@
 #include <linux/route.h>
 #include <linux/init.h>
 #include <linux/rcupdate.h>
+#include <linux/slab.h>
 #ifdef CONFIG_SYSCTL
 #include <linux/sysctl.h>
 #endif
@@ -90,7 +91,9 @@
 #include <linux/netfilter.h>
 #include <linux/netfilter_ipv6.h>
 
-static u32 ndisc_hash(const void *pkey, const struct net_device *dev);
+static u32 ndisc_hash(const void *pkey,
+                     const struct net_device *dev,
+                     __u32 *hash_rnd);
 static int ndisc_constructor(struct neighbour *neigh);
 static void ndisc_solicit(struct neighbour *neigh, struct sk_buff *skb);
 static void ndisc_error_report(struct neighbour *neigh, struct sk_buff *skb);
@@ -98,38 +101,31 @@ static int pndisc_constructor(struct pneigh_entry *n);
 static void pndisc_destructor(struct pneigh_entry *n);
 static void pndisc_redo(struct sk_buff *skb);
 
-static struct neigh_ops ndisc_generic_ops = {
+static const struct neigh_ops ndisc_generic_ops = {
        .family =               AF_INET6,
        .solicit =              ndisc_solicit,
        .error_report =         ndisc_error_report,
        .output =               neigh_resolve_output,
        .connected_output =     neigh_connected_output,
-       .hh_output =            dev_queue_xmit,
-       .queue_xmit =           dev_queue_xmit,
 };
 
-static struct neigh_ops ndisc_hh_ops = {
+static const struct neigh_ops ndisc_hh_ops = {
        .family =               AF_INET6,
        .solicit =              ndisc_solicit,
        .error_report =         ndisc_error_report,
        .output =               neigh_resolve_output,
        .connected_output =     neigh_resolve_output,
-       .hh_output =            dev_queue_xmit,
-       .queue_xmit =           dev_queue_xmit,
 };
 
 
-static struct neigh_ops ndisc_direct_ops = {
+static const struct neigh_ops ndisc_direct_ops = {
        .family =               AF_INET6,
-       .output =               dev_queue_xmit,
-       .connected_output =     dev_queue_xmit,
-       .hh_output =            dev_queue_xmit,
-       .queue_xmit =           dev_queue_xmit,
+       .output =               neigh_direct_output,
+       .connected_output =     neigh_direct_output,
 };
 
 struct neigh_table nd_tbl = {
        .family =       AF_INET6,
-       .entry_size =   sizeof(struct neighbour) + sizeof(struct in6_addr),
        .key_len =      sizeof(struct in6_addr),
        .hash =         ndisc_hash,
        .constructor =  ndisc_constructor,
@@ -138,18 +134,18 @@ struct neigh_table nd_tbl = {
        .proxy_redo =   pndisc_redo,
        .id =           "ndisc_cache",
        .parms = {
-               .tbl =                  &nd_tbl,
-               .base_reachable_time =  30 * HZ,
-               .retrans_time =  1 * HZ,
-               .gc_staletime = 60 * HZ,
-               .reachable_time =               30 * HZ,
-               .delay_probe_time =      5 * HZ,
-               .queue_len =             3,
-               .ucast_probes =  3,
-               .mcast_probes =  3,
-               .anycast_delay =         1 * HZ,
-               .proxy_delay =          (8 * HZ) / 10,
-               .proxy_qlen =           64,
+               .tbl                    = &nd_tbl,
+               .base_reachable_time    = ND_REACHABLE_TIME,
+               .retrans_time           = ND_RETRANS_TIMER,
+               .gc_staletime           = 60 * HZ,
+               .reachable_time         = ND_REACHABLE_TIME,
+               .delay_probe_time       = 5 * HZ,
+               .queue_len_bytes        = 64*1024,
+               .ucast_probes           = 3,
+               .mcast_probes           = 3,
+               .anycast_delay          = 1 * HZ,
+               .proxy_delay            = (8 * HZ) / 10,
+               .proxy_qlen             = 64,
        },
        .gc_interval =    30 * HZ,
        .gc_thresh1 =    128,
@@ -227,12 +223,12 @@ static struct nd_opt_hdr *ndisc_next_option(struct nd_opt_hdr *cur,
        do {
                cur = ((void *)cur) + (cur->nd_opt_len << 3);
        } while(cur < end && cur->nd_opt_type != type);
-       return (cur <= end && cur->nd_opt_type == type ? cur : NULL);
+       return cur <= end && cur->nd_opt_type == type ? cur : NULL;
 }
 
 static inline int ndisc_is_useropt(struct nd_opt_hdr *opt)
 {
-       return (opt->nd_opt_type == ND_OPT_RDNSS);
+       return opt->nd_opt_type == ND_OPT_RDNSS;
 }
 
 static struct nd_opt_hdr *ndisc_next_useropt(struct nd_opt_hdr *cur,
@@ -243,7 +239,7 @@ static struct nd_opt_hdr *ndisc_next_useropt(struct nd_opt_hdr *cur,
        do {
                cur = ((void *)cur) + (cur->nd_opt_len << 3);
        } while(cur < end && !ndisc_is_useropt(cur));
-       return (cur <= end && ndisc_is_useropt(cur) ? cur : NULL);
+       return cur <= end && ndisc_is_useropt(cur) ? cur : NULL;
 }
 
 static struct ndisc_options *ndisc_parse_options(u8 *opt, int opt_len,
@@ -318,10 +314,10 @@ static inline u8 *ndisc_opt_addr_data(struct nd_opt_hdr *p,
        int prepad = ndisc_addr_option_pad(dev->type);
        if (lladdrlen != NDISC_OPT_SPACE(dev->addr_len + prepad))
                return NULL;
-       return (lladdr + prepad);
+       return lladdr + prepad;
 }
 
-int ndisc_mc_map(struct in6_addr *addr, char *buf, struct net_device *dev, int dir)
+int ndisc_mc_map(const struct in6_addr *addr, char *buf, struct net_device *dev, int dir)
 {
        switch (dev->type) {
        case ARPHRD_ETHER:
@@ -338,6 +334,8 @@ int ndisc_mc_map(struct in6_addr *addr, char *buf, struct net_device *dev, int d
        case ARPHRD_INFINIBAND:
                ipv6_ib_mc_map(addr, dev->broadcast, buf);
                return 0;
+       case ARPHRD_IPGRE:
+               return ipv6_ipgre_mc_map(addr, dev->broadcast, buf);
        default:
                if (dir) {
                        memcpy(buf, dev->broadcast, dev->addr_len);
@@ -349,16 +347,11 @@ int ndisc_mc_map(struct in6_addr *addr, char *buf, struct net_device *dev, int d
 
 EXPORT_SYMBOL(ndisc_mc_map);
 
-static u32 ndisc_hash(const void *pkey, const struct net_device *dev)
+static u32 ndisc_hash(const void *pkey,
+                     const struct net_device *dev,
+                     __u32 *hash_rnd)
 {
-       const u32 *p32 = pkey;
-       u32 addr_hash, i;
-
-       addr_hash = 0;
-       for (i = 0; i < (sizeof(struct in6_addr) / sizeof(u32)); i++)
-               addr_hash ^= *p32++;
-
-       return jhash_2words(addr_hash, dev->ifindex, nd_tbl.hash_rnd);
+       return ndisc_hashfn(pkey, dev, hash_rnd);
 }
 
 static int ndisc_constructor(struct neighbour *neigh)
@@ -369,23 +362,20 @@ static int ndisc_constructor(struct neighbour *neigh)
        struct neigh_parms *parms;
        int is_multicast = ipv6_addr_is_multicast(addr);
 
-       rcu_read_lock();
        in6_dev = in6_dev_get(dev);
        if (in6_dev == NULL) {
-               rcu_read_unlock();
                return -EINVAL;
        }
 
        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->header_ops) {
                neigh->nud_state = NUD_NOARP;
                neigh->ops = &ndisc_direct_ops;
-               neigh->output = neigh->ops->queue_xmit;
+               neigh->output = neigh_direct_output;
        } else {
                if (is_multicast) {
                        neigh->nud_state = NUD_NOARP;
@@ -437,38 +427,22 @@ static void pndisc_destructor(struct pneigh_entry *n)
        ipv6_dev_mc_dec(dev, &maddr);
 }
 
-/*
- *     Send a Neighbour Advertisement
- */
-static void __ndisc_send(struct net_device *dev,
-                        struct neighbour *neigh,
-                        const struct in6_addr *daddr,
-                        const struct in6_addr *saddr,
-                        struct icmp6hdr *icmp6h, const struct in6_addr *target,
-                        int llinfo)
+struct sk_buff *ndisc_build_skb(struct net_device *dev,
+                               const struct in6_addr *daddr,
+                               const struct in6_addr *saddr,
+                               struct icmp6hdr *icmp6h,
+                               const struct in6_addr *target,
+                               int llinfo)
 {
-       struct flowi fl;
-       struct dst_entry *dst;
        struct net *net = dev_net(dev);
        struct sock *sk = net->ipv6.ndisc_sk;
        struct sk_buff *skb;
        struct icmp6hdr *hdr;
-       struct inet6_dev *idev;
+       int hlen = LL_RESERVED_SPACE(dev);
+       int tlen = dev->needed_tailroom;
        int len;
        int err;
-       u8 *opt, type;
-
-       type = icmp6h->icmp6_type;
-
-       icmpv6_flow_init(sk, &fl, type, saddr, daddr, dev->ifindex);
-
-       dst = icmp6_dst_alloc(dev, neigh, daddr);
-       if (!dst)
-               return;
-
-       err = xfrm_lookup(&dst, &fl, NULL, 0);
-       if (err < 0)
-               return;
+       u8 *opt;
 
        if (!dev->addr_len)
                llinfo = 0;
@@ -479,17 +453,16 @@ static void __ndisc_send(struct net_device *dev,
 
        skb = sock_alloc_send_skb(sk,
                                  (MAX_HEADER + sizeof(struct ipv6hdr) +
-                                  len + LL_ALLOCATED_SPACE(dev)),
+                                  len + hlen + tlen),
                                  1, &err);
        if (!skb) {
                ND_PRINTK0(KERN_ERR
-                          "ICMPv6 ND: %s() failed to allocate an skb.\n",
-                          __func__);
-               dst_release(dst);
-               return;
+                          "ICMPv6 ND: %s() failed to allocate an skb, err=%d.\n",
+                          __func__, err);
+               return NULL;
        }
 
-       skb_reserve(skb, LL_RESERVED_SPACE(dev));
+       skb_reserve(skb, hlen);
        ip6_nd_hdr(sk, skb, dev, saddr, daddr, IPPROTO_ICMPV6, len);
 
        skb->transport_header = skb->tail;
@@ -500,7 +473,7 @@ static void __ndisc_send(struct net_device *dev,
 
        opt = skb_transport_header(skb) + sizeof(struct icmp6hdr);
        if (target) {
-               ipv6_addr_copy((struct in6_addr *)opt, target);
+               *(struct in6_addr *)opt = *target;
                opt += sizeof(*target);
        }
 
@@ -510,23 +483,73 @@ static void __ndisc_send(struct net_device *dev,
 
        hdr->icmp6_cksum = csum_ipv6_magic(saddr, daddr, len,
                                           IPPROTO_ICMPV6,
-                                          csum_partial((__u8 *) hdr,
+                                          csum_partial(hdr,
                                                        len, 0));
 
-       skb->dst = dst;
+       return skb;
+}
+
+EXPORT_SYMBOL(ndisc_build_skb);
+
+void ndisc_send_skb(struct sk_buff *skb,
+                   struct net_device *dev,
+                   struct neighbour *neigh,
+                   const struct in6_addr *daddr,
+                   const struct in6_addr *saddr,
+                   struct icmp6hdr *icmp6h)
+{
+       struct flowi6 fl6;
+       struct dst_entry *dst;
+       struct net *net = dev_net(dev);
+       struct sock *sk = net->ipv6.ndisc_sk;
+       struct inet6_dev *idev;
+       int err;
+       u8 type;
+
+       type = icmp6h->icmp6_type;
+
+       icmpv6_flow_init(sk, &fl6, type, saddr, daddr, dev->ifindex);
+       dst = icmp6_dst_alloc(dev, neigh, &fl6);
+       if (IS_ERR(dst)) {
+               kfree_skb(skb);
+               return;
+       }
+
+       skb_dst_set(skb, dst);
 
-       idev = in6_dev_get(dst->dev);
-       IP6_INC_STATS(net, idev, IPSTATS_MIB_OUTREQUESTS);
+       rcu_read_lock();
+       idev = __in6_dev_get(dst->dev);
+       IP6_UPD_PO_STATS(net, idev, IPSTATS_MIB_OUT, skb->len);
 
-       err = NF_HOOK(PF_INET6, NF_INET_LOCAL_OUT, skb, NULL, dst->dev,
+       err = NF_HOOK(NFPROTO_IPV6, NF_INET_LOCAL_OUT, skb, NULL, dst->dev,
                      dst_output);
        if (!err) {
                ICMP6MSGOUT_INC_STATS(net, idev, type);
                ICMP6_INC_STATS(net, idev, ICMP6_MIB_OUTMSGS);
        }
 
-       if (likely(idev != NULL))
-               in6_dev_put(idev);
+       rcu_read_unlock();
+}
+
+EXPORT_SYMBOL(ndisc_send_skb);
+
+/*
+ *     Send a Neighbour Discover packet
+ */
+static void __ndisc_send(struct net_device *dev,
+                        struct neighbour *neigh,
+                        const struct in6_addr *daddr,
+                        const struct in6_addr *saddr,
+                        struct icmp6hdr *icmp6h, const struct in6_addr *target,
+                        int llinfo)
+{
+       struct sk_buff *skb;
+
+       skb = ndisc_build_skb(dev, daddr, saddr, icmp6h, target, llinfo);
+       if (!skb)
+               return;
+
+       ndisc_send_skb(skb, dev, neigh, daddr, saddr, icmp6h);
 }
 
 static void ndisc_send_na(struct net_device *dev, struct neighbour *neigh,
@@ -547,6 +570,7 @@ static void ndisc_send_na(struct net_device *dev, struct neighbour *neigh,
                src_addr = solicited_addr;
                if (ifp->flags & IFA_F_OPTIMISTIC)
                        override = 0;
+               inc_opt |= ifp->idev->cnf.force_tllao;
                in6_ifa_put(ifp);
        } else {
                if (ipv6_dev_get_saddr(dev_net(dev), dev, daddr,
@@ -565,6 +589,29 @@ static void ndisc_send_na(struct net_device *dev, struct neighbour *neigh,
                     inc_opt ? ND_OPT_TARGET_LL_ADDR : 0);
 }
 
+static void ndisc_send_unsol_na(struct net_device *dev)
+{
+       struct inet6_dev *idev;
+       struct inet6_ifaddr *ifa;
+       struct in6_addr mcaddr;
+
+       idev = in6_dev_get(dev);
+       if (!idev)
+               return;
+
+       read_lock_bh(&idev->lock);
+       list_for_each_entry(ifa, &idev->addr_list, if_list) {
+               addrconf_addr_solict_mult(&ifa->addr, &mcaddr);
+               ndisc_send_na(dev, NULL, &mcaddr, &ifa->addr,
+                             /*router=*/ !!idev->cnf.forwarding,
+                             /*solicited=*/ false, /*override=*/ true,
+                             /*inc_opt=*/ true);
+       }
+       read_unlock_bh(&idev->lock);
+
+       in6_dev_put(idev);
+}
+
 void ndisc_send_ns(struct net_device *dev, struct neighbour *neigh,
                   const struct in6_addr *solicit,
                   const struct in6_addr *daddr, const struct in6_addr *saddr)
@@ -679,8 +726,8 @@ static int pndisc_is_router(const void *pkey,
 static void ndisc_recv_ns(struct sk_buff *skb)
 {
        struct nd_msg *msg = (struct nd_msg *)skb_transport_header(skb);
-       struct in6_addr *saddr = &ipv6_hdr(skb)->saddr;
-       struct in6_addr *daddr = &ipv6_hdr(skb)->daddr;
+       const struct in6_addr *saddr = &ipv6_hdr(skb)->saddr;
+       const struct in6_addr *daddr = &ipv6_hdr(skb)->daddr;
        u8 *lladdr = NULL;
        u32 ndoptlen = skb->tail - (skb->transport_header +
                                    offsetof(struct nd_msg, opt));
@@ -850,15 +897,13 @@ out:
                in6_ifa_put(ifp);
        else
                in6_dev_put(idev);
-
-       return;
 }
 
 static void ndisc_recv_na(struct sk_buff *skb)
 {
        struct nd_msg *msg = (struct nd_msg *)skb_transport_header(skb);
-       struct in6_addr *saddr = &ipv6_hdr(skb)->saddr;
-       struct in6_addr *daddr = &ipv6_hdr(skb)->daddr;
+       const struct in6_addr *saddr = &ipv6_hdr(skb)->saddr;
+       const struct in6_addr *daddr = &ipv6_hdr(skb)->daddr;
        u8 *lladdr = NULL;
        u32 ndoptlen = skb->tail - (skb->transport_header +
                                    offsetof(struct nd_msg, opt));
@@ -901,18 +946,24 @@ static void ndisc_recv_na(struct sk_buff *skb)
        }
        ifp = ipv6_get_ifaddr(dev_net(dev), &msg->target, dev, 1);
        if (ifp) {
-               if (ifp->flags & IFA_F_TENTATIVE) {
-                       addrconf_dad_failure(ifp);
-                       return;
+               if (skb->pkt_type != PACKET_LOOPBACK
+                   && (ifp->flags & IFA_F_TENTATIVE)) {
+                               addrconf_dad_failure(ifp);
+                               return;
                }
                /* What should we make now? The advertisement
                   is invalid, but ndisc specs say nothing
                   about it. It could be misconfiguration, or
                   an smart proxy agent tries to help us :-)
+
+                  We should not print the error if NA has been
+                  received from loopback - it is just our own
+                  unsolicited advertisement.
                 */
-               ND_PRINTK1(KERN_WARNING
-                          "ICMPv6 NA: someone advertises our address on %s!\n",
-                          ifp->idev->dev->name);
+               if (skb->pkt_type != PACKET_LOOPBACK)
+                       ND_PRINTK1(KERN_WARNING
+                          "ICMPv6 NA: someone advertises our address %pI6 on %s!\n",
+                          &ifp->addr, ifp->idev->dev->name);
                in6_ifa_put(ifp);
                return;
        }
@@ -965,14 +1016,14 @@ static void ndisc_recv_rs(struct sk_buff *skb)
        unsigned long ndoptlen = skb->len - sizeof(*rs_msg);
        struct neighbour *neigh;
        struct inet6_dev *idev;
-       struct in6_addr *saddr = &ipv6_hdr(skb)->saddr;
+       const struct in6_addr *saddr = &ipv6_hdr(skb)->saddr;
        struct ndisc_options ndopts;
        u8 *lladdr = NULL;
 
        if (skb->len < sizeof(*rs_msg))
                return;
 
-       idev = in6_dev_get(skb->dev);
+       idev = __in6_dev_get(skb->dev);
        if (!idev) {
                if (net_ratelimit())
                        ND_PRINTK1("ICMP6 RS: can't find in6 device\n");
@@ -1013,7 +1064,7 @@ static void ndisc_recv_rs(struct sk_buff *skb)
                neigh_release(neigh);
        }
 out:
-       in6_dev_put(idev);
+       return;
 }
 
 static void ndisc_ra_useropt(struct sk_buff *ra, struct nd_opt_hdr *opt)
@@ -1052,11 +1103,7 @@ static void ndisc_ra_useropt(struct sk_buff *ra, struct nd_opt_hdr *opt)
                &ipv6_hdr(ra)->saddr);
        nlmsg_end(skb, nlh);
 
-       err = rtnl_notify(skb, net, 0, RTNLGRP_ND_USEROPT, NULL,
-                         GFP_ATOMIC);
-       if (err < 0)
-               goto errout;
-
+       rtnl_notify(skb, net, 0, RTNLGRP_ND_USEROPT, NULL, GFP_ATOMIC);
        return;
 
 nla_put_failure:
@@ -1066,6 +1113,18 @@ errout:
        rtnl_set_sk_err(net, RTNLGRP_ND_USEROPT, err);
 }
 
+static inline int accept_ra(struct inet6_dev *in6_dev)
+{
+       /*
+        * If forwarding is enabled, RA are not accepted unless the special
+        * hybrid mode (accept_ra=2) is enabled.
+        */
+       if (in6_dev->cnf.forwarding && in6_dev->cnf.accept_ra < 2)
+               return 0;
+
+       return in6_dev->cnf.accept_ra;
+}
+
 static void ndisc_router_discovery(struct sk_buff *skb)
 {
        struct ra_msg *ra_msg = (struct ra_msg *)skb_transport_header(skb);
@@ -1104,25 +1163,23 @@ static void ndisc_router_discovery(struct sk_buff *skb)
         *      set the RA_RECV flag in the interface
         */
 
-       in6_dev = in6_dev_get(skb->dev);
+       in6_dev = __in6_dev_get(skb->dev);
        if (in6_dev == NULL) {
                ND_PRINTK0(KERN_ERR
                           "ICMPv6 RA: can't find inet6 device for %s.\n",
                           skb->dev->name);
                return;
        }
-       if (in6_dev->cnf.forwarding || !in6_dev->cnf.accept_ra) {
-               in6_dev_put(in6_dev);
-               return;
-       }
 
        if (!ndisc_parse_options(opt, optlen, &ndopts)) {
-               in6_dev_put(in6_dev);
                ND_PRINTK2(KERN_WARNING
                           "ICMP6 RA: invalid ND options\n");
                return;
        }
 
+       if (!accept_ra(in6_dev))
+               goto skip_linkparms;
+
 #ifdef CONFIG_IPV6_NDISC_NODETYPE
        /* skip link-specific parameters from interior routers */
        if (skb->ndisc_nodetype == NDISC_NODETYPE_NODEFAULT)
@@ -1151,6 +1208,9 @@ static void ndisc_router_discovery(struct sk_buff *skb)
        if (!in6_dev->cnf.accept_ra_defrtr)
                goto skip_defrtr;
 
+       if (ipv6_chk_addr(dev_net(in6_dev->dev), &ipv6_hdr(skb)->saddr, NULL, 0))
+               goto skip_defrtr;
+
        lifetime = ntohs(ra_msg->icmph.icmp6_rt_lifetime);
 
 #ifdef CONFIG_IPV6_ROUTER_PREF
@@ -1163,11 +1223,17 @@ static void ndisc_router_discovery(struct sk_buff *skb)
 
        rt = rt6_get_dflt_router(&ipv6_hdr(skb)->saddr, skb->dev);
 
-       if (rt)
-               neigh = rt->rt6i_nexthop;
-
+       if (rt) {
+               neigh = dst_neigh_lookup(&rt->dst, &ipv6_hdr(skb)->saddr);
+               if (!neigh) {
+                       ND_PRINTK0(KERN_ERR
+                                  "ICMPv6 RA: %s() got default router without neighbour.\n",
+                                  __func__);
+                       dst_release(&rt->dst);
+                       return;
+               }
+       }
        if (rt && lifetime == 0) {
-               neigh_clone(neigh);
                ip6_del_rt(rt);
                rt = NULL;
        }
@@ -1181,17 +1247,15 @@ static void ndisc_router_discovery(struct sk_buff *skb)
                        ND_PRINTK0(KERN_ERR
                                   "ICMPv6 RA: %s() failed to add default route.\n",
                                   __func__);
-                       in6_dev_put(in6_dev);
                        return;
                }
 
-               neigh = rt->rt6i_nexthop;
+               neigh = dst_neigh_lookup(&rt->dst, &ipv6_hdr(skb)->saddr);
                if (neigh == NULL) {
                        ND_PRINTK0(KERN_ERR
                                   "ICMPv6 RA: %s() got default router without neighbour.\n",
                                   __func__);
-                       dst_release(&rt->u.dst);
-                       in6_dev_put(in6_dev);
+                       dst_release(&rt->dst);
                        return;
                }
                neigh->flags |= NTF_ROUTER;
@@ -1200,12 +1264,12 @@ static void ndisc_router_discovery(struct sk_buff *skb)
        }
 
        if (rt)
-               rt->rt6i_expires = jiffies + (HZ * lifetime);
-
+               rt6_set_expires(rt, jiffies + (HZ * lifetime));
        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;
+                       dst_metric_set(&rt->dst, RTAX_HOPLIMIT,
+                                      ra_msg->icmph.icmp6_hop_limit);
        }
 
 skip_defrtr:
@@ -1243,9 +1307,7 @@ skip_defrtr:
                }
        }
 
-#ifdef CONFIG_IPV6_NDISC_NODETYPE
 skip_linkparms:
-#endif
 
        /*
         *      Process options.
@@ -1272,7 +1334,13 @@ skip_linkparms:
                             NEIGH_UPDATE_F_ISROUTER);
        }
 
+       if (!accept_ra(in6_dev))
+               goto out;
+
 #ifdef CONFIG_IPV6_ROUTE_INFO
+       if (ipv6_chk_addr(dev_net(in6_dev->dev), &ipv6_hdr(skb)->saddr, NULL, 0))
+               goto skip_routeinfo;
+
        if (in6_dev->cnf.accept_ra_rtr_pref && ndopts.nd_opts_ri) {
                struct nd_opt_hdr *p;
                for (p = ndopts.nd_opts_ri;
@@ -1290,6 +1358,8 @@ skip_linkparms:
                                      &ipv6_hdr(skb)->saddr);
                }
        }
+
+skip_routeinfo:
 #endif
 
 #ifdef CONFIG_IPV6_NDISC_NODETYPE
@@ -1303,7 +1373,9 @@ skip_linkparms:
                for (p = ndopts.nd_opts_pi;
                     p;
                     p = ndisc_next_option(p, ndopts.nd_opts_pi_end)) {
-                       addrconf_prefix_rcv(skb->dev, (u8*)p, (p->nd_opt_len) << 3);
+                       addrconf_prefix_rcv(skb->dev, (u8 *)p,
+                                           (p->nd_opt_len) << 3,
+                                           ndopts.nd_opts_src_lladdr != NULL);
                }
        }
 
@@ -1322,7 +1394,7 @@ skip_linkparms:
                        in6_dev->cnf.mtu6 = mtu;
 
                        if (rt)
-                               rt->u.dst.metrics[RTAX_MTU-1] = mtu;
+                               dst_metric_set(&rt->dst, RTAX_MTU, mtu);
 
                        rt6_mtu_change(skb->dev, mtu);
                }
@@ -1343,18 +1415,17 @@ skip_linkparms:
        }
 out:
        if (rt)
-               dst_release(&rt->u.dst);
-       else if (neigh)
+               dst_release(&rt->dst);
+       if (neigh)
                neigh_release(neigh);
-       in6_dev_put(in6_dev);
 }
 
 static void ndisc_redirect_rcv(struct sk_buff *skb)
 {
        struct inet6_dev *in6_dev;
        struct icmp6hdr *icmph;
-       struct in6_addr *dest;
-       struct in6_addr *target;        /* new first hop to destination */
+       const struct in6_addr *dest;
+       const struct in6_addr *target;  /* new first hop to destination */
        struct neighbour *neigh;
        int on_link = 0;
        struct ndisc_options ndopts;
@@ -1387,7 +1458,7 @@ static void ndisc_redirect_rcv(struct sk_buff *skb)
        }
 
        icmph = icmp6_hdr(skb);
-       target = (struct in6_addr *) (icmph + 1);
+       target = (const struct in6_addr *) (icmph + 1);
        dest = target + 1;
 
        if (ipv6_addr_is_multicast(dest)) {
@@ -1405,13 +1476,11 @@ static void ndisc_redirect_rcv(struct sk_buff *skb)
                return;
        }
 
-       in6_dev = in6_dev_get(skb->dev);
+       in6_dev = __in6_dev_get(skb->dev);
        if (!in6_dev)
                return;
-       if (in6_dev->cnf.forwarding || !in6_dev->cnf.accept_redirects) {
-               in6_dev_put(in6_dev);
+       if (in6_dev->cnf.forwarding || !in6_dev->cnf.accept_redirects)
                return;
-       }
 
        /* RFC2461 8.1:
         *      The IP source address of the Redirect MUST be the same as the current
@@ -1421,7 +1490,6 @@ static void ndisc_redirect_rcv(struct sk_buff *skb)
        if (!ndisc_parse_options((u8*)(dest + 1), optlen, &ndopts)) {
                ND_PRINTK2(KERN_WARNING
                           "ICMPv6 Redirect: invalid ND options\n");
-               in6_dev_put(in6_dev);
                return;
        }
        if (ndopts.nd_opts_tgt_lladdr) {
@@ -1430,7 +1498,6 @@ static void ndisc_redirect_rcv(struct sk_buff *skb)
                if (!lladdr) {
                        ND_PRINTK2(KERN_WARNING
                                   "ICMPv6 Redirect: invalid link-layer address length\n");
-                       in6_dev_put(in6_dev);
                        return;
                }
        }
@@ -1442,11 +1509,9 @@ static void ndisc_redirect_rcv(struct sk_buff *skb)
                             on_link);
                neigh_release(neigh);
        }
-       in6_dev_put(in6_dev);
 }
 
-void ndisc_send_redirect(struct sk_buff *skb, struct neighbour *neigh,
-                        const struct in6_addr *target)
+void ndisc_send_redirect(struct sk_buff *skb, const struct in6_addr *target)
 {
        struct net_device *dev = skb->dev;
        struct net *net = dev_net(dev);
@@ -1459,8 +1524,9 @@ void ndisc_send_redirect(struct sk_buff *skb, struct neighbour *neigh,
        struct rt6_info *rt;
        struct dst_entry *dst;
        struct inet6_dev *idev;
-       struct flowi fl;
+       struct flowi6 fl6;
        u8 *opt;
+       int hlen, tlen;
        int rd_len;
        int err;
        u8 ha_buf[MAX_ADDR_LEN], *ha = NULL;
@@ -1479,15 +1545,16 @@ void ndisc_send_redirect(struct sk_buff *skb, struct neighbour *neigh,
                return;
        }
 
-       icmpv6_flow_init(sk, &fl, NDISC_REDIRECT,
+       icmpv6_flow_init(sk, &fl6, NDISC_REDIRECT,
                         &saddr_buf, &ipv6_hdr(skb)->saddr, dev->ifindex);
 
-       dst = ip6_route_output(net, NULL, &fl);
-       if (dst == NULL)
+       dst = ip6_route_output(net, NULL, &fl6);
+       if (dst->error) {
+               dst_release(dst);
                return;
-
-       err = xfrm_lookup(&dst, &fl, NULL, 0);
-       if (err)
+       }
+       dst = xfrm_lookup(net, dst, flowi6_to_flowi(&fl6), NULL, 0);
+       if (IS_ERR(dst))
                return;
 
        rt = (struct rt6_info *) dst;
@@ -1495,15 +1562,21 @@ void ndisc_send_redirect(struct sk_buff *skb, struct neighbour *neigh,
        if (rt->rt6i_flags & RTF_GATEWAY) {
                ND_PRINTK2(KERN_WARNING
                           "ICMPv6 Redirect: destination is not a neighbour.\n");
-               dst_release(dst);
-               return;
-       }
-       if (!xrlim_allow(dst, 1*HZ)) {
-               dst_release(dst);
-               return;
+               goto release;
        }
+       if (!rt->rt6i_peer)
+               rt6_bind_peer(rt, 1);
+       if (!inet_peer_xrlim_allow(rt->rt6i_peer, 1*HZ))
+               goto release;
 
        if (dev->addr_len) {
+               struct neighbour *neigh = dst_neigh_lookup(skb_dst(skb), target);
+               if (!neigh) {
+                       ND_PRINTK2(KERN_WARNING
+                                  "ICMPv6 Redirect: no neigh for target address\n");
+                       goto release;
+               }
+
                read_lock_bh(&neigh->lock);
                if (neigh->nud_state & NUD_VALID) {
                        memcpy(ha_buf, neigh->ha, dev->addr_len);
@@ -1512,6 +1585,8 @@ void ndisc_send_redirect(struct sk_buff *skb, struct neighbour *neigh,
                        len += ndisc_opt_addr_space(dev);
                } else
                        read_unlock_bh(&neigh->lock);
+
+               neigh_release(neigh);
        }
 
        rd_len = min_t(unsigned int,
@@ -1519,19 +1594,20 @@ void ndisc_send_redirect(struct sk_buff *skb, struct neighbour *neigh,
        rd_len &= ~0x7;
        len += rd_len;
 
+       hlen = LL_RESERVED_SPACE(dev);
+       tlen = dev->needed_tailroom;
        buff = sock_alloc_send_skb(sk,
                                   (MAX_HEADER + sizeof(struct ipv6hdr) +
-                                   len + LL_ALLOCATED_SPACE(dev)),
+                                   len + hlen + tlen),
                                   1, &err);
        if (buff == NULL) {
                ND_PRINTK0(KERN_ERR
-                          "ICMPv6 Redirect: %s() failed to allocate an skb.\n",
-                          __func__);
-               dst_release(dst);
-               return;
+                          "ICMPv6 Redirect: %s() failed to allocate an skb, err=%d.\n",
+                          __func__, err);
+               goto release;
        }
 
-       skb_reserve(buff, LL_RESERVED_SPACE(dev));
+       skb_reserve(buff, hlen);
        ip6_nd_hdr(sk, buff, dev, &saddr_buf, &ipv6_hdr(skb)->saddr,
                   IPPROTO_ICMPV6, len);
 
@@ -1547,9 +1623,9 @@ void ndisc_send_redirect(struct sk_buff *skb, struct neighbour *neigh,
         */
 
        addrp = (struct in6_addr *)(icmph + 1);
-       ipv6_addr_copy(addrp, target);
+       *addrp = *target;
        addrp++;
-       ipv6_addr_copy(addrp, &ipv6_hdr(skb)->daddr);
+       *addrp = ipv6_hdr(skb)->daddr;
 
        opt = (u8*) (addrp + 1);
 
@@ -1574,20 +1650,24 @@ void ndisc_send_redirect(struct sk_buff *skb, struct neighbour *neigh,
 
        icmph->icmp6_cksum = csum_ipv6_magic(&saddr_buf, &ipv6_hdr(skb)->saddr,
                                             len, IPPROTO_ICMPV6,
-                                            csum_partial((u8 *) icmph, len, 0));
+                                            csum_partial(icmph, len, 0));
 
-       buff->dst = dst;
-       idev = in6_dev_get(dst->dev);
-       IP6_INC_STATS(net, idev, IPSTATS_MIB_OUTREQUESTS);
-       err = NF_HOOK(PF_INET6, NF_INET_LOCAL_OUT, buff, NULL, dst->dev,
+       skb_dst_set(buff, dst);
+       rcu_read_lock();
+       idev = __in6_dev_get(dst->dev);
+       IP6_UPD_PO_STATS(net, idev, IPSTATS_MIB_OUT, skb->len);
+       err = NF_HOOK(NFPROTO_IPV6, NF_INET_LOCAL_OUT, buff, NULL, dst->dev,
                      dst_output);
        if (!err) {
                ICMP6MSGOUT_INC_STATS(net, idev, NDISC_REDIRECT);
                ICMP6_INC_STATS(net, idev, ICMP6_MIB_OUTMSGS);
        }
 
-       if (likely(idev != NULL))
-               in6_dev_put(idev);
+       rcu_read_unlock();
+       return;
+
+release:
+       dst_release(dst);
 }
 
 static void pndisc_redo(struct sk_buff *skb)
@@ -1662,6 +1742,9 @@ static int ndisc_netdev_event(struct notifier_block *this, unsigned long event,
                neigh_ifdown(&nd_tbl, dev);
                fib6_run_gc(~0UL, net);
                break;
+       case NETDEV_NOTIFY_PEERS:
+               ndisc_send_unsol_na(dev);
+               break;
        default:
                break;
        }
@@ -1693,7 +1776,7 @@ static void ndisc_warn_deprecated_sysctl(struct ctl_table *ctl,
        }
 }
 
-int ndisc_ifinfo_sysctl_change(struct ctl_table *ctl, int write, struct file * filp, void __user *buffer, size_t *lenp, loff_t *ppos)
+int ndisc_ifinfo_sysctl_change(struct ctl_table *ctl, int write, void __user *buffer, size_t *lenp, loff_t *ppos)
 {
        struct net_device *dev = ctl->extra1;
        struct inet6_dev *idev;
@@ -1704,16 +1787,16 @@ int ndisc_ifinfo_sysctl_change(struct ctl_table *ctl, int write, struct file * f
                ndisc_warn_deprecated_sysctl(ctl, "syscall", dev ? dev->name : "default");
 
        if (strcmp(ctl->procname, "retrans_time") == 0)
-               ret = proc_dointvec(ctl, write, filp, buffer, lenp, ppos);
+               ret = proc_dointvec(ctl, write, buffer, lenp, ppos);
 
        else if (strcmp(ctl->procname, "base_reachable_time") == 0)
                ret = proc_dointvec_jiffies(ctl, write,
-                                           filp, buffer, lenp, ppos);
+                                           buffer, lenp, ppos);
 
        else if ((strcmp(ctl->procname, "retrans_time_ms") == 0) ||
                 (strcmp(ctl->procname, "base_reachable_time_ms") == 0))
                ret = proc_dointvec_ms_jiffies(ctl, write,
-                                              filp, buffer, lenp, ppos);
+                                              buffer, lenp, ppos);
        else
                ret = -1;
 
@@ -1727,46 +1810,10 @@ int ndisc_ifinfo_sysctl_change(struct ctl_table *ctl, int write, struct file * f
        return ret;
 }
 
-int ndisc_ifinfo_sysctl_strategy(ctl_table *ctl,
-                                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, oldval, oldlenp, newval, newlen);
-               break;
-       case NET_NEIGH_RETRANS_TIME_MS:
-       case NET_NEIGH_REACHABLE_TIME_MS:
-                ret = sysctl_ms_jiffies(ctl, 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 ret;
-}
 
 #endif
 
-static int ndisc_net_init(struct net *net)
+static int __net_init ndisc_net_init(struct net *net)
 {
        struct ipv6_pinfo *np;
        struct sock *sk;
@@ -1791,7 +1838,7 @@ static int ndisc_net_init(struct net *net)
        return 0;
 }
 
-static void ndisc_net_exit(struct net *net)
+static void __net_exit ndisc_net_exit(struct net *net)
 {
        inet_ctl_sock_destroy(net->ipv6.ndisc_sk);
 }
@@ -1814,10 +1861,8 @@ int __init ndisc_init(void)
        neigh_table_init(&nd_tbl);
 
 #ifdef CONFIG_SYSCTL
-       err = neigh_sysctl_register(NULL, &nd_tbl.parms, NET_IPV6,
-                                   NET_IPV6_NEIGH, "ipv6",
-                                   &ndisc_ifinfo_sysctl_change,
-                                   &ndisc_ifinfo_sysctl_strategy);
+       err = neigh_sysctl_register(NULL, &nd_tbl.parms, "ipv6",
+                                   &ndisc_ifinfo_sysctl_change);
        if (err)
                goto out_unregister_pernet;
 #endif