fedora core 6 1.2949 + vserver 2.2.0
[linux-2.6.git] / net / ipv6 / route.c
index 83dc099..01bcf4a 100644 (file)
  *             routers in REACHABLE, STALE, DELAY or PROBE states).
  *             - always select the same router if it is (probably)
  *             reachable.  otherwise, round-robin the list.
+ *     Ville Nuorvala
+ *             Fixed routing subtrees.
  */
 
-#include <linux/config.h>
+#include <linux/capability.h>
 #include <linux/errno.h>
 #include <linux/types.h>
 #include <linux/times.h>
@@ -35,7 +37,6 @@
 #include <linux/netdevice.h>
 #include <linux/in6.h>
 #include <linux/init.h>
-#include <linux/netlink.h>
 #include <linux/if_arp.h>
 
 #ifdef         CONFIG_PROC_FS
@@ -53,6 +54,8 @@
 #include <linux/rtnetlink.h>
 #include <net/dst.h>
 #include <net/xfrm.h>
+#include <net/netevent.h>
+#include <net/netlink.h>
 
 #include <asm/uaccess.h>
 
@@ -71,6 +74,7 @@
 #define RT6_TRACE(x...) do { ; } while (0)
 #endif
 
+#define CLONE_OFFLINK_ROUTE 0
 
 static int ip6_rt_max_size = 4096;
 static int ip6_rt_gc_min_interval = HZ / 2;
@@ -84,14 +88,23 @@ static struct rt6_info * ip6_rt_copy(struct rt6_info *ort);
 static struct dst_entry        *ip6_dst_check(struct dst_entry *dst, u32 cookie);
 static struct dst_entry *ip6_negative_advice(struct dst_entry *);
 static void            ip6_dst_destroy(struct dst_entry *);
-static void            ip6_dst_ifdown(struct dst_entry *, int how);
+static void            ip6_dst_ifdown(struct dst_entry *,
+                                      struct net_device *dev, int how);
 static int              ip6_dst_gc(void);
 
 static int             ip6_pkt_discard(struct sk_buff *skb);
-static int             ip6_pkt_discard_out(struct sk_buff **pskb);
+static int             ip6_pkt_discard_out(struct sk_buff *skb);
 static void            ip6_link_failure(struct sk_buff *skb);
 static void            ip6_rt_update_pmtu(struct dst_entry *dst, u32 mtu);
 
+#ifdef CONFIG_IPV6_ROUTE_INFO
+static struct rt6_info *rt6_add_route_info(struct in6_addr *prefix, int prefixlen,
+                                          struct in6_addr *gwaddr, int ifindex,
+                                          unsigned pref);
+static struct rt6_info *rt6_get_route_info(struct in6_addr *prefix, int prefixlen,
+                                          struct in6_addr *gwaddr, int ifindex);
+#endif
+
 static struct dst_ops ip6_dst_ops = {
        .family                 =       AF_INET6,
        .protocol               =       __constant_htons(ETH_P_IPV6),
@@ -126,15 +139,53 @@ struct rt6_info ip6_null_entry = {
        .rt6i_ref       = ATOMIC_INIT(1),
 };
 
-struct fib6_node ip6_routing_table = {
-       .leaf           = &ip6_null_entry,
-       .fn_flags       = RTN_ROOT | RTN_TL_ROOT | RTN_RTINFO,
-};
+#ifdef CONFIG_IPV6_MULTIPLE_TABLES
 
-/* Protects all the ip6 fib */
+static int ip6_pkt_prohibit(struct sk_buff *skb);
+static int ip6_pkt_prohibit_out(struct sk_buff *skb);
+static int ip6_pkt_blk_hole(struct sk_buff *skb);
 
-rwlock_t rt6_lock = RW_LOCK_UNLOCKED;
+struct rt6_info ip6_prohibit_entry = {
+       .u = {
+               .dst = {
+                       .__refcnt       = ATOMIC_INIT(1),
+                       .__use          = 1,
+                       .dev            = &loopback_dev,
+                       .obsolete       = -1,
+                       .error          = -EACCES,
+                       .metrics        = { [RTAX_HOPLIMIT - 1] = 255, },
+                       .input          = ip6_pkt_prohibit,
+                       .output         = ip6_pkt_prohibit_out,
+                       .ops            = &ip6_dst_ops,
+                       .path           = (struct dst_entry*)&ip6_prohibit_entry,
+               }
+       },
+       .rt6i_flags     = (RTF_REJECT | RTF_NONEXTHOP),
+       .rt6i_metric    = ~(u32) 0,
+       .rt6i_ref       = ATOMIC_INIT(1),
+};
+
+struct rt6_info ip6_blk_hole_entry = {
+       .u = {
+               .dst = {
+                       .__refcnt       = ATOMIC_INIT(1),
+                       .__use          = 1,
+                       .dev            = &loopback_dev,
+                       .obsolete       = -1,
+                       .error          = -EINVAL,
+                       .metrics        = { [RTAX_HOPLIMIT - 1] = 255, },
+                       .input          = ip6_pkt_blk_hole,
+                       .output         = ip6_pkt_blk_hole,
+                       .ops            = &ip6_dst_ops,
+                       .path           = (struct dst_entry*)&ip6_blk_hole_entry,
+               }
+       },
+       .rt6i_flags     = (RTF_REJECT | RTF_NONEXTHOP),
+       .rt6i_metric    = ~(u32) 0,
+       .rt6i_ref       = ATOMIC_INIT(1),
+};
 
+#endif
 
 /* allocate dst with ip6_dst_ops */
 static __inline__ struct rt6_info *ip6_dst_alloc(void)
@@ -153,12 +204,13 @@ static void ip6_dst_destroy(struct dst_entry *dst)
        }       
 }
 
-static void ip6_dst_ifdown(struct dst_entry *dst, int how)
+static void ip6_dst_ifdown(struct dst_entry *dst, struct net_device *dev,
+                          int how)
 {
        struct rt6_info *rt = (struct rt6_info *)dst;
        struct inet6_dev *idev = rt->rt6i_idev;
 
-       if (idev != NULL && idev->dev != &loopback_dev) {
+       if (dev != &loopback_dev && idev != NULL && idev->dev == dev) {
                struct inet6_dev *loopback_idev = in6_dev_get(&loopback_dev);
                if (loopback_idev != NULL) {
                        rt->rt6i_idev = loopback_idev;
@@ -167,8 +219,20 @@ static void ip6_dst_ifdown(struct dst_entry *dst, int how)
        }
 }
 
+static __inline__ int rt6_check_expired(const struct rt6_info *rt)
+{
+       return (rt->rt6i_flags & RTF_EXPIRES &&
+               time_after(jiffies, rt->rt6i_expires));
+}
+
+static inline int rt6_need_strict(struct in6_addr *daddr)
+{
+       return (ipv6_addr_type(daddr) &
+               (IPV6_ADDR_MULTICAST | IPV6_ADDR_LINKLOCAL));
+}
+
 /*
- *     Route lookup. Any rt6_lock is implied.
+ *     Route lookup. Any table->tb6_lock is implied.
  */
 
 static __inline__ struct rt6_info *rt6_device_match(struct rt6_info *rt,
@@ -205,194 +269,339 @@ static __inline__ struct rt6_info *rt6_device_match(struct rt6_info *rt,
        return rt;
 }
 
+#ifdef CONFIG_IPV6_ROUTER_PREF
+static void rt6_probe(struct rt6_info *rt)
+{
+       struct neighbour *neigh = rt ? rt->rt6i_nexthop : NULL;
+       /*
+        * Okay, this does not seem to be appropriate
+        * for now, however, we need to check if it
+        * is really so; aka Router Reachability Probing.
+        *
+        * Router Reachability Probe MUST be rate-limited
+        * to no more than one per minute.
+        */
+       if (!neigh || (neigh->nud_state & NUD_VALID))
+               return;
+       read_lock_bh(&neigh->lock);
+       if (!(neigh->nud_state & NUD_VALID) &&
+           time_after(jiffies, neigh->updated + rt->rt6i_idev->cnf.rtr_probe_interval)) {
+               struct in6_addr mcaddr;
+               struct in6_addr *target;
+
+               neigh->updated = jiffies;
+               read_unlock_bh(&neigh->lock);
+
+               target = (struct in6_addr *)&neigh->primary_key;
+               addrconf_addr_solict_mult(target, &mcaddr);
+               ndisc_send_ns(rt->rt6i_dev, NULL, target, &mcaddr, NULL);
+       } else
+               read_unlock_bh(&neigh->lock);
+}
+#else
+static inline void rt6_probe(struct rt6_info *rt)
+{
+       return;
+}
+#endif
+
 /*
- *     pointer to the last default router chosen. BH is disabled locally.
+ * Default Router Selection (RFC 2461 6.3.6)
  */
-struct rt6_info *rt6_dflt_pointer;
-spinlock_t rt6_dflt_lock = SPIN_LOCK_UNLOCKED;
+static int inline rt6_check_dev(struct rt6_info *rt, int oif)
+{
+       struct net_device *dev = rt->rt6i_dev;
+       if (!oif || dev->ifindex == oif)
+               return 2;
+       if ((dev->flags & IFF_LOOPBACK) &&
+           rt->rt6i_idev && rt->rt6i_idev->dev->ifindex == oif)
+               return 1;
+       return 0;
+}
 
-void rt6_reset_dflt_pointer(struct rt6_info *rt)
+static int inline rt6_check_neigh(struct rt6_info *rt)
 {
-       spin_lock_bh(&rt6_dflt_lock);
-       if (rt == NULL || rt == rt6_dflt_pointer) {
-               RT6_TRACE("reset default router: %p->NULL\n", rt6_dflt_pointer);
-               rt6_dflt_pointer = NULL;
+       struct neighbour *neigh = rt->rt6i_nexthop;
+       int m = 0;
+       if (rt->rt6i_flags & RTF_NONEXTHOP ||
+           !(rt->rt6i_flags & RTF_GATEWAY))
+               m = 1;
+       else if (neigh) {
+               read_lock_bh(&neigh->lock);
+               if (neigh->nud_state & NUD_VALID)
+                       m = 2;
+               else if (!(neigh->nud_state & NUD_FAILED))
+                       m = 1;
+               read_unlock_bh(&neigh->lock);
        }
-       spin_unlock_bh(&rt6_dflt_lock);
+       return m;
 }
 
-/* Default Router Selection (RFC 2461 6.3.6) */
-static struct rt6_info *rt6_best_dflt(struct rt6_info *rt, int oif)
+static int rt6_score_route(struct rt6_info *rt, int oif,
+                          int strict)
 {
-       struct rt6_info *match = NULL;
-       struct rt6_info *sprt;
-       int mpri = 0;
-
-       for (sprt = rt; sprt; sprt = sprt->u.next) {
-               struct neighbour *neigh;
-               int m = 0;
+       int m, n;
+               
+       m = rt6_check_dev(rt, oif);
+       if (!m && (strict & RT6_LOOKUP_F_IFACE))
+               return -1;
+#ifdef CONFIG_IPV6_ROUTER_PREF
+       m |= IPV6_DECODE_PREF(IPV6_EXTRACT_PREF(rt->rt6i_flags)) << 2;
+#endif
+       n = rt6_check_neigh(rt);
+       if (!n && (strict & RT6_LOOKUP_F_REACHABLE))
+               return -1;
+       return m;
+}
 
-               if (!oif ||
-                   (sprt->rt6i_dev &&
-                    sprt->rt6i_dev->ifindex == oif))
-                       m += 8;
+static struct rt6_info *find_match(struct rt6_info *rt, int oif, int strict,
+                                  int *mpri, struct rt6_info *match)
+{
+       int m;
 
-               if ((sprt->rt6i_flags & RTF_EXPIRES) &&
-                   time_after(jiffies, sprt->rt6i_expires))
-                       continue;
+       if (rt6_check_expired(rt))
+               goto out;
 
-               if (sprt == rt6_dflt_pointer)
-                       m += 4;
-
-               if ((neigh = sprt->rt6i_nexthop) != NULL) {
-                       read_lock_bh(&neigh->lock);
-                       switch (neigh->nud_state) {
-                       case NUD_REACHABLE:
-                               m += 3;
-                               break;
-
-                       case NUD_STALE:
-                       case NUD_DELAY:
-                       case NUD_PROBE:
-                               m += 2;
-                               break;
-
-                       case NUD_NOARP:
-                       case NUD_PERMANENT:
-                               m += 1;
-                               break;
-
-                       case NUD_INCOMPLETE:
-                       default:
-                               read_unlock_bh(&neigh->lock);
-                               continue;
-                       }
-                       read_unlock_bh(&neigh->lock);
-               } else {
-                       continue;
-               }
+       m = rt6_score_route(rt, oif, strict);
+       if (m < 0)
+               goto out;
 
-               if (m > mpri || m >= 12) {
-                       match = sprt;
-                       mpri = m;
-                       if (m >= 12) {
-                               /* we choose the last default router if it
-                                * is in (probably) reachable state.
-                                * If route changed, we should do pmtu
-                                * discovery. --yoshfuji
-                                */
-                               break;
-                       }
-               }
+       if (m > *mpri) {
+               if (strict & RT6_LOOKUP_F_REACHABLE)
+                       rt6_probe(match);
+               *mpri = m;
+               match = rt;
+       } else if (strict & RT6_LOOKUP_F_REACHABLE) {
+               rt6_probe(rt);
        }
 
-       spin_lock(&rt6_dflt_lock);
-       if (!match) {
-               /*
-                *      No default routers are known to be reachable.
-                *      SHOULD round robin
-                */
-               if (rt6_dflt_pointer) {
-                       for (sprt = rt6_dflt_pointer->u.next;
-                            sprt; sprt = sprt->u.next) {
-                               if (sprt->u.dst.obsolete <= 0 &&
-                                   sprt->u.dst.error == 0) {
-                                       match = sprt;
-                                       break;
-                               }
-                       }
-                       for (sprt = rt;
-                            !match && sprt;
-                            sprt = sprt->u.next) {
-                               if (sprt->u.dst.obsolete <= 0 &&
-                                   sprt->u.dst.error == 0) {
-                                       match = sprt;
-                                       break;
-                               }
-                               if (sprt == rt6_dflt_pointer)
-                                       break;
-                       }
-               }
+out:
+       return match;
+}
+
+static struct rt6_info *find_rr_leaf(struct fib6_node *fn,
+                                    struct rt6_info *rr_head,
+                                    u32 metric, int oif, int strict)
+{
+       struct rt6_info *rt, *match;
+       int mpri = -1;
+
+       match = NULL;
+       for (rt = rr_head; rt && rt->rt6i_metric == metric;
+            rt = rt->u.next)
+               match = find_match(rt, oif, strict, &mpri, match);
+       for (rt = fn->leaf; rt && rt != rr_head && rt->rt6i_metric == metric;
+            rt = rt->u.next)
+               match = find_match(rt, oif, strict, &mpri, match);
+
+       return match;
+}
+
+static struct rt6_info *rt6_select(struct fib6_node *fn, int oif, int strict)
+{
+       struct rt6_info *match, *rt0;
+
+       RT6_TRACE("%s(fn->leaf=%p, oif=%d)\n",
+                 __FUNCTION__, fn->leaf, oif);
+
+       rt0 = fn->rr_ptr;
+       if (!rt0)
+               fn->rr_ptr = rt0 = fn->leaf;
+
+       match = find_rr_leaf(fn, rt0, rt0->rt6i_metric, oif, strict);
+
+       if (!match &&
+           (strict & RT6_LOOKUP_F_REACHABLE)) {
+               struct rt6_info *next = rt0->u.next;
+
+               /* no entries matched; do round-robin */
+               if (!next || next->rt6i_metric != rt0->rt6i_metric)
+                       next = fn->leaf;
+
+               if (next != rt0)
+                       fn->rr_ptr = next;
        }
 
-       if (match) {
-               if (rt6_dflt_pointer != match)
-                       RT6_TRACE("changed default router: %p->%p\n",
-                                 rt6_dflt_pointer, match);
-               rt6_dflt_pointer = match;
+       RT6_TRACE("%s() => %p\n",
+                 __FUNCTION__, match);
+
+       return (match ? match : &ip6_null_entry);
+}
+
+#ifdef CONFIG_IPV6_ROUTE_INFO
+int rt6_route_rcv(struct net_device *dev, u8 *opt, int len,
+                 struct in6_addr *gwaddr)
+{
+       struct route_info *rinfo = (struct route_info *) opt;
+       struct in6_addr prefix_buf, *prefix;
+       unsigned int pref;
+       u32 lifetime;
+       struct rt6_info *rt;
+
+       if (len < sizeof(struct route_info)) {
+               return -EINVAL;
        }
-       spin_unlock(&rt6_dflt_lock);
 
-       if (!match) {
-               /*
-                * Last Resort: if no default routers found, 
-                * use addrconf default route.
-                * We don't record this route.
-                */
-               for (sprt = ip6_routing_table.leaf;
-                    sprt; sprt = sprt->u.next) {
-                       if ((sprt->rt6i_flags & RTF_DEFAULT) &&
-                           (!oif ||
-                            (sprt->rt6i_dev &&
-                             sprt->rt6i_dev->ifindex == oif))) {
-                               match = sprt;
-                               break;
-                       }
+       /* Sanity check for prefix_len and length */
+       if (rinfo->length > 3) {
+               return -EINVAL;
+       } else if (rinfo->prefix_len > 128) {
+               return -EINVAL;
+       } else if (rinfo->prefix_len > 64) {
+               if (rinfo->length < 2) {
+                       return -EINVAL;
                }
-               if (!match) {
-                       /* no default route.  give up. */
-                       match = &ip6_null_entry;
+       } else if (rinfo->prefix_len > 0) {
+               if (rinfo->length < 1) {
+                       return -EINVAL;
                }
        }
 
-       return match;
+       pref = rinfo->route_pref;
+       if (pref == ICMPV6_ROUTER_PREF_INVALID)
+               pref = ICMPV6_ROUTER_PREF_MEDIUM;
+
+       lifetime = ntohl(rinfo->lifetime);
+       if (lifetime == 0xffffffff) {
+               /* infinity */
+       } else if (lifetime > 0x7fffffff/HZ) {
+               /* Avoid arithmetic overflow */
+               lifetime = 0x7fffffff/HZ - 1;
+       }
+
+       if (rinfo->length == 3)
+               prefix = (struct in6_addr *)rinfo->prefix;
+       else {
+               /* this function is safe */
+               ipv6_addr_prefix(&prefix_buf,
+                                (struct in6_addr *)rinfo->prefix,
+                                rinfo->prefix_len);
+               prefix = &prefix_buf;
+       }
+
+       rt = rt6_get_route_info(prefix, rinfo->prefix_len, gwaddr, dev->ifindex);
+
+       if (rt && !lifetime) {
+               ip6_del_rt(rt);
+               rt = NULL;
+       }
+
+       if (!rt && lifetime)
+               rt = rt6_add_route_info(prefix, rinfo->prefix_len, gwaddr, dev->ifindex,
+                                       pref);
+       else if (rt)
+               rt->rt6i_flags = RTF_ROUTEINFO |
+                                (rt->rt6i_flags & ~RTF_PREF_MASK) | RTF_PREF(pref);
+
+       if (rt) {
+               if (lifetime == 0xffffffff) {
+                       rt->rt6i_flags &= ~RTF_EXPIRES;
+               } else {
+                       rt->rt6i_expires = jiffies + HZ * lifetime;
+                       rt->rt6i_flags |= RTF_EXPIRES;
+               }
+               dst_release(&rt->u.dst);
+       }
+       return 0;
 }
+#endif
 
-struct rt6_info *rt6_lookup(struct in6_addr *daddr, struct in6_addr *saddr,
-                           int oif, int strict)
+#define BACKTRACK(saddr) \
+do { \
+       if (rt == &ip6_null_entry) { \
+               struct fib6_node *pn; \
+               while (1) { \
+                       if (fn->fn_flags & RTN_TL_ROOT) \
+                               goto out; \
+                       pn = fn->parent; \
+                       if (FIB6_SUBTREE(pn) && FIB6_SUBTREE(pn) != fn) \
+                               fn = fib6_lookup(FIB6_SUBTREE(pn), NULL, saddr); \
+                       else \
+                               fn = pn; \
+                       if (fn->fn_flags & RTN_RTINFO) \
+                               goto restart; \
+               } \
+       } \
+} while(0)
+
+static struct rt6_info *ip6_pol_route_lookup(struct fib6_table *table,
+                                            struct flowi *fl, int flags)
 {
        struct fib6_node *fn;
        struct rt6_info *rt;
 
-       read_lock_bh(&rt6_lock);
-       fn = fib6_lookup(&ip6_routing_table, daddr, saddr);
-       rt = rt6_device_match(fn->leaf, oif, strict);
+       read_lock_bh(&table->tb6_lock);
+       fn = fib6_lookup(&table->tb6_root, &fl->fl6_dst, &fl->fl6_src);
+restart:
+       rt = fn->leaf;
+       rt = rt6_device_match(rt, fl->oif, flags);
+       BACKTRACK(&fl->fl6_src);
+out:
        dst_hold(&rt->u.dst);
-       rt->u.dst.__use++;
-       read_unlock_bh(&rt6_lock);
+       read_unlock_bh(&table->tb6_lock);
 
        rt->u.dst.lastuse = jiffies;
-       if (rt->u.dst.error == 0)
-               return rt;
-       dst_release(&rt->u.dst);
+       rt->u.dst.__use++;
+
+       return rt;
+
+}
+
+struct rt6_info *rt6_lookup(struct in6_addr *daddr, struct in6_addr *saddr,
+                           int oif, int strict)
+{
+       struct flowi fl = {
+               .oif = oif,
+               .nl_u = {
+                       .ip6_u = {
+                               .daddr = *daddr,
+                       },
+               },
+       };
+       struct dst_entry *dst;
+       int flags = strict ? RT6_LOOKUP_F_IFACE : 0;
+
+       if (saddr) {
+               memcpy(&fl.fl6_src, saddr, sizeof(*saddr));
+               flags |= RT6_LOOKUP_F_HAS_SADDR;
+       }
+
+       dst = fib6_rule_lookup(&fl, flags, ip6_pol_route_lookup);
+       if (dst->error == 0)
+               return (struct rt6_info *) dst;
+
+       dst_release(dst);
+
        return NULL;
 }
 
-/* ip6_ins_rt is called with FREE rt6_lock.
+/* ip6_ins_rt is called with FREE table->tb6_lock.
    It takes new route entry, the addition fails by any reason the
    route is freed. In any case, if caller does not hold it, it may
    be destroyed.
  */
 
-int ip6_ins_rt(struct rt6_info *rt, struct nlmsghdr *nlh, void *_rtattr)
+static int __ip6_ins_rt(struct rt6_info *rt, struct nl_info *info)
 {
        int err;
+       struct fib6_table *table;
 
-       write_lock_bh(&rt6_lock);
-       err = fib6_add(&ip6_routing_table, rt, nlh, _rtattr);
-       write_unlock_bh(&rt6_lock);
+       table = rt->rt6i_table;
+       write_lock_bh(&table->tb6_lock);
+       err = fib6_add(&table->tb6_root, rt, info);
+       write_unlock_bh(&table->tb6_lock);
 
        return err;
 }
 
-/* No rt6_lock! If COW failed, the function returns dead route entry
-   with dst->error set to errno value.
- */
+int ip6_ins_rt(struct rt6_info *rt)
+{
+       return __ip6_ins_rt(rt, NULL);
+}
 
-static struct rt6_info *rt6_cow(struct rt6_info *ort, struct in6_addr *daddr,
-                               struct in6_addr *saddr)
+static struct rt6_info *rt6_alloc_cow(struct rt6_info *ort, struct in6_addr *daddr,
+                                     struct in6_addr *saddr)
 {
-       int err;
        struct rt6_info *rt;
 
        /*
@@ -402,11 +611,14 @@ static struct rt6_info *rt6_cow(struct rt6_info *ort, struct in6_addr *daddr,
        rt = ip6_rt_copy(ort);
 
        if (rt) {
-               ipv6_addr_copy(&rt->rt6i_dst.addr, daddr);
-
-               if (!(rt->rt6i_flags&RTF_GATEWAY))
+               if (!(rt->rt6i_flags&RTF_GATEWAY)) {
+                       if (rt->rt6i_dst.plen != 128 &&
+                           ipv6_addr_equal(&rt->rt6i_dst.addr, daddr))
+                               rt->rt6i_flags |= RTF_ANYCAST;
                        ipv6_addr_copy(&rt->rt6i_gateway, daddr);
+               }
 
+               ipv6_addr_copy(&rt->rt6i_dst.addr, daddr);
                rt->rt6i_dst.plen = 128;
                rt->rt6i_flags |= RTF_CACHE;
                rt->u.dst.flags |= DST_HOST;
@@ -420,138 +632,201 @@ static struct rt6_info *rt6_cow(struct rt6_info *ort, struct in6_addr *daddr,
 
                rt->rt6i_nexthop = ndisc_get_neigh(rt->rt6i_dev, &rt->rt6i_gateway);
 
-               dst_hold(&rt->u.dst);
-
-               err = ip6_ins_rt(rt, NULL, NULL);
-               if (err == 0)
-                       return rt;
-
-               rt->u.dst.error = err;
-
-               return rt;
        }
-       dst_hold(&ip6_null_entry.u.dst);
-       return &ip6_null_entry;
-}
 
-#define BACKTRACK() \
-if (rt == &ip6_null_entry && strict) { \
-       while ((fn = fn->parent) != NULL) { \
-               if (fn->fn_flags & RTN_ROOT) { \
-                       dst_hold(&rt->u.dst); \
-                       goto out; \
-               } \
-               if (fn->fn_flags & RTN_RTINFO) \
-                       goto restart; \
-       } \
+       return rt;
 }
 
+static struct rt6_info *rt6_alloc_clone(struct rt6_info *ort, struct in6_addr *daddr)
+{
+       struct rt6_info *rt = ip6_rt_copy(ort);
+       if (rt) {
+               ipv6_addr_copy(&rt->rt6i_dst.addr, daddr);
+               rt->rt6i_dst.plen = 128;
+               rt->rt6i_flags |= RTF_CACHE;
+               rt->u.dst.flags |= DST_HOST;
+               rt->rt6i_nexthop = neigh_clone(ort->rt6i_nexthop);
+       }
+       return rt;
+}
 
-void ip6_route_input(struct sk_buff *skb)
+static struct rt6_info *ip6_pol_route_input(struct fib6_table *table,
+                                           struct flowi *fl, int flags)
 {
        struct fib6_node *fn;
-       struct rt6_info *rt;
-       int strict;
+       struct rt6_info *rt, *nrt;
+       int strict = 0;
        int attempts = 3;
+       int err;
+       int reachable = ipv6_devconf.forwarding ? 0 : RT6_LOOKUP_F_REACHABLE;
 
-       strict = ipv6_addr_type(&skb->nh.ipv6h->daddr) & (IPV6_ADDR_MULTICAST|IPV6_ADDR_LINKLOCAL);
+       strict |= flags & RT6_LOOKUP_F_IFACE;
 
 relookup:
-       read_lock_bh(&rt6_lock);
+       read_lock_bh(&table->tb6_lock);
 
-       fn = fib6_lookup(&ip6_routing_table, &skb->nh.ipv6h->daddr,
-                        &skb->nh.ipv6h->saddr);
+restart_2:
+       fn = fib6_lookup(&table->tb6_root, &fl->fl6_dst, &fl->fl6_src);
 
 restart:
-       rt = fn->leaf;
-
-       if ((rt->rt6i_flags & RTF_CACHE)) {
-               rt = rt6_device_match(rt, skb->dev->ifindex, strict);
-               BACKTRACK();
-               dst_hold(&rt->u.dst);
+       rt = rt6_select(fn, fl->iif, strict | reachable);
+       BACKTRACK(&fl->fl6_src);
+       if (rt == &ip6_null_entry ||
+           rt->rt6i_flags & RTF_CACHE)
                goto out;
-       }
 
-       rt = rt6_device_match(rt, skb->dev->ifindex, 0);
-       BACKTRACK();
+       dst_hold(&rt->u.dst);
+       read_unlock_bh(&table->tb6_lock);
+
+       if (!rt->rt6i_nexthop && !(rt->rt6i_flags & RTF_NONEXTHOP))
+               nrt = rt6_alloc_cow(rt, &fl->fl6_dst, &fl->fl6_src);
+       else {
+#if CLONE_OFFLINK_ROUTE
+               nrt = rt6_alloc_clone(rt, &fl->fl6_dst);
+#else
+               goto out2;
+#endif
+       }
 
-       if (!rt->rt6i_nexthop && !(rt->rt6i_flags & RTF_NONEXTHOP)) {
-               read_unlock_bh(&rt6_lock);
+       dst_release(&rt->u.dst);
+       rt = nrt ? : &ip6_null_entry;
 
-               rt = rt6_cow(rt, &skb->nh.ipv6h->daddr,
-                            &skb->nh.ipv6h->saddr);
-                       
-               if (rt->u.dst.error != -EEXIST || --attempts <= 0)
+       dst_hold(&rt->u.dst);
+       if (nrt) {
+               err = ip6_ins_rt(nrt);
+               if (!err)
                        goto out2;
-               /* Race condition! In the gap, when rt6_lock was
-                  released someone could insert this route.  Relookup.
-               */
-               dst_release(&rt->u.dst);
-               goto relookup;
        }
-       dst_hold(&rt->u.dst);
+
+       if (--attempts <= 0)
+               goto out2;
+
+       /*
+        * Race condition! In the gap, when table->tb6_lock was
+        * released someone could insert this route.  Relookup.
+        */
+       dst_release(&rt->u.dst);
+       goto relookup;
 
 out:
-       read_unlock_bh(&rt6_lock);
+       if (reachable) {
+               reachable = 0;
+               goto restart_2;
+       }
+       dst_hold(&rt->u.dst);
+       read_unlock_bh(&table->tb6_lock);
 out2:
        rt->u.dst.lastuse = jiffies;
        rt->u.dst.__use++;
-       skb->dst = (struct dst_entry *) rt;
+
+       return rt;
 }
 
-struct dst_entry * ip6_route_output(struct sock *sk, struct flowi *fl)
+void ip6_route_input(struct sk_buff *skb)
+{
+       struct ipv6hdr *iph = skb->nh.ipv6h;
+       int flags = RT6_LOOKUP_F_HAS_SADDR;
+       struct flowi fl = {
+               .iif = skb->dev->ifindex,
+               .nl_u = {
+                       .ip6_u = {
+                               .daddr = iph->daddr,
+                               .saddr = iph->saddr,
+                               .flowlabel = (* (__be32 *) iph)&IPV6_FLOWINFO_MASK,
+                       },
+               },
+               .mark = skb->mark,
+               .proto = iph->nexthdr,
+       };
+
+       if (rt6_need_strict(&iph->daddr))
+               flags |= RT6_LOOKUP_F_IFACE;
+
+       skb->dst = fib6_rule_lookup(&fl, flags, ip6_pol_route_input);
+}
+
+static struct rt6_info *ip6_pol_route_output(struct fib6_table *table,
+                                            struct flowi *fl, int flags)
 {
        struct fib6_node *fn;
-       struct rt6_info *rt;
-       int strict;
+       struct rt6_info *rt, *nrt;
+       int strict = 0;
        int attempts = 3;
+       int err;
+       int reachable = ipv6_devconf.forwarding ? 0 : RT6_LOOKUP_F_REACHABLE;
 
-       strict = ipv6_addr_type(&fl->fl6_dst) & (IPV6_ADDR_MULTICAST|IPV6_ADDR_LINKLOCAL);
+       strict |= flags & RT6_LOOKUP_F_IFACE;
 
 relookup:
-       read_lock_bh(&rt6_lock);
+       read_lock_bh(&table->tb6_lock);
 
-       fn = fib6_lookup(&ip6_routing_table, &fl->fl6_dst, &fl->fl6_src);
+restart_2:
+       fn = fib6_lookup(&table->tb6_root, &fl->fl6_dst, &fl->fl6_src);
 
 restart:
-       rt = fn->leaf;
-
-       if ((rt->rt6i_flags & RTF_CACHE)) {
-               rt = rt6_device_match(rt, fl->oif, strict);
-               BACKTRACK();
-               dst_hold(&rt->u.dst);
+       rt = rt6_select(fn, fl->oif, strict | reachable);
+       BACKTRACK(&fl->fl6_src);
+       if (rt == &ip6_null_entry ||
+           rt->rt6i_flags & RTF_CACHE)
                goto out;
-       }
-       if (rt->rt6i_flags & RTF_DEFAULT) {
-               if (rt->rt6i_metric >= IP6_RT_PRIO_ADDRCONF)
-                       rt = rt6_best_dflt(rt, fl->oif);
-       } else {
-               rt = rt6_device_match(rt, fl->oif, strict);
-               BACKTRACK();
-       }
 
-       if (!rt->rt6i_nexthop && !(rt->rt6i_flags & RTF_NONEXTHOP)) {
-               read_unlock_bh(&rt6_lock);
+       dst_hold(&rt->u.dst);
+       read_unlock_bh(&table->tb6_lock);
 
-               rt = rt6_cow(rt, &fl->fl6_dst, &fl->fl6_src);
+       if (!rt->rt6i_nexthop && !(rt->rt6i_flags & RTF_NONEXTHOP))
+               nrt = rt6_alloc_cow(rt, &fl->fl6_dst, &fl->fl6_src);
+       else {
+#if CLONE_OFFLINK_ROUTE
+               nrt = rt6_alloc_clone(rt, &fl->fl6_dst);
+#else
+               goto out2;
+#endif
+       }
 
-               if (rt->u.dst.error != -EEXIST || --attempts <= 0)
-                       goto out2;
+       dst_release(&rt->u.dst);
+       rt = nrt ? : &ip6_null_entry;
 
-               /* Race condition! In the gap, when rt6_lock was
-                  released someone could insert this route.  Relookup.
-               */
-               dst_release(&rt->u.dst);
-               goto relookup;
-       }
        dst_hold(&rt->u.dst);
+       if (nrt) {
+               err = ip6_ins_rt(nrt);
+               if (!err)
+                       goto out2;
+       }
+
+       if (--attempts <= 0)
+               goto out2;
+
+       /*
+        * Race condition! In the gap, when table->tb6_lock was
+        * released someone could insert this route.  Relookup.
+        */
+       dst_release(&rt->u.dst);
+       goto relookup;
 
 out:
-       read_unlock_bh(&rt6_lock);
+       if (reachable) {
+               reachable = 0;
+               goto restart_2;
+       }
+       dst_hold(&rt->u.dst);
+       read_unlock_bh(&table->tb6_lock);
 out2:
        rt->u.dst.lastuse = jiffies;
        rt->u.dst.__use++;
-       return &rt->u.dst;
+       return rt;
+}
+
+struct dst_entry * ip6_route_output(struct sock *sk, struct flowi *fl)
+{
+       int flags = 0;
+
+       if (rt6_need_strict(&fl->fl6_dst))
+               flags |= RT6_LOOKUP_F_IFACE;
+
+       if (!ipv6_addr_any(&fl->fl6_src))
+               flags |= RT6_LOOKUP_F_HAS_SADDR;
+
+       return fib6_rule_lookup(fl, flags, ip6_pol_route_output);
 }
 
 
@@ -568,7 +843,6 @@ static struct dst_entry *ip6_dst_check(struct dst_entry *dst, u32 cookie)
        if (rt && rt->rt6i_node && (rt->rt6i_node->fn_sernum == cookie))
                return dst;
 
-       dst_release(dst);
        return NULL;
 }
 
@@ -578,7 +852,7 @@ static struct dst_entry *ip6_negative_advice(struct dst_entry *dst)
 
        if (rt) {
                if (rt->rt6i_flags & RTF_CACHE)
-                       ip6_del_rt(rt, NULL, NULL);
+                       ip6_del_rt(rt);
                else
                        dst_release(dst);
        }
@@ -605,16 +879,17 @@ static void ip6_rt_update_pmtu(struct dst_entry *dst, u32 mtu)
 {
        struct rt6_info *rt6 = (struct rt6_info*)dst;
 
-       if (mtu < dst_pmtu(dst) && rt6->rt6i_dst.plen == 128) {
+       if (mtu < dst_mtu(dst) && rt6->rt6i_dst.plen == 128) {
                rt6->rt6i_flags |= RTF_MODIFIED;
-               if (mtu < IPV6_MIN_MTU)
+               if (mtu < IPV6_MIN_MTU) {
                        mtu = IPV6_MIN_MTU;
+                       dst->metrics[RTAX_FEATURES-1] |= RTAX_FEATURE_ALLFRAG;
+               }
                dst->metrics[RTAX_MTU-1] = mtu;
+               call_netevent_notifiers(NETEVENT_PMTU_UPDATE, dst);
        }
 }
 
-/* Protected by rt6_lock.  */
-static struct dst_entry *ndisc_dst_gc_list;
 static int ipv6_get_mtu(struct net_device *dev);
 
 static inline unsigned int ipv6_advmss(unsigned int mtu)
@@ -635,10 +910,13 @@ static inline unsigned int ipv6_advmss(unsigned int mtu)
        return mtu;
 }
 
+static struct dst_entry *ndisc_dst_gc_list;
+static DEFINE_SPINLOCK(ndisc_lock);
+
 struct dst_entry *ndisc_dst_alloc(struct net_device *dev, 
                                  struct neighbour *neigh,
                                  struct in6_addr *addr,
-                                 int (*output)(struct sk_buff **))
+                                 int (*output)(struct sk_buff *))
 {
        struct rt6_info *rt;
        struct inet6_dev *idev = in6_dev_get(dev);
@@ -647,8 +925,10 @@ struct dst_entry *ndisc_dst_alloc(struct net_device *dev,
                return NULL;
 
        rt = ip6_dst_alloc();
-       if (unlikely(rt == NULL))
+       if (unlikely(rt == NULL)) {
+               in6_dev_put(idev);
                goto out;
+       }
 
        dev_hold(dev);
        if (neigh)
@@ -662,7 +942,7 @@ struct dst_entry *ndisc_dst_alloc(struct net_device *dev,
        atomic_set(&rt->u.dst.__refcnt, 1);
        rt->u.dst.metrics[RTAX_HOPLIMIT-1] = 255;
        rt->u.dst.metrics[RTAX_MTU-1] = ipv6_get_mtu(rt->rt6i_dev);
-       rt->u.dst.metrics[RTAX_ADVMSS-1] = ipv6_advmss(dst_pmtu(&rt->u.dst));
+       rt->u.dst.metrics[RTAX_ADVMSS-1] = ipv6_advmss(dst_mtu(&rt->u.dst));
        rt->u.dst.output  = output;
 
 #if 0  /* there's no chance to use these for ndisc */
@@ -673,15 +953,15 @@ struct dst_entry *ndisc_dst_alloc(struct net_device *dev,
        rt->rt6i_dst.plen = 128;
 #endif
 
-       write_lock_bh(&rt6_lock);
+       spin_lock_bh(&ndisc_lock);
        rt->u.dst.next = ndisc_dst_gc_list;
        ndisc_dst_gc_list = &rt->u.dst;
-       write_unlock_bh(&rt6_lock);
+       spin_unlock_bh(&ndisc_lock);
 
        fib6_force_start_gc();
 
 out:
-       return (struct dst_entry *)rt;
+       return &rt->u.dst;
 }
 
 int ndisc_dst_gc(int *more)
@@ -690,8 +970,11 @@ int ndisc_dst_gc(int *more)
        int freed;
 
        next = NULL;
+       freed = 0;
+
+       spin_lock_bh(&ndisc_lock);
        pprev = &ndisc_dst_gc_list;
-       freed = 0;
+
        while ((dst = *pprev) != NULL) {
                if (!atomic_read(&dst->__refcnt)) {
                        *pprev = dst->next;
@@ -703,6 +986,8 @@ int ndisc_dst_gc(int *more)
                }
        }
 
+       spin_unlock_bh(&ndisc_lock);
+
        return freed;
 }
 
@@ -746,7 +1031,7 @@ static int ipv6_get_mtu(struct net_device *dev)
        return mtu;
 }
 
-static int ipv6_get_hoplimit(struct net_device *dev)
+int ipv6_get_hoplimit(struct net_device *dev)
 {
        int hoplimit = ipv6_devconf.hop_limit;
        struct inet6_dev *idev;
@@ -763,27 +1048,24 @@ static int ipv6_get_hoplimit(struct net_device *dev)
  *
  */
 
-int ip6_route_add(struct in6_rtmsg *rtmsg, struct nlmsghdr *nlh, void *_rtattr)
+int ip6_route_add(struct fib6_config *cfg)
 {
        int err;
-       struct rtmsg *r;
-       struct rtattr **rta;
        struct rt6_info *rt = NULL;
        struct net_device *dev = NULL;
        struct inet6_dev *idev = NULL;
+       struct fib6_table *table;
        int addr_type;
 
-       rta = (struct rtattr **) _rtattr;
-
-       if (rtmsg->rtmsg_dst_len > 128 || rtmsg->rtmsg_src_len > 128)
+       if (cfg->fc_dst_len > 128 || cfg->fc_src_len > 128)
                return -EINVAL;
 #ifndef CONFIG_IPV6_SUBTREES
-       if (rtmsg->rtmsg_src_len)
+       if (cfg->fc_src_len)
                return -EINVAL;
 #endif
-       if (rtmsg->rtmsg_ifindex) {
+       if (cfg->fc_ifindex) {
                err = -ENODEV;
-               dev = dev_get_by_index(rtmsg->rtmsg_ifindex);
+               dev = dev_get_by_index(cfg->fc_ifindex);
                if (!dev)
                        goto out;
                idev = in6_dev_get(dev);
@@ -791,23 +1073,30 @@ int ip6_route_add(struct in6_rtmsg *rtmsg, struct nlmsghdr *nlh, void *_rtattr)
                        goto out;
        }
 
-       if (rtmsg->rtmsg_metric == 0)
-               rtmsg->rtmsg_metric = IP6_RT_PRIO_USER;
+       if (cfg->fc_metric == 0)
+               cfg->fc_metric = IP6_RT_PRIO_USER;
+
+       table = fib6_new_table(cfg->fc_table);
+       if (table == NULL) {
+               err = -ENOBUFS;
+               goto out;
+       }
 
        rt = ip6_dst_alloc();
 
-       if (rt == NULL)
-               return -ENOMEM;
+       if (rt == NULL) {
+               err = -ENOMEM;
+               goto out;
+       }
 
        rt->u.dst.obsolete = -1;
-       rt->rt6i_expires = clock_t_to_jiffies(rtmsg->rtmsg_info);
-       if (nlh && (r = NLMSG_DATA(nlh))) {
-               rt->rt6i_protocol = r->rtm_protocol;
-       } else {
-               rt->rt6i_protocol = RTPROT_BOOT;
-       }
+       rt->rt6i_expires = jiffies + clock_t_to_jiffies(cfg->fc_expires);
+
+       if (cfg->fc_protocol == RTPROT_UNSPEC)
+               cfg->fc_protocol = RTPROT_BOOT;
+       rt->rt6i_protocol = cfg->fc_protocol;
 
-       addr_type = ipv6_addr_type(&rtmsg->rtmsg_dst);
+       addr_type = ipv6_addr_type(&cfg->fc_dst);
 
        if (addr_type & IPV6_ADDR_MULTICAST)
                rt->u.dst.input = ip6_mc_input;
@@ -816,24 +1105,22 @@ int ip6_route_add(struct in6_rtmsg *rtmsg, struct nlmsghdr *nlh, void *_rtattr)
 
        rt->u.dst.output = ip6_output;
 
-       ipv6_addr_prefix(&rt->rt6i_dst.addr, 
-                        &rtmsg->rtmsg_dst, rtmsg->rtmsg_dst_len);
-       rt->rt6i_dst.plen = rtmsg->rtmsg_dst_len;
+       ipv6_addr_prefix(&rt->rt6i_dst.addr, &cfg->fc_dst, cfg->fc_dst_len);
+       rt->rt6i_dst.plen = cfg->fc_dst_len;
        if (rt->rt6i_dst.plen == 128)
               rt->u.dst.flags = DST_HOST;
 
 #ifdef CONFIG_IPV6_SUBTREES
-       ipv6_addr_prefix(&rt->rt6i_src.addr, 
-                        &rtmsg->rtmsg_src, rtmsg->rtmsg_src_len);
-       rt->rt6i_src.plen = rtmsg->rtmsg_src_len;
+       ipv6_addr_prefix(&rt->rt6i_src.addr, &cfg->fc_src, cfg->fc_src_len);
+       rt->rt6i_src.plen = cfg->fc_src_len;
 #endif
 
-       rt->rt6i_metric = rtmsg->rtmsg_metric;
+       rt->rt6i_metric = cfg->fc_metric;
 
        /* We cannot add true routes via loopback here,
           they would result in kernel looping; promote them to reject routes
         */
-       if ((rtmsg->rtmsg_flags&RTF_REJECT) ||
+       if ((cfg->fc_flags & RTF_REJECT) ||
            (dev && (dev->flags&IFF_LOOPBACK) && !(addr_type&IPV6_ADDR_LOOPBACK))) {
                /* hold loopback dev/idev if we haven't done so. */
                if (dev != &loopback_dev) {
@@ -856,12 +1143,12 @@ int ip6_route_add(struct in6_rtmsg *rtmsg, struct nlmsghdr *nlh, void *_rtattr)
                goto install_route;
        }
 
-       if (rtmsg->rtmsg_flags & RTF_GATEWAY) {
+       if (cfg->fc_flags & RTF_GATEWAY) {
                struct in6_addr *gw_addr;
                int gwa_type;
 
-               gw_addr = &rtmsg->rtmsg_gateway;
-               ipv6_addr_copy(&rt->rt6i_gateway, &rtmsg->rtmsg_gateway);
+               gw_addr = &cfg->fc_gateway;
+               ipv6_addr_copy(&rt->rt6i_gateway, gw_addr);
                gwa_type = ipv6_addr_type(gw_addr);
 
                if (gwa_type != (IPV6_ADDR_LINKLOCAL|IPV6_ADDR_UNICAST)) {
@@ -878,7 +1165,7 @@ int ip6_route_add(struct in6_rtmsg *rtmsg, struct nlmsghdr *nlh, void *_rtattr)
                        if (!(gwa_type&IPV6_ADDR_UNICAST))
                                goto out;
 
-                       grt = rt6_lookup(gw_addr, NULL, rtmsg->rtmsg_ifindex, 1);
+                       grt = rt6_lookup(gw_addr, NULL, cfg->fc_ifindex, 1);
 
                        err = -EHOSTUNREACH;
                        if (grt == NULL)
@@ -910,7 +1197,7 @@ int ip6_route_add(struct in6_rtmsg *rtmsg, struct nlmsghdr *nlh, void *_rtattr)
        if (dev == NULL)
                goto out;
 
-       if (rtmsg->rtmsg_flags & (RTF_GATEWAY|RTF_NONEXTHOP)) {
+       if (cfg->fc_flags & (RTF_GATEWAY | RTF_NONEXTHOP)) {
                rt->rt6i_nexthop = __neigh_lookup_errno(&nd_tbl, &rt->rt6i_gateway, dev);
                if (IS_ERR(rt->rt6i_nexthop)) {
                        err = PTR_ERR(rt->rt6i_nexthop);
@@ -919,161 +1206,213 @@ int ip6_route_add(struct in6_rtmsg *rtmsg, struct nlmsghdr *nlh, void *_rtattr)
                }
        }
 
-       rt->rt6i_flags = rtmsg->rtmsg_flags;
+       rt->rt6i_flags = cfg->fc_flags;
 
 install_route:
-       if (rta && rta[RTA_METRICS-1]) {
-               int attrlen = RTA_PAYLOAD(rta[RTA_METRICS-1]);
-               struct rtattr *attr = RTA_DATA(rta[RTA_METRICS-1]);
-
-               while (RTA_OK(attr, attrlen)) {
-                       unsigned flavor = attr->rta_type;
-                       if (flavor) {
-                               if (flavor > RTAX_MAX) {
+       if (cfg->fc_mx) {
+               struct nlattr *nla;
+               int remaining;
+
+               nla_for_each_attr(nla, cfg->fc_mx, cfg->fc_mx_len, remaining) {
+                       int type = nla->nla_type;
+
+                       if (type) {
+                               if (type > RTAX_MAX) {
                                        err = -EINVAL;
                                        goto out;
                                }
-                               rt->u.dst.metrics[flavor-1] =
-                                       *(u32 *)RTA_DATA(attr);
+
+                               rt->u.dst.metrics[type - 1] = nla_get_u32(nla);
                        }
-                       attr = RTA_NEXT(attr, attrlen);
                }
        }
 
-       if (rt->u.dst.metrics[RTAX_HOPLIMIT-1] == 0) {
-               if (ipv6_addr_is_multicast(&rt->rt6i_dst.addr))
-                       rt->u.dst.metrics[RTAX_HOPLIMIT-1] =
-                               IPV6_DEFAULT_MCASTHOPS;
-               else
-                       rt->u.dst.metrics[RTAX_HOPLIMIT-1] =
-                               ipv6_get_hoplimit(dev);
-       }
-
+       if (rt->u.dst.metrics[RTAX_HOPLIMIT-1] == 0)
+               rt->u.dst.metrics[RTAX_HOPLIMIT-1] = -1;
        if (!rt->u.dst.metrics[RTAX_MTU-1])
                rt->u.dst.metrics[RTAX_MTU-1] = ipv6_get_mtu(dev);
        if (!rt->u.dst.metrics[RTAX_ADVMSS-1])
-               rt->u.dst.metrics[RTAX_ADVMSS-1] = ipv6_advmss(dst_pmtu(&rt->u.dst));
+               rt->u.dst.metrics[RTAX_ADVMSS-1] = ipv6_advmss(dst_mtu(&rt->u.dst));
        rt->u.dst.dev = dev;
        rt->rt6i_idev = idev;
-       return ip6_ins_rt(rt, nlh, _rtattr);
+       rt->rt6i_table = table;
+       return __ip6_ins_rt(rt, &cfg->fc_nlinfo);
 
 out:
        if (dev)
                dev_put(dev);
-       dst_free((struct dst_entry *) rt);
+       if (idev)
+               in6_dev_put(idev);
+       if (rt)
+               dst_free(&rt->u.dst);
        return err;
 }
 
-int ip6_del_rt(struct rt6_info *rt, struct nlmsghdr *nlh, void *_rtattr)
+static int __ip6_del_rt(struct rt6_info *rt, struct nl_info *info)
 {
        int err;
+       struct fib6_table *table;
 
-       write_lock_bh(&rt6_lock);
+       if (rt == &ip6_null_entry)
+               return -ENOENT;
 
-       rt6_reset_dflt_pointer(NULL);
+       table = rt->rt6i_table;
+       write_lock_bh(&table->tb6_lock);
 
+       err = fib6_del(rt, info);
        dst_release(&rt->u.dst);
 
-       err = fib6_del(rt, nlh, _rtattr);
-       write_unlock_bh(&rt6_lock);
+       write_unlock_bh(&table->tb6_lock);
+
+       return err;
+}
+
+int ip6_del_rt(struct rt6_info *rt)
+{
+       return __ip6_del_rt(rt, NULL);
+}
+
+static int ip6_route_del(struct fib6_config *cfg)
+{
+       struct fib6_table *table;
+       struct fib6_node *fn;
+       struct rt6_info *rt;
+       int err = -ESRCH;
+
+       table = fib6_get_table(cfg->fc_table);
+       if (table == NULL)
+               return err;
+
+       read_lock_bh(&table->tb6_lock);
+
+       fn = fib6_locate(&table->tb6_root,
+                        &cfg->fc_dst, cfg->fc_dst_len,
+                        &cfg->fc_src, cfg->fc_src_len);
+       
+       if (fn) {
+               for (rt = fn->leaf; rt; rt = rt->u.next) {
+                       if (cfg->fc_ifindex &&
+                           (rt->rt6i_dev == NULL ||
+                            rt->rt6i_dev->ifindex != cfg->fc_ifindex))
+                               continue;
+                       if (cfg->fc_flags & RTF_GATEWAY &&
+                           !ipv6_addr_equal(&cfg->fc_gateway, &rt->rt6i_gateway))
+                               continue;
+                       if (cfg->fc_metric && cfg->fc_metric != rt->rt6i_metric)
+                               continue;
+                       dst_hold(&rt->u.dst);
+                       read_unlock_bh(&table->tb6_lock);
+
+                       return __ip6_del_rt(rt, &cfg->fc_nlinfo);
+               }
+       }
+       read_unlock_bh(&table->tb6_lock);
+
+       return err;
+}
+
+/*
+ *     Handle redirects
+ */
+struct ip6rd_flowi {
+       struct flowi fl;
+       struct in6_addr gateway;
+};
+
+static struct rt6_info *__ip6_route_redirect(struct fib6_table *table,
+                                            struct flowi *fl,
+                                            int flags)
+{
+       struct ip6rd_flowi *rdfl = (struct ip6rd_flowi *)fl;
+       struct rt6_info *rt;
+       struct fib6_node *fn;
+
+       /*
+        * Get the "current" route for this destination and
+        * check if the redirect has come from approriate router.
+        *
+        * RFC 2461 specifies that redirects should only be
+        * accepted if they come from the nexthop to the target.
+        * Due to the way the routes are chosen, this notion
+        * is a bit fuzzy and one might need to check all possible
+        * routes.
+        */
+
+       read_lock_bh(&table->tb6_lock);
+       fn = fib6_lookup(&table->tb6_root, &fl->fl6_dst, &fl->fl6_src);
+restart:
+       for (rt = fn->leaf; rt; rt = rt->u.next) {
+               /*
+                * Current route is on-link; redirect is always invalid.
+                *
+                * Seems, previous statement is not true. It could
+                * be node, which looks for us as on-link (f.e. proxy ndisc)
+                * But then router serving it might decide, that we should
+                * know truth 8)8) --ANK (980726).
+                */
+               if (rt6_check_expired(rt))
+                       continue;
+               if (!(rt->rt6i_flags & RTF_GATEWAY))
+                       continue;
+               if (fl->oif != rt->rt6i_dev->ifindex)
+                       continue;
+               if (!ipv6_addr_equal(&rdfl->gateway, &rt->rt6i_gateway))
+                       continue;
+               break;
+       }
+
+       if (!rt)
+               rt = &ip6_null_entry;
+       BACKTRACK(&fl->fl6_src);
+out:
+       dst_hold(&rt->u.dst);
+
+       read_unlock_bh(&table->tb6_lock);
 
-       return err;
-}
+       return rt;
+};
 
-static int ip6_route_del(struct in6_rtmsg *rtmsg, struct nlmsghdr *nlh, void *_rtattr)
+static struct rt6_info *ip6_route_redirect(struct in6_addr *dest,
+                                          struct in6_addr *src,
+                                          struct in6_addr *gateway,
+                                          struct net_device *dev)
 {
-       struct fib6_node *fn;
-       struct rt6_info *rt;
-       int err = -ESRCH;
-
-       read_lock_bh(&rt6_lock);
-
-       fn = fib6_locate(&ip6_routing_table,
-                        &rtmsg->rtmsg_dst, rtmsg->rtmsg_dst_len,
-                        &rtmsg->rtmsg_src, rtmsg->rtmsg_src_len);
-       
-       if (fn) {
-               for (rt = fn->leaf; rt; rt = rt->u.next) {
-                       if (rtmsg->rtmsg_ifindex &&
-                           (rt->rt6i_dev == NULL ||
-                            rt->rt6i_dev->ifindex != rtmsg->rtmsg_ifindex))
-                               continue;
-                       if (rtmsg->rtmsg_flags&RTF_GATEWAY &&
-                           ipv6_addr_cmp(&rtmsg->rtmsg_gateway, &rt->rt6i_gateway))
-                               continue;
-                       if (rtmsg->rtmsg_metric &&
-                           rtmsg->rtmsg_metric != rt->rt6i_metric)
-                               continue;
-                       dst_hold(&rt->u.dst);
-                       read_unlock_bh(&rt6_lock);
+       int flags = RT6_LOOKUP_F_HAS_SADDR;
+       struct ip6rd_flowi rdfl = {
+               .fl = {
+                       .oif = dev->ifindex,
+                       .nl_u = {
+                               .ip6_u = {
+                                       .daddr = *dest,
+                                       .saddr = *src,
+                               },
+                       },
+               },
+               .gateway = *gateway,
+       };
 
-                       return ip6_del_rt(rt, nlh, _rtattr);
-               }
-       }
-       read_unlock_bh(&rt6_lock);
+       if (rt6_need_strict(dest))
+               flags |= RT6_LOOKUP_F_IFACE;
 
-       return err;
+       return (struct rt6_info *)fib6_rule_lookup((struct flowi *)&rdfl, flags, __ip6_route_redirect);
 }
 
-/*
- *     Handle redirects
- */
-void rt6_redirect(struct in6_addr *dest, struct in6_addr *saddr,
+void rt6_redirect(struct in6_addr *dest, struct in6_addr *src,
+                 struct in6_addr *saddr,
                  struct neighbour *neigh, u8 *lladdr, int on_link)
 {
-       struct rt6_info *rt, *nrt;
-
-       /* Locate old route to this destination. */
-       rt = rt6_lookup(dest, NULL, neigh->dev->ifindex, 1);
-
-       if (rt == NULL)
-               return;
-
-       if (neigh->dev != rt->rt6i_dev)
-               goto out;
+       struct rt6_info *rt, *nrt = NULL;
+       struct netevent_redirect netevent;
 
-       /*
-        * Current route is on-link; redirect is always invalid.
-        * 
-        * Seems, previous statement is not true. It could
-        * be node, which looks for us as on-link (f.e. proxy ndisc)
-        * But then router serving it might decide, that we should
-        * know truth 8)8) --ANK (980726).
-        */
-       if (!(rt->rt6i_flags&RTF_GATEWAY))
-               goto out;
+       rt = ip6_route_redirect(dest, src, saddr, neigh->dev);
 
-       /*
-        *      RFC 2461 specifies that redirects should only be
-        *      accepted if they come from the nexthop to the target.
-        *      Due to the way default routers are chosen, this notion
-        *      is a bit fuzzy and one might need to check all default
-        *      routers.
-        */
-       if (ipv6_addr_cmp(saddr, &rt->rt6i_gateway)) {
-               if (rt->rt6i_flags & RTF_DEFAULT) {
-                       struct rt6_info *rt1;
-
-                       read_lock(&rt6_lock);
-                       for (rt1 = ip6_routing_table.leaf; rt1; rt1 = rt1->u.next) {
-                               if (!ipv6_addr_cmp(saddr, &rt1->rt6i_gateway)) {
-                                       dst_hold(&rt1->u.dst);
-                                       dst_release(&rt->u.dst);
-                                       read_unlock(&rt6_lock);
-                                       rt = rt1;
-                                       goto source_ok;
-                               }
-                       }
-                       read_unlock(&rt6_lock);
-               }
+       if (rt == &ip6_null_entry) {
                if (net_ratelimit())
                        printk(KERN_DEBUG "rt6_redirect: source isn't a valid nexthop "
                               "for redirect target\n");
                goto out;
        }
 
-source_ok:
-
        /*
         *      We have finally decided to accept it.
         */
@@ -1112,13 +1451,17 @@ source_ok:
        nrt->rt6i_nexthop = neigh_clone(neigh);
        /* Reset pmtu, it may be better */
        nrt->u.dst.metrics[RTAX_MTU-1] = ipv6_get_mtu(neigh->dev);
-       nrt->u.dst.metrics[RTAX_ADVMSS-1] = ipv6_advmss(dst_pmtu(&nrt->u.dst));
+       nrt->u.dst.metrics[RTAX_ADVMSS-1] = ipv6_advmss(dst_mtu(&nrt->u.dst));
 
-       if (ip6_ins_rt(nrt, NULL, NULL))
+       if (ip6_ins_rt(nrt))
                goto out;
 
+       netevent.old = &rt->u.dst;
+       netevent.new = &nrt->u.dst;
+       call_netevent_notifiers(NETEVENT_REDIRECT, &netevent);
+
        if (rt->rt6i_flags&RTF_CACHE) {
-               ip6_del_rt(rt, NULL, NULL);
+               ip6_del_rt(rt);
                return;
        }
 
@@ -1136,26 +1479,26 @@ void rt6_pmtu_discovery(struct in6_addr *daddr, struct in6_addr *saddr,
                        struct net_device *dev, u32 pmtu)
 {
        struct rt6_info *rt, *nrt;
-
-       if (pmtu < IPV6_MIN_MTU) {
-               if (net_ratelimit())
-                       printk(KERN_DEBUG "rt6_pmtu_discovery: invalid MTU value %d\n",
-                              pmtu);
-               /* According to RFC1981, the PMTU is set to the IPv6 minimum
-                  link MTU if the node receives a Packet Too Big message
-                  reporting next-hop MTU that is less than the IPv6 minimum MTU.
-                  */
-               pmtu = IPV6_MIN_MTU;
-       }
+       int allfrag = 0;
 
        rt = rt6_lookup(daddr, saddr, dev->ifindex, 0);
-
        if (rt == NULL)
                return;
 
-       if (pmtu >= dst_pmtu(&rt->u.dst))
+       if (pmtu >= dst_mtu(&rt->u.dst))
                goto out;
 
+       if (pmtu < IPV6_MIN_MTU) {
+               /*
+                * According to RFC2460, PMTU is set to the IPv6 Minimum Link 
+                * MTU (1280) and a fragment header should always be included
+                * after a node receiving Too Big message reporting PMTU is
+                * less than the IPv6 Minimum Link MTU.
+                */
+               pmtu = IPV6_MIN_MTU;
+               allfrag = 1;
+       }
+
        /* New mtu received -> path was valid.
           They are sent only in response to data packets,
           so that this nexthop apparently is reachable. --ANK
@@ -1169,6 +1512,8 @@ void rt6_pmtu_discovery(struct in6_addr *daddr, struct in6_addr *saddr,
         */
        if (rt->rt6i_flags & RTF_CACHE) {
                rt->u.dst.metrics[RTAX_MTU-1] = pmtu;
+               if (allfrag)
+                       rt->u.dst.metrics[RTAX_FEATURES-1] |= RTAX_FEATURE_ALLFRAG;
                dst_set_expires(&rt->u.dst, ip6_rt_mtu_expires);
                rt->rt6i_flags |= RTF_MODIFIED|RTF_EXPIRES;
                goto out;
@@ -1179,34 +1524,27 @@ void rt6_pmtu_discovery(struct in6_addr *daddr, struct in6_addr *saddr,
           1. It is connected route. Action: COW
           2. It is gatewayed route or NONEXTHOP route. Action: clone it.
         */
-       if (!rt->rt6i_nexthop && !(rt->rt6i_flags & RTF_NONEXTHOP)) {
-               nrt = rt6_cow(rt, daddr, saddr);
-               if (!nrt->u.dst.error) {
-                       nrt->u.dst.metrics[RTAX_MTU-1] = pmtu;
-                       /* According to RFC 1981, detecting PMTU increase shouldn't be
-                          happened within 5 mins, the recommended timer is 10 mins.
-                          Here this route expiration time is set to ip6_rt_mtu_expires
-                          which is 10 mins. After 10 mins the decreased pmtu is expired
-                          and detecting PMTU increase will be automatically happened.
-                        */
-                       dst_set_expires(&nrt->u.dst, ip6_rt_mtu_expires);
-                       nrt->rt6i_flags |= RTF_DYNAMIC|RTF_EXPIRES;
-               }
-               dst_release(&nrt->u.dst);
-       } else {
-               nrt = ip6_rt_copy(rt);
-               if (nrt == NULL)
-                       goto out;
-               ipv6_addr_copy(&nrt->rt6i_dst.addr, daddr);
-               nrt->rt6i_dst.plen = 128;
-               nrt->u.dst.flags |= DST_HOST;
-               nrt->rt6i_nexthop = neigh_clone(rt->rt6i_nexthop);
-               dst_set_expires(&nrt->u.dst, ip6_rt_mtu_expires);
-               nrt->rt6i_flags |= RTF_DYNAMIC|RTF_CACHE|RTF_EXPIRES;
+       if (!rt->rt6i_nexthop && !(rt->rt6i_flags & RTF_NONEXTHOP))
+               nrt = rt6_alloc_cow(rt, daddr, saddr);
+       else
+               nrt = rt6_alloc_clone(rt, daddr);
+
+       if (nrt) {
                nrt->u.dst.metrics[RTAX_MTU-1] = pmtu;
-               ip6_ins_rt(nrt, NULL, NULL);
-       }
+               if (allfrag)
+                       nrt->u.dst.metrics[RTAX_FEATURES-1] |= RTAX_FEATURE_ALLFRAG;
+
+               /* According to RFC 1981, detecting PMTU increase shouldn't be
+                * happened within 5 mins, the recommended timer is 10 mins.
+                * Here this route expiration time is set to ip6_rt_mtu_expires
+                * which is 10 mins. After 10 mins the decreased pmtu is expired
+                * and detecting PMTU increase will be automatically happened.
+                */
+               dst_set_expires(&nrt->u.dst, ip6_rt_mtu_expires);
+               nrt->rt6i_flags |= RTF_DYNAMIC|RTF_EXPIRES;
 
+               ip6_ins_rt(nrt);
+       }
 out:
        dst_release(&rt->u.dst);
 }
@@ -1224,6 +1562,7 @@ static struct rt6_info * ip6_rt_copy(struct rt6_info *ort)
                rt->u.dst.output = ort->u.dst.output;
 
                memcpy(rt->u.dst.metrics, ort->u.dst.metrics, RTAX_MAX*sizeof(u32));
+               rt->u.dst.error = ort->u.dst.error;
                rt->u.dst.dev = ort->u.dst.dev;
                if (rt->u.dst.dev)
                        dev_hold(rt->u.dst.dev);
@@ -1241,76 +1580,154 @@ static struct rt6_info * ip6_rt_copy(struct rt6_info *ort)
 #ifdef CONFIG_IPV6_SUBTREES
                memcpy(&rt->rt6i_src, &ort->rt6i_src, sizeof(struct rt6key));
 #endif
+               rt->rt6i_table = ort->rt6i_table;
        }
        return rt;
 }
 
+#ifdef CONFIG_IPV6_ROUTE_INFO
+static struct rt6_info *rt6_get_route_info(struct in6_addr *prefix, int prefixlen,
+                                          struct in6_addr *gwaddr, int ifindex)
+{
+       struct fib6_node *fn;
+       struct rt6_info *rt = NULL;
+       struct fib6_table *table;
+
+       table = fib6_get_table(RT6_TABLE_INFO);
+       if (table == NULL)
+               return NULL;
+
+       write_lock_bh(&table->tb6_lock);
+       fn = fib6_locate(&table->tb6_root, prefix ,prefixlen, NULL, 0);
+       if (!fn)
+               goto out;
+
+       for (rt = fn->leaf; rt; rt = rt->u.next) {
+               if (rt->rt6i_dev->ifindex != ifindex)
+                       continue;
+               if ((rt->rt6i_flags & (RTF_ROUTEINFO|RTF_GATEWAY)) != (RTF_ROUTEINFO|RTF_GATEWAY))
+                       continue;
+               if (!ipv6_addr_equal(&rt->rt6i_gateway, gwaddr))
+                       continue;
+               dst_hold(&rt->u.dst);
+               break;
+       }
+out:
+       write_unlock_bh(&table->tb6_lock);
+       return rt;
+}
+
+static struct rt6_info *rt6_add_route_info(struct in6_addr *prefix, int prefixlen,
+                                          struct in6_addr *gwaddr, int ifindex,
+                                          unsigned pref)
+{
+       struct fib6_config cfg = {
+               .fc_table       = RT6_TABLE_INFO,
+               .fc_metric      = 1024,
+               .fc_ifindex     = ifindex,
+               .fc_dst_len     = prefixlen,
+               .fc_flags       = RTF_GATEWAY | RTF_ADDRCONF | RTF_ROUTEINFO |
+                                 RTF_UP | RTF_PREF(pref),
+       };
+
+       ipv6_addr_copy(&cfg.fc_dst, prefix);
+       ipv6_addr_copy(&cfg.fc_gateway, gwaddr);
+
+       /* We should treat it as a default route if prefix length is 0. */
+       if (!prefixlen)
+               cfg.fc_flags |= RTF_DEFAULT;
+
+       ip6_route_add(&cfg);
+
+       return rt6_get_route_info(prefix, prefixlen, gwaddr, ifindex);
+}
+#endif
+
 struct rt6_info *rt6_get_dflt_router(struct in6_addr *addr, struct net_device *dev)
 {      
        struct rt6_info *rt;
-       struct fib6_node *fn;
+       struct fib6_table *table;
 
-       fn = &ip6_routing_table;
+       table = fib6_get_table(RT6_TABLE_DFLT);
+       if (table == NULL)
+               return NULL;
 
-       write_lock_bh(&rt6_lock);
-       for (rt = fn->leaf; rt; rt=rt->u.next) {
+       write_lock_bh(&table->tb6_lock);
+       for (rt = table->tb6_root.leaf; rt; rt=rt->u.next) {
                if (dev == rt->rt6i_dev &&
-                   ipv6_addr_cmp(&rt->rt6i_gateway, addr) == 0)
+                   ((rt->rt6i_flags & (RTF_ADDRCONF | RTF_DEFAULT)) == (RTF_ADDRCONF | RTF_DEFAULT)) &&
+                   ipv6_addr_equal(&rt->rt6i_gateway, addr))
                        break;
        }
        if (rt)
                dst_hold(&rt->u.dst);
-       write_unlock_bh(&rt6_lock);
+       write_unlock_bh(&table->tb6_lock);
        return rt;
 }
 
 struct rt6_info *rt6_add_dflt_router(struct in6_addr *gwaddr,
-                                    struct net_device *dev)
+                                    struct net_device *dev,
+                                    unsigned int pref)
 {
-       struct in6_rtmsg rtmsg;
+       struct fib6_config cfg = {
+               .fc_table       = RT6_TABLE_DFLT,
+               .fc_metric      = 1024,
+               .fc_ifindex     = dev->ifindex,
+               .fc_flags       = RTF_GATEWAY | RTF_ADDRCONF | RTF_DEFAULT |
+                                 RTF_UP | RTF_EXPIRES | RTF_PREF(pref),
+       };
 
-       memset(&rtmsg, 0, sizeof(struct in6_rtmsg));
-       rtmsg.rtmsg_type = RTMSG_NEWROUTE;
-       ipv6_addr_copy(&rtmsg.rtmsg_gateway, gwaddr);
-       rtmsg.rtmsg_metric = 1024;
-       rtmsg.rtmsg_flags = RTF_GATEWAY | RTF_ADDRCONF | RTF_DEFAULT | RTF_UP | RTF_EXPIRES;
+       ipv6_addr_copy(&cfg.fc_gateway, gwaddr);
 
-       rtmsg.rtmsg_ifindex = dev->ifindex;
+       ip6_route_add(&cfg);
 
-       ip6_route_add(&rtmsg, NULL, NULL);
        return rt6_get_dflt_router(gwaddr, dev);
 }
 
-void rt6_purge_dflt_routers(int last_resort)
+void rt6_purge_dflt_routers(void)
 {
        struct rt6_info *rt;
-       u32 flags;
+       struct fib6_table *table;
 
-       if (last_resort)
-               flags = RTF_ALLONLINK;
-       else
-               flags = RTF_DEFAULT | RTF_ADDRCONF;     
+       /* NOTE: Keep consistent with rt6_get_dflt_router */
+       table = fib6_get_table(RT6_TABLE_DFLT);
+       if (table == NULL)
+               return;
 
 restart:
-       read_lock_bh(&rt6_lock);
-       for (rt = ip6_routing_table.leaf; rt; rt = rt->u.next) {
-               if (rt->rt6i_flags & flags) {
+       read_lock_bh(&table->tb6_lock);
+       for (rt = table->tb6_root.leaf; rt; rt = rt->u.next) {
+               if (rt->rt6i_flags & (RTF_DEFAULT | RTF_ADDRCONF)) {
                        dst_hold(&rt->u.dst);
-
-                       rt6_reset_dflt_pointer(NULL);
-
-                       read_unlock_bh(&rt6_lock);
-
-                       ip6_del_rt(rt, NULL, NULL);
-
+                       read_unlock_bh(&table->tb6_lock);
+                       ip6_del_rt(rt);
                        goto restart;
                }
        }
-       read_unlock_bh(&rt6_lock);
+       read_unlock_bh(&table->tb6_lock);
+}
+
+static void rtmsg_to_fib6_config(struct in6_rtmsg *rtmsg,
+                                struct fib6_config *cfg)
+{
+       memset(cfg, 0, sizeof(*cfg));
+
+       cfg->fc_table = RT6_TABLE_MAIN;
+       cfg->fc_ifindex = rtmsg->rtmsg_ifindex;
+       cfg->fc_metric = rtmsg->rtmsg_metric;
+       cfg->fc_expires = rtmsg->rtmsg_info;
+       cfg->fc_dst_len = rtmsg->rtmsg_dst_len;
+       cfg->fc_src_len = rtmsg->rtmsg_src_len;
+       cfg->fc_flags = rtmsg->rtmsg_flags;
+
+       ipv6_addr_copy(&cfg->fc_dst, &rtmsg->rtmsg_dst);
+       ipv6_addr_copy(&cfg->fc_src, &rtmsg->rtmsg_src);
+       ipv6_addr_copy(&cfg->fc_gateway, &rtmsg->rtmsg_gateway);
 }
 
 int ipv6_route_ioctl(unsigned int cmd, void __user *arg)
 {
+       struct fib6_config cfg;
        struct in6_rtmsg rtmsg;
        int err;
 
@@ -1323,14 +1740,16 @@ int ipv6_route_ioctl(unsigned int cmd, void __user *arg)
                                     sizeof(struct in6_rtmsg));
                if (err)
                        return -EFAULT;
-                       
+
+               rtmsg_to_fib6_config(&rtmsg, &cfg);
+
                rtnl_lock();
                switch (cmd) {
                case SIOCADDRT:
-                       err = ip6_route_add(&rtmsg, NULL, NULL);
+                       err = ip6_route_add(&cfg);
                        break;
                case SIOCDELRT:
-                       err = ip6_route_del(&rtmsg, NULL, NULL);
+                       err = ip6_route_del(&cfg);
                        break;
                default:
                        err = -EINVAL;
@@ -1347,21 +1766,50 @@ int ipv6_route_ioctl(unsigned int cmd, void __user *arg)
  *     Drop the packet on the floor
  */
 
-int ip6_pkt_discard(struct sk_buff *skb)
+static inline int ip6_pkt_drop(struct sk_buff *skb, int code)
 {
-       IP6_INC_STATS(IPSTATS_MIB_OUTNOROUTES);
-       icmpv6_send(skb, ICMPV6_DEST_UNREACH, ICMPV6_NOROUTE, 0, skb->dev);
+       int type = ipv6_addr_type(&skb->nh.ipv6h->daddr);
+       if (type == IPV6_ADDR_ANY || type == IPV6_ADDR_RESERVED)
+               IP6_INC_STATS(ip6_dst_idev(skb->dst), IPSTATS_MIB_INADDRERRORS);
+
+       IP6_INC_STATS(ip6_dst_idev(skb->dst), IPSTATS_MIB_OUTNOROUTES);
+       icmpv6_send(skb, ICMPV6_DEST_UNREACH, code, 0, skb->dev);
        kfree_skb(skb);
        return 0;
 }
 
-int ip6_pkt_discard_out(struct sk_buff **pskb)
+static int ip6_pkt_discard(struct sk_buff *skb)
+{
+       return ip6_pkt_drop(skb, ICMPV6_NOROUTE);
+}
+
+static int ip6_pkt_discard_out(struct sk_buff *skb)
+{
+       skb->dev = skb->dst->dev;
+       return ip6_pkt_discard(skb);
+}
+
+#ifdef CONFIG_IPV6_MULTIPLE_TABLES
+
+static int ip6_pkt_prohibit(struct sk_buff *skb)
+{
+       return ip6_pkt_drop(skb, ICMPV6_ADM_PROHIBITED);
+}
+
+static int ip6_pkt_prohibit_out(struct sk_buff *skb)
 {
-       (*pskb)->dev = (*pskb)->dst->dev;
-       BUG_ON(!(*pskb)->dev);
-       return ip6_pkt_discard(*pskb);
+       skb->dev = skb->dst->dev;
+       return ip6_pkt_prohibit(skb);
+}
+
+static int ip6_pkt_blk_hole(struct sk_buff *skb)
+{
+       kfree_skb(skb);
+       return 0;
 }
 
+#endif
+
 /*
  *     Allocate a dst for local (unicast / anycast) address.
  */
@@ -1384,21 +1832,24 @@ struct rt6_info *addrconf_dst_alloc(struct inet6_dev *idev,
        rt->rt6i_dev = &loopback_dev;
        rt->rt6i_idev = idev;
        rt->u.dst.metrics[RTAX_MTU-1] = ipv6_get_mtu(rt->rt6i_dev);
-       rt->u.dst.metrics[RTAX_ADVMSS-1] = ipv6_advmss(dst_pmtu(&rt->u.dst));
-       rt->u.dst.metrics[RTAX_HOPLIMIT-1] = ipv6_get_hoplimit(rt->rt6i_dev);
+       rt->u.dst.metrics[RTAX_ADVMSS-1] = ipv6_advmss(dst_mtu(&rt->u.dst));
+       rt->u.dst.metrics[RTAX_HOPLIMIT-1] = -1;
        rt->u.dst.obsolete = -1;
 
        rt->rt6i_flags = RTF_UP | RTF_NONEXTHOP;
-       if (!anycast)
+       if (anycast)
+               rt->rt6i_flags |= RTF_ANYCAST;
+       else
                rt->rt6i_flags |= RTF_LOCAL;
        rt->rt6i_nexthop = ndisc_get_neigh(rt->rt6i_dev, &rt->rt6i_gateway);
        if (rt->rt6i_nexthop == NULL) {
-               dst_free((struct dst_entry *) rt);
+               dst_free(&rt->u.dst);
                return ERR_PTR(-ENOMEM);
        }
 
        ipv6_addr_copy(&rt->rt6i_dst.addr, addr);
        rt->rt6i_dst.plen = 128;
+       rt->rt6i_table = fib6_get_table(RT6_TABLE_LOCAL);
 
        atomic_set(&rt->u.dst.__refcnt, 1);
 
@@ -1417,9 +1868,7 @@ static int fib6_ifdown(struct rt6_info *rt, void *arg)
 
 void rt6_ifdown(struct net_device *dev)
 {
-       write_lock_bh(&rt6_lock);
-       fib6_clean_tree(&ip6_routing_table, fib6_ifdown, 0, dev);
-       write_unlock_bh(&rt6_lock);
+       fib6_clean_all(fib6_ifdown, 0, dev);
 }
 
 struct rt6_mtu_change_arg
@@ -1459,9 +1908,9 @@ static int rt6_mtu_change_route(struct rt6_info *rt, void *p_arg)
         */
        if (rt->rt6i_dev == arg->dev &&
            !dst_metric_locked(&rt->u.dst, RTAX_MTU) &&
-            (dst_pmtu(&rt->u.dst) > arg->mtu ||
-             (dst_pmtu(&rt->u.dst) < arg->mtu &&
-             dst_pmtu(&rt->u.dst) == idev->cnf.mtu6)))
+            (dst_mtu(&rt->u.dst) > arg->mtu ||
+             (dst_mtu(&rt->u.dst) < arg->mtu &&
+             dst_mtu(&rt->u.dst) == idev->cnf.mtu6)))
                rt->u.dst.metrics[RTAX_MTU-1] = arg->mtu;
        rt->u.dst.metrics[RTAX_ADVMSS-1] = ipv6_advmss(arg->mtu);
        return 0;
@@ -1469,92 +1918,139 @@ static int rt6_mtu_change_route(struct rt6_info *rt, void *p_arg)
 
 void rt6_mtu_change(struct net_device *dev, unsigned mtu)
 {
-       struct rt6_mtu_change_arg arg;
+       struct rt6_mtu_change_arg arg = {
+               .dev = dev,
+               .mtu = mtu,
+       };
 
-       arg.dev = dev;
-       arg.mtu = mtu;
-       read_lock_bh(&rt6_lock);
-       fib6_clean_tree(&ip6_routing_table, rt6_mtu_change_route, 0, &arg);
-       read_unlock_bh(&rt6_lock);
+       fib6_clean_all(rt6_mtu_change_route, 0, &arg);
 }
 
-static int inet6_rtm_to_rtmsg(struct rtmsg *r, struct rtattr **rta,
-                             struct in6_rtmsg *rtmsg)
+static struct nla_policy rtm_ipv6_policy[RTA_MAX+1] __read_mostly = {
+       [RTA_GATEWAY]           = { .len = sizeof(struct in6_addr) },
+       [RTA_OIF]               = { .type = NLA_U32 },
+       [RTA_IIF]               = { .type = NLA_U32 },
+       [RTA_PRIORITY]          = { .type = NLA_U32 },
+       [RTA_METRICS]           = { .type = NLA_NESTED },
+};
+
+static int rtm_to_fib6_config(struct sk_buff *skb, struct nlmsghdr *nlh,
+                             struct fib6_config *cfg)
 {
-       memset(rtmsg, 0, sizeof(*rtmsg));
+       struct rtmsg *rtm;
+       struct nlattr *tb[RTA_MAX+1];
+       int err;
 
-       rtmsg->rtmsg_dst_len = r->rtm_dst_len;
-       rtmsg->rtmsg_src_len = r->rtm_src_len;
-       rtmsg->rtmsg_flags = RTF_UP;
-       if (r->rtm_type == RTN_UNREACHABLE)
-               rtmsg->rtmsg_flags |= RTF_REJECT;
+       err = nlmsg_parse(nlh, sizeof(*rtm), tb, RTA_MAX, rtm_ipv6_policy);
+       if (err < 0)
+               goto errout;
 
-       if (rta[RTA_GATEWAY-1]) {
-               if (rta[RTA_GATEWAY-1]->rta_len != RTA_LENGTH(16))
-                       return -EINVAL;
-               memcpy(&rtmsg->rtmsg_gateway, RTA_DATA(rta[RTA_GATEWAY-1]), 16);
-               rtmsg->rtmsg_flags |= RTF_GATEWAY;
-       }
-       if (rta[RTA_DST-1]) {
-               if (RTA_PAYLOAD(rta[RTA_DST-1]) < ((r->rtm_dst_len+7)>>3))
-                       return -EINVAL;
-               memcpy(&rtmsg->rtmsg_dst, RTA_DATA(rta[RTA_DST-1]), ((r->rtm_dst_len+7)>>3));
+       err = -EINVAL;
+       rtm = nlmsg_data(nlh);
+       memset(cfg, 0, sizeof(*cfg));
+
+       cfg->fc_table = rtm->rtm_table;
+       cfg->fc_dst_len = rtm->rtm_dst_len;
+       cfg->fc_src_len = rtm->rtm_src_len;
+       cfg->fc_flags = RTF_UP;
+       cfg->fc_protocol = rtm->rtm_protocol;
+
+       if (rtm->rtm_type == RTN_UNREACHABLE)
+               cfg->fc_flags |= RTF_REJECT;
+
+       cfg->fc_nlinfo.pid = NETLINK_CB(skb).pid;
+       cfg->fc_nlinfo.nlh = nlh;
+
+       if (tb[RTA_GATEWAY]) {
+               nla_memcpy(&cfg->fc_gateway, tb[RTA_GATEWAY], 16);
+               cfg->fc_flags |= RTF_GATEWAY;
        }
-       if (rta[RTA_SRC-1]) {
-               if (RTA_PAYLOAD(rta[RTA_SRC-1]) < ((r->rtm_src_len+7)>>3))
-                       return -EINVAL;
-               memcpy(&rtmsg->rtmsg_src, RTA_DATA(rta[RTA_SRC-1]), ((r->rtm_src_len+7)>>3));
+
+       if (tb[RTA_DST]) {
+               int plen = (rtm->rtm_dst_len + 7) >> 3;
+
+               if (nla_len(tb[RTA_DST]) < plen)
+                       goto errout;
+
+               nla_memcpy(&cfg->fc_dst, tb[RTA_DST], plen);
        }
-       if (rta[RTA_OIF-1]) {
-               if (rta[RTA_OIF-1]->rta_len != RTA_LENGTH(sizeof(int)))
-                       return -EINVAL;
-               memcpy(&rtmsg->rtmsg_ifindex, RTA_DATA(rta[RTA_OIF-1]), sizeof(int));
+
+       if (tb[RTA_SRC]) {
+               int plen = (rtm->rtm_src_len + 7) >> 3;
+
+               if (nla_len(tb[RTA_SRC]) < plen)
+                       goto errout;
+
+               nla_memcpy(&cfg->fc_src, tb[RTA_SRC], plen);
        }
-       if (rta[RTA_PRIORITY-1]) {
-               if (rta[RTA_PRIORITY-1]->rta_len != RTA_LENGTH(4))
-                       return -EINVAL;
-               memcpy(&rtmsg->rtmsg_metric, RTA_DATA(rta[RTA_PRIORITY-1]), 4);
+
+       if (tb[RTA_OIF])
+               cfg->fc_ifindex = nla_get_u32(tb[RTA_OIF]);
+
+       if (tb[RTA_PRIORITY])
+               cfg->fc_metric = nla_get_u32(tb[RTA_PRIORITY]);
+
+       if (tb[RTA_METRICS]) {
+               cfg->fc_mx = nla_data(tb[RTA_METRICS]);
+               cfg->fc_mx_len = nla_len(tb[RTA_METRICS]);
        }
-       return 0;
+
+       if (tb[RTA_TABLE])
+               cfg->fc_table = nla_get_u32(tb[RTA_TABLE]);
+
+       err = 0;
+errout:
+       return err;
 }
 
 int inet6_rtm_delroute(struct sk_buff *skb, struct nlmsghdr* nlh, void *arg)
 {
-       struct rtmsg *r = NLMSG_DATA(nlh);
-       struct in6_rtmsg rtmsg;
+       struct fib6_config cfg;
+       int err;
 
-       if (inet6_rtm_to_rtmsg(r, arg, &rtmsg))
-               return -EINVAL;
-       return ip6_route_del(&rtmsg, nlh, arg);
+       err = rtm_to_fib6_config(skb, nlh, &cfg);
+       if (err < 0)
+               return err;
+
+       return ip6_route_del(&cfg);
 }
 
 int inet6_rtm_newroute(struct sk_buff *skb, struct nlmsghdr* nlh, void *arg)
 {
-       struct rtmsg *r = NLMSG_DATA(nlh);
-       struct in6_rtmsg rtmsg;
+       struct fib6_config cfg;
+       int err;
 
-       if (inet6_rtm_to_rtmsg(r, arg, &rtmsg))
-               return -EINVAL;
-       return ip6_route_add(&rtmsg, nlh, arg);
+       err = rtm_to_fib6_config(skb, nlh, &cfg);
+       if (err < 0)
+               return err;
+
+       return ip6_route_add(&cfg);
 }
 
-struct rt6_rtnl_dump_arg
+static inline size_t rt6_nlmsg_size(void)
 {
-       struct sk_buff *skb;
-       struct netlink_callback *cb;
-};
+       return NLMSG_ALIGN(sizeof(struct rtmsg))
+              + nla_total_size(16) /* RTA_SRC */
+              + nla_total_size(16) /* RTA_DST */
+              + nla_total_size(16) /* RTA_GATEWAY */
+              + nla_total_size(16) /* RTA_PREFSRC */
+              + nla_total_size(4) /* RTA_TABLE */
+              + nla_total_size(4) /* RTA_IIF */
+              + nla_total_size(4) /* RTA_OIF */
+              + nla_total_size(4) /* RTA_PRIORITY */
+              + RTAX_MAX * nla_total_size(4) /* RTA_METRICS */
+              + nla_total_size(sizeof(struct rta_cacheinfo));
+}
 
 static int rt6_fill_node(struct sk_buff *skb, struct rt6_info *rt,
-                        struct in6_addr *dst,
-                        struct in6_addr *src,
-                        int iif,
-                        int type, u32 pid, u32 seq,
-                        struct nlmsghdr *in_nlh, int prefix)
+                        struct in6_addr *dst, struct in6_addr *src,
+                        int iif, int type, u32 pid, u32 seq,
+                        int prefix, unsigned int flags)
 {
        struct rtmsg *rtm;
-       struct nlmsghdr  *nlh;
-       unsigned char    *b = skb->tail;
-       struct rta_cacheinfo ci;
+       struct nlmsghdr *nlh;
+       long expires;
+       u32 table;
 
        if (prefix) {   /* user wants prefix routes only */
                if (!(rt->rt6i_flags & RTF_PREFIX_RT)) {
@@ -1563,17 +2059,21 @@ static int rt6_fill_node(struct sk_buff *skb, struct rt6_info *rt,
                }
        }
 
-       if (!pid && in_nlh) {
-               pid = in_nlh->nlmsg_pid;
-       }
+       nlh = nlmsg_put(skb, pid, seq, type, sizeof(*rtm), flags);
+       if (nlh == NULL)
+               return -ENOBUFS;
 
-       nlh = NLMSG_PUT(skb, pid, seq, type, sizeof(*rtm));
-       rtm = NLMSG_DATA(nlh);
+       rtm = nlmsg_data(nlh);
        rtm->rtm_family = AF_INET6;
        rtm->rtm_dst_len = rt->rt6i_dst.plen;
        rtm->rtm_src_len = rt->rt6i_src.plen;
        rtm->rtm_tos = 0;
-       rtm->rtm_table = RT_TABLE_MAIN;
+       if (rt->rt6i_table)
+               table = rt->rt6i_table->tb6_id;
+       else
+               table = RT6_TABLE_UNSPEC;
+       rtm->rtm_table = table;
+       NLA_PUT_U32(skb, RTA_TABLE, table);
        if (rt->rt6i_flags&RTF_REJECT)
                rtm->rtm_type = RTN_UNREACHABLE;
        else if (rt->rt6i_dev && (rt->rt6i_dev->flags&IFF_LOOPBACK))
@@ -1585,7 +2085,7 @@ static int rt6_fill_node(struct sk_buff *skb, struct rt6_info *rt,
        rtm->rtm_protocol = rt->rt6i_protocol;
        if (rt->rt6i_flags&RTF_DYNAMIC)
                rtm->rtm_protocol = RTPROT_REDIRECT;
-       else if (rt->rt6i_flags&(RTF_ADDRCONF|RTF_ALLONLINK))
+       else if (rt->rt6i_flags & RTF_ADDRCONF)
                rtm->rtm_protocol = RTPROT_KERNEL;
        else if (rt->rt6i_flags&RTF_DEFAULT)
                rtm->rtm_protocol = RTPROT_RA;
@@ -1594,246 +2094,162 @@ static int rt6_fill_node(struct sk_buff *skb, struct rt6_info *rt,
                rtm->rtm_flags |= RTM_F_CLONED;
 
        if (dst) {
-               RTA_PUT(skb, RTA_DST, 16, dst);
+               NLA_PUT(skb, RTA_DST, 16, dst);
                rtm->rtm_dst_len = 128;
        } else if (rtm->rtm_dst_len)
-               RTA_PUT(skb, RTA_DST, 16, &rt->rt6i_dst.addr);
+               NLA_PUT(skb, RTA_DST, 16, &rt->rt6i_dst.addr);
 #ifdef CONFIG_IPV6_SUBTREES
        if (src) {
-               RTA_PUT(skb, RTA_SRC, 16, src);
+               NLA_PUT(skb, RTA_SRC, 16, src);
                rtm->rtm_src_len = 128;
        } else if (rtm->rtm_src_len)
-               RTA_PUT(skb, RTA_SRC, 16, &rt->rt6i_src.addr);
+               NLA_PUT(skb, RTA_SRC, 16, &rt->rt6i_src.addr);
 #endif
        if (iif)
-               RTA_PUT(skb, RTA_IIF, 4, &iif);
+               NLA_PUT_U32(skb, RTA_IIF, iif);
        else if (dst) {
                struct in6_addr saddr_buf;
                if (ipv6_get_saddr(&rt->u.dst, dst, &saddr_buf) == 0)
-                       RTA_PUT(skb, RTA_PREFSRC, 16, &saddr_buf);
+                       NLA_PUT(skb, RTA_PREFSRC, 16, &saddr_buf);
        }
+
        if (rtnetlink_put_metrics(skb, rt->u.dst.metrics) < 0)
-               goto rtattr_failure;
+               goto nla_put_failure;
+
        if (rt->u.dst.neighbour)
-               RTA_PUT(skb, RTA_GATEWAY, 16, &rt->u.dst.neighbour->primary_key);
+               NLA_PUT(skb, RTA_GATEWAY, 16, &rt->u.dst.neighbour->primary_key);
+
        if (rt->u.dst.dev)
-               RTA_PUT(skb, RTA_OIF, sizeof(int), &rt->rt6i_dev->ifindex);
-       RTA_PUT(skb, RTA_PRIORITY, 4, &rt->rt6i_metric);
-       ci.rta_lastuse = jiffies_to_clock_t(jiffies - rt->u.dst.lastuse);
-       if (rt->rt6i_expires)
-               ci.rta_expires = jiffies_to_clock_t(rt->rt6i_expires - jiffies);
-       else
-               ci.rta_expires = 0;
-       ci.rta_used = rt->u.dst.__use;
-       ci.rta_clntref = atomic_read(&rt->u.dst.__refcnt);
-       ci.rta_error = rt->u.dst.error;
-       ci.rta_id = 0;
-       ci.rta_ts = 0;
-       ci.rta_tsage = 0;
-       RTA_PUT(skb, RTA_CACHEINFO, sizeof(ci), &ci);
-       nlh->nlmsg_len = skb->tail - b;
-       return skb->len;
+               NLA_PUT_U32(skb, RTA_OIF, rt->rt6i_dev->ifindex);
+
+       NLA_PUT_U32(skb, RTA_PRIORITY, rt->rt6i_metric);
 
-nlmsg_failure:
-rtattr_failure:
-       skb_trim(skb, b - skb->data);
-       return -1;
+       expires = rt->rt6i_expires ? rt->rt6i_expires - jiffies : 0;
+       if (rtnl_put_cacheinfo(skb, &rt->u.dst, 0, 0, 0,
+                              expires, rt->u.dst.error) < 0)
+               goto nla_put_failure;
+
+       return nlmsg_end(skb, nlh);
+
+nla_put_failure:
+       return nlmsg_cancel(skb, nlh);
 }
 
-static int rt6_dump_route(struct rt6_info *rt, void *p_arg)
+int rt6_dump_route(struct rt6_info *rt, void *p_arg)
 {
        struct rt6_rtnl_dump_arg *arg = (struct rt6_rtnl_dump_arg *) p_arg;
        int prefix;
 
-       if (arg->cb->nlh->nlmsg_len >= NLMSG_LENGTH(sizeof(struct rtmsg))) {
-               struct rtmsg *rtm = NLMSG_DATA(arg->cb->nlh);
+       if (nlmsg_len(arg->cb->nlh) >= sizeof(struct rtmsg)) {
+               struct rtmsg *rtm = nlmsg_data(arg->cb->nlh);
                prefix = (rtm->rtm_flags & RTM_F_PREFIX) != 0;
        } else
                prefix = 0;
 
        return rt6_fill_node(arg->skb, rt, NULL, NULL, 0, RTM_NEWROUTE,
                     NETLINK_CB(arg->cb->skb).pid, arg->cb->nlh->nlmsg_seq,
-                    NULL, prefix);
+                    prefix, NLM_F_MULTI);
 }
 
-static int fib6_dump_node(struct fib6_walker_t *w)
+int inet6_rtm_getroute(struct sk_buff *in_skb, struct nlmsghdr* nlh, void *arg)
 {
-       int res;
+       struct nlattr *tb[RTA_MAX+1];
        struct rt6_info *rt;
+       struct sk_buff *skb;
+       struct rtmsg *rtm;
+       struct flowi fl;
+       int err, iif = 0;
 
-       for (rt = w->leaf; rt; rt = rt->u.next) {
-               res = rt6_dump_route(rt, w->args);
-               if (res < 0) {
-                       /* Frame is full, suspend walking */
-                       w->leaf = rt;
-                       return 1;
-               }
-               BUG_TRAP(res!=0);
-       }
-       w->leaf = NULL;
-       return 0;
-}
-
-static void fib6_dump_end(struct netlink_callback *cb)
-{
-       struct fib6_walker_t *w = (void*)cb->args[0];
-
-       if (w) {
-               cb->args[0] = 0;
-               fib6_walker_unlink(w);
-               kfree(w);
-       }
-       if (cb->args[1]) {
-               cb->done = (void*)cb->args[1];
-               cb->args[1] = 0;
-       }
-}
-
-static int fib6_dump_done(struct netlink_callback *cb)
-{
-       fib6_dump_end(cb);
-       return cb->done(cb);
-}
-
-int inet6_dump_fib(struct sk_buff *skb, struct netlink_callback *cb)
-{
-       struct rt6_rtnl_dump_arg arg;
-       struct fib6_walker_t *w;
-       int res;
+       err = nlmsg_parse(nlh, sizeof(*rtm), tb, RTA_MAX, rtm_ipv6_policy);
+       if (err < 0)
+               goto errout;
 
-       arg.skb = skb;
-       arg.cb = cb;
+       err = -EINVAL;
+       memset(&fl, 0, sizeof(fl));
 
-       w = (void*)cb->args[0];
-       if (w == NULL) {
-               /* New dump:
-                * 
-                * 1. hook callback destructor.
-                */
-               cb->args[1] = (long)cb->done;
-               cb->done = fib6_dump_done;
+       if (tb[RTA_SRC]) {
+               if (nla_len(tb[RTA_SRC]) < sizeof(struct in6_addr))
+                       goto errout;
 
-               /*
-                * 2. allocate and initialize walker.
-                */
-               w = kmalloc(sizeof(*w), GFP_ATOMIC);
-               if (w == NULL)
-                       return -ENOMEM;
-               RT6_TRACE("dump<%p", w);
-               memset(w, 0, sizeof(*w));
-               w->root = &ip6_routing_table;
-               w->func = fib6_dump_node;
-               w->args = &arg;
-               cb->args[0] = (long)w;
-               read_lock_bh(&rt6_lock);
-               res = fib6_walk(w);
-               read_unlock_bh(&rt6_lock);
-       } else {
-               w->args = &arg;
-               read_lock_bh(&rt6_lock);
-               res = fib6_walk_continue(w);
-               read_unlock_bh(&rt6_lock);
+               ipv6_addr_copy(&fl.fl6_src, nla_data(tb[RTA_SRC]));
        }
-#if RT6_DEBUG >= 3
-       if (res <= 0 && skb->len == 0)
-               RT6_TRACE("%p>dump end\n", w);
-#endif
-       res = res < 0 ? res : skb->len;
-       /* res < 0 is an error. (really, impossible)
-          res == 0 means that dump is complete, but skb still can contain data.
-          res > 0 dump is not complete, but frame is full.
-        */
-       /* Destroy walker, if dump of this table is complete. */
-       if (res <= 0)
-               fib6_dump_end(cb);
-       return res;
-}
-
-int inet6_rtm_getroute(struct sk_buff *in_skb, struct nlmsghdr* nlh, void *arg)
-{
-       struct rtattr **rta = arg;
-       int iif = 0;
-       int err = -ENOBUFS;
-       struct sk_buff *skb;
-       struct flowi fl;
-       struct rt6_info *rt;
 
-       skb = alloc_skb(NLMSG_GOODSIZE, GFP_KERNEL);
-       if (skb == NULL)
-               goto out;
+       if (tb[RTA_DST]) {
+               if (nla_len(tb[RTA_DST]) < sizeof(struct in6_addr))
+                       goto errout;
 
-       /* Reserve room for dummy headers, this skb can pass
-          through good chunk of routing engine.
-        */
-       skb->mac.raw = skb->data;
-       skb_reserve(skb, MAX_HEADER + sizeof(struct ipv6hdr));
+               ipv6_addr_copy(&fl.fl6_dst, nla_data(tb[RTA_DST]));
+       }
 
-       memset(&fl, 0, sizeof(fl));
-       if (rta[RTA_SRC-1])
-               ipv6_addr_copy(&fl.fl6_src,
-                              (struct in6_addr*)RTA_DATA(rta[RTA_SRC-1]));
-       if (rta[RTA_DST-1])
-               ipv6_addr_copy(&fl.fl6_dst,
-                              (struct in6_addr*)RTA_DATA(rta[RTA_DST-1]));
+       if (tb[RTA_IIF])
+               iif = nla_get_u32(tb[RTA_IIF]);
 
-       if (rta[RTA_IIF-1])
-               memcpy(&iif, RTA_DATA(rta[RTA_IIF-1]), sizeof(int));
+       if (tb[RTA_OIF])
+               fl.oif = nla_get_u32(tb[RTA_OIF]);
 
        if (iif) {
                struct net_device *dev;
                dev = __dev_get_by_index(iif);
                if (!dev) {
                        err = -ENODEV;
-                       goto out_free;
+                       goto errout;
                }
        }
 
-       fl.oif = 0;
-       if (rta[RTA_OIF-1])
-               memcpy(&fl.oif, RTA_DATA(rta[RTA_OIF-1]), sizeof(int));
+       skb = alloc_skb(NLMSG_GOODSIZE, GFP_KERNEL);
+       if (skb == NULL) {
+               err = -ENOBUFS;
+               goto errout;
+       }
 
-       rt = (struct rt6_info*)ip6_route_output(NULL, &fl);
+       /* Reserve room for dummy headers, this skb can pass
+          through good chunk of routing engine.
+        */
+       skb->mac.raw = skb->data;
+       skb_reserve(skb, MAX_HEADER + sizeof(struct ipv6hdr));
 
+       rt = (struct rt6_info*) ip6_route_output(NULL, &fl);
        skb->dst = &rt->u.dst;
 
-       NETLINK_CB(skb).dst_pid = NETLINK_CB(in_skb).pid;
-       err = rt6_fill_node(skb, rt, 
-                           &fl.fl6_dst, &fl.fl6_src,
-                           iif,
+       err = rt6_fill_node(skb, rt, &fl.fl6_dst, &fl.fl6_src, iif,
                            RTM_NEWROUTE, NETLINK_CB(in_skb).pid,
-                           nlh->nlmsg_seq, nlh, 0);
+                           nlh->nlmsg_seq, 0, 0);
        if (err < 0) {
-               err = -EMSGSIZE;
-               goto out_free;
+               kfree_skb(skb);
+               goto errout;
        }
 
-       err = netlink_unicast(rtnl, skb, NETLINK_CB(in_skb).pid, MSG_DONTWAIT);
-       if (err > 0)
-               err = 0;
-out:
+       err = rtnl_unicast(skb, NETLINK_CB(in_skb).pid);
+errout:
        return err;
-out_free:
-       kfree_skb(skb);
-       goto out;       
 }
 
-void inet6_rt_notify(int event, struct rt6_info *rt, struct nlmsghdr *nlh)
+void inet6_rt_notify(int event, struct rt6_info *rt, struct nl_info *info)
 {
        struct sk_buff *skb;
-       int size = NLMSG_SPACE(sizeof(struct rtmsg)+256);
+       u32 pid = 0, seq = 0;
+       struct nlmsghdr *nlh = NULL;
+       int err = -ENOBUFS;
 
-       skb = alloc_skb(size, gfp_any());
-       if (!skb) {
-               netlink_set_err(rtnl, 0, RTMGRP_IPV6_ROUTE, ENOBUFS);
-               return;
+       if (info) {
+               pid = info->pid;
+               nlh = info->nlh;
+               if (nlh)
+                       seq = nlh->nlmsg_seq;
        }
-       if (rt6_fill_node(skb, rt, NULL, NULL, 0, event, 0, 0, nlh, 0) < 0) {
-               kfree_skb(skb);
-               netlink_set_err(rtnl, 0, RTMGRP_IPV6_ROUTE, EINVAL);
-               return;
-       }
-       NETLINK_CB(skb).dst_groups = RTMGRP_IPV6_ROUTE;
-       netlink_broadcast(rtnl, skb, 0, RTMGRP_IPV6_ROUTE, gfp_any());
+
+       skb = nlmsg_new(rt6_nlmsg_size(), gfp_any());
+       if (skb == NULL)
+               goto errout;
+
+       err = rt6_fill_node(skb, rt, NULL, NULL, 0, event, pid, seq, 0, 0);
+       /* failure implies BUG in rt6_nlmsg_size() */
+       BUG_ON(err < 0);
+
+       err = rtnl_notify(skb, pid, RTNLGRP_IPV6_ROUTE, nlh, gfp_any());
+errout:
+       if (err < 0)
+               rtnl_set_sk_err(RTNLGRP_IPV6_ROUTE, err);
 }
 
 /*
@@ -1856,7 +2272,6 @@ struct rt6_proc_arg
 static int rt6_info_route(struct rt6_info *rt, void *p_arg)
 {
        struct rt6_proc_arg *arg = (struct rt6_proc_arg *) p_arg;
-       int i;
 
        if (arg->skip < arg->offset / RT6_INFO_LEN) {
                arg->skip++;
@@ -1866,38 +2281,28 @@ static int rt6_info_route(struct rt6_info *rt, void *p_arg)
        if (arg->len >= arg->length)
                return 0;
 
-       for (i=0; i<16; i++) {
-               sprintf(arg->buffer + arg->len, "%02x",
-                       rt->rt6i_dst.addr.s6_addr[i]);
-               arg->len += 2;
-       }
-       arg->len += sprintf(arg->buffer + arg->len, " %02x ",
+       arg->len += sprintf(arg->buffer + arg->len,
+                           NIP6_SEQFMT " %02x ",
+                           NIP6(rt->rt6i_dst.addr),
                            rt->rt6i_dst.plen);
 
 #ifdef CONFIG_IPV6_SUBTREES
-       for (i=0; i<16; i++) {
-               sprintf(arg->buffer + arg->len, "%02x",
-                       rt->rt6i_src.addr.s6_addr[i]);
-               arg->len += 2;
-       }
-       arg->len += sprintf(arg->buffer + arg->len, " %02x ",
+       arg->len += sprintf(arg->buffer + arg->len,
+                           NIP6_SEQFMT " %02x ",
+                           NIP6(rt->rt6i_src.addr),
                            rt->rt6i_src.plen);
 #else
-       sprintf(arg->buffer + arg->len,
-               "00000000000000000000000000000000 00 ");
-       arg->len += 36;
+       arg->len += sprintf(arg->buffer + arg->len,
+                           "00000000000000000000000000000000 00 ");
 #endif
 
        if (rt->rt6i_nexthop) {
-               for (i=0; i<16; i++) {
-                       sprintf(arg->buffer + arg->len, "%02x",
-                               rt->rt6i_nexthop->primary_key[i]);
-                       arg->len += 2;
-               }
+               arg->len += sprintf(arg->buffer + arg->len,
+                                   NIP6_SEQFMT,
+                                   NIP6(*((struct in6_addr *)rt->rt6i_nexthop->primary_key)));
        } else {
-               sprintf(arg->buffer + arg->len,
-                       "00000000000000000000000000000000");
-               arg->len += 32;
+               arg->len += sprintf(arg->buffer + arg->len,
+                                   "00000000000000000000000000000000");
        }
        arg->len += sprintf(arg->buffer + arg->len,
                            " %08x %08x %08x %08x %8s\n",
@@ -1909,16 +2314,13 @@ static int rt6_info_route(struct rt6_info *rt, void *p_arg)
 
 static int rt6_proc_info(char *buffer, char **start, off_t offset, int length)
 {
-       struct rt6_proc_arg arg;
-       arg.buffer = buffer;
-       arg.offset = offset;
-       arg.length = length;
-       arg.skip = 0;
-       arg.len = 0;
+       struct rt6_proc_arg arg = {
+               .buffer = buffer,
+               .offset = offset,
+               .length = length,
+       };
 
-       read_lock_bh(&rt6_lock);
-       fib6_clean_tree(&ip6_routing_table, rt6_info_route, 0, &arg);
-       read_unlock_bh(&rt6_lock);
+       fib6_clean_all(rt6_info_route, 0, &arg);
 
        *start = buffer;
        if (offset)
@@ -1934,8 +2336,6 @@ static int rt6_proc_info(char *buffer, char **start, off_t offset, int length)
        return arg.len;
 }
 
-extern struct rt6_statistics rt6_stats;
-
 static int rt6_stats_seq_show(struct seq_file *seq, void *v)
 {
        seq_printf(seq, "%04x %04x %04x %04x %04x %04x %04x\n",
@@ -1972,9 +2372,7 @@ int ipv6_sysctl_rtcache_flush(ctl_table *ctl, int write, struct file * filp,
 {
        if (write) {
                proc_dointvec(ctl, write, filp, buffer, lenp, ppos);
-               if (flush_delay < 0)
-                       flush_delay = 0;
-               fib6_run_gc((unsigned long)flush_delay);
+               fib6_run_gc(flush_delay <= 0 ? ~0UL : (unsigned long)flush_delay);
                return 0;
        } else
                return -EINVAL;
@@ -1986,7 +2384,7 @@ ctl_table ipv6_route_table[] = {
                .procname       =       "flush",
                .data           =       &flush_delay,
                .maxlen         =       sizeof(int),
-               .mode           =       0644,
+               .mode           =       0200,
                .proc_handler   =       &ipv6_sysctl_rtcache_flush
        },
        {
@@ -2059,6 +2457,15 @@ ctl_table ipv6_route_table[] = {
                .proc_handler   =       &proc_dointvec_jiffies,
                .strategy       =       &sysctl_jiffies,
        },
+       {
+               .ctl_name       =       NET_IPV6_ROUTE_GC_MIN_INTERVAL_MS,
+               .procname       =       "gc_min_interval_ms",
+               .data           =       &ip6_rt_gc_min_interval,
+               .maxlen         =       sizeof(int),
+               .mode           =       0644,
+               .proc_handler   =       &proc_dointvec_ms_jiffies,
+               .strategy       =       &sysctl_ms_jiffies,
+       },
        { .ctl_name = 0 }
 };
 
@@ -2068,13 +2475,9 @@ void __init ip6_route_init(void)
 {
        struct proc_dir_entry *p;
 
-       ip6_dst_ops.kmem_cachep = kmem_cache_create("ip6_dst_cache",
-                                                    sizeof(struct rt6_info),
-                                                    0, SLAB_HWCACHE_ALIGN,
-                                                    NULL, NULL);
-       if (!ip6_dst_ops.kmem_cachep)
-               panic("cannot create ip6_dst_cache");
-
+       ip6_dst_ops.kmem_cachep =
+               kmem_cache_create("ip6_dst_cache", sizeof(struct rt6_info), 0,
+                                 SLAB_HWCACHE_ALIGN|SLAB_PANIC, NULL, NULL);
        fib6_init();
 #ifdef         CONFIG_PROC_FS
        p = proc_net_create("ipv6_route", 0, rt6_proc_info);
@@ -2086,10 +2489,16 @@ void __init ip6_route_init(void)
 #ifdef CONFIG_XFRM
        xfrm6_init();
 #endif
+#ifdef CONFIG_IPV6_MULTIPLE_TABLES
+       fib6_rules_init();
+#endif
 }
 
-void __exit ip6_route_cleanup(void)
+void ip6_route_cleanup(void)
 {
+#ifdef CONFIG_IPV6_MULTIPLE_TABLES
+       fib6_rules_cleanup();
+#endif
 #ifdef CONFIG_PROC_FS
        proc_net_remove("ipv6_route");
        proc_net_remove("rt6_stats");