ipip: add module alias for tunl0 tunnel device
[linux-2.6.git] / net / ipv4 / fib_semantics.c
index a9a44b4..12d3dc3 100644 (file)
@@ -5,8 +5,6 @@
  *
  *             IPv4 Forwarding Information Base: semantics.
  *
- * Version:    $Id: fib_semantics.c,v 1.19 2002/01/12 07:54:56 davem Exp $
- *
  * Authors:    Alexey Kuznetsov, <kuznet@ms2.inr.ac.ru>
  *
  *             This program is free software; you can redistribute it and/or
@@ -15,7 +13,6 @@
  *             2 of the License, or (at your option) any later version.
  */
 
-#include <linux/config.h>
 #include <asm/uaccess.h>
 #include <asm/system.h>
 #include <linux/bitops.h>
 #include <linux/errno.h>
 #include <linux/in.h>
 #include <linux/inet.h>
+#include <linux/inetdevice.h>
 #include <linux/netdevice.h>
 #include <linux/if_arp.h>
 #include <linux/proc_fs.h>
 #include <linux/skbuff.h>
-#include <linux/netlink.h>
 #include <linux/init.h>
+#include <linux/slab.h>
 
+#include <net/arp.h>
 #include <net/ip.h>
 #include <net/protocol.h>
 #include <net/route.h>
 #include <net/tcp.h>
 #include <net/sock.h>
 #include <net/ip_fib.h>
-#include <net/ip_mp_alg.h>
+#include <net/netlink.h>
+#include <net/nexthop.h>
 
 #include "fib_lookup.h"
 
-#define FSprintk(a...)
-
-static DEFINE_RWLOCK(fib_info_lock);
+static DEFINE_SPINLOCK(fib_info_lock);
 static struct hlist_head *fib_info_hash;
 static struct hlist_head *fib_info_laddrhash;
 static unsigned int fib_hash_size;
@@ -62,119 +60,136 @@ static struct hlist_head fib_info_devhash[DEVINDEX_HASHSIZE];
 
 static DEFINE_SPINLOCK(fib_multipath_lock);
 
-#define for_nexthops(fi) { int nhsel; const struct fib_nh * nh; \
-for (nhsel=0, nh = (fi)->fib_nh; nhsel < (fi)->fib_nhs; nh++, nhsel++)
+#define for_nexthops(fi) {                                             \
+       int nhsel; const struct fib_nh *nh;                             \
+       for (nhsel = 0, nh = (fi)->fib_nh;                              \
+            nhsel < (fi)->fib_nhs;                                     \
+            nh++, nhsel++)
 
-#define change_nexthops(fi) { int nhsel; struct fib_nh * nh; \
-for (nhsel=0, nh = (struct fib_nh*)((fi)->fib_nh); nhsel < (fi)->fib_nhs; nh++, nhsel++)
+#define change_nexthops(fi) {                                          \
+       int nhsel; struct fib_nh *nexthop_nh;                           \
+       for (nhsel = 0, nexthop_nh = (struct fib_nh *)((fi)->fib_nh);   \
+            nhsel < (fi)->fib_nhs;                                     \
+            nexthop_nh++, nhsel++)
 
 #else /* CONFIG_IP_ROUTE_MULTIPATH */
 
 /* Hope, that gcc will optimize it to get rid of dummy loop */
 
-#define for_nexthops(fi) { int nhsel=0; const struct fib_nh * nh = (fi)->fib_nh; \
-for (nhsel=0; nhsel < 1; nhsel++)
+#define for_nexthops(fi) {                                             \
+       int nhsel; const struct fib_nh *nh = (fi)->fib_nh;              \
+       for (nhsel = 0; nhsel < 1; nhsel++)
 
-#define change_nexthops(fi) { int nhsel=0; struct fib_nh * nh = (struct fib_nh*)((fi)->fib_nh); \
-for (nhsel=0; nhsel < 1; nhsel++)
+#define change_nexthops(fi) {                                          \
+       int nhsel;                                                      \
+       struct fib_nh *nexthop_nh = (struct fib_nh *)((fi)->fib_nh);    \
+       for (nhsel = 0; nhsel < 1; nhsel++)
 
 #endif /* CONFIG_IP_ROUTE_MULTIPATH */
 
 #define endfor_nexthops(fi) }
 
 
-static struct 
+static const struct
 {
        int     error;
        u8      scope;
-} fib_props[RTA_MAX + 1] = {
-        {
+} fib_props[RTN_MAX + 1] = {
+       [RTN_UNSPEC] = {
                .error  = 0,
                .scope  = RT_SCOPE_NOWHERE,
-       },      /* RTN_UNSPEC */
-       {
+       },
+       [RTN_UNICAST] = {
                .error  = 0,
                .scope  = RT_SCOPE_UNIVERSE,
-       },      /* RTN_UNICAST */
-       {
+       },
+       [RTN_LOCAL] = {
                .error  = 0,
                .scope  = RT_SCOPE_HOST,
-       },      /* RTN_LOCAL */
-       {
+       },
+       [RTN_BROADCAST] = {
                .error  = 0,
                .scope  = RT_SCOPE_LINK,
-       },      /* RTN_BROADCAST */
-       {
+       },
+       [RTN_ANYCAST] = {
                .error  = 0,
                .scope  = RT_SCOPE_LINK,
-       },      /* RTN_ANYCAST */
-       {
+       },
+       [RTN_MULTICAST] = {
                .error  = 0,
                .scope  = RT_SCOPE_UNIVERSE,
-       },      /* RTN_MULTICAST */
-       {
+       },
+       [RTN_BLACKHOLE] = {
                .error  = -EINVAL,
                .scope  = RT_SCOPE_UNIVERSE,
-       },      /* RTN_BLACKHOLE */
-       {
+       },
+       [RTN_UNREACHABLE] = {
                .error  = -EHOSTUNREACH,
                .scope  = RT_SCOPE_UNIVERSE,
-       },      /* RTN_UNREACHABLE */
-       {
+       },
+       [RTN_PROHIBIT] = {
                .error  = -EACCES,
                .scope  = RT_SCOPE_UNIVERSE,
-       },      /* RTN_PROHIBIT */
-       {
+       },
+       [RTN_THROW] = {
                .error  = -EAGAIN,
                .scope  = RT_SCOPE_UNIVERSE,
-       },      /* RTN_THROW */
-       {
+       },
+       [RTN_NAT] = {
                .error  = -EINVAL,
                .scope  = RT_SCOPE_NOWHERE,
-       },      /* RTN_NAT */
-       {
+       },
+       [RTN_XRESOLVE] = {
                .error  = -EINVAL,
                .scope  = RT_SCOPE_NOWHERE,
-       },      /* RTN_XRESOLVE */
+       },
 };
 
 
 /* Release a nexthop info record */
 
+static void free_fib_info_rcu(struct rcu_head *head)
+{
+       struct fib_info *fi = container_of(head, struct fib_info, rcu);
+
+       kfree(fi);
+}
+
 void free_fib_info(struct fib_info *fi)
 {
        if (fi->fib_dead == 0) {
-               printk("Freeing alive fib_info %p\n", fi);
+               pr_warning("Freeing alive fib_info %p\n", fi);
                return;
        }
        change_nexthops(fi) {
-               if (nh->nh_dev)
-                       dev_put(nh->nh_dev);
-               nh->nh_dev = NULL;
+               if (nexthop_nh->nh_dev)
+                       dev_put(nexthop_nh->nh_dev);
+               nexthop_nh->nh_dev = NULL;
        } endfor_nexthops(fi);
        fib_info_cnt--;
-       kfree(fi);
+       release_net(fi->fib_net);
+       call_rcu(&fi->rcu, free_fib_info_rcu);
 }
 
 void fib_release_info(struct fib_info *fi)
 {
-       write_lock(&fib_info_lock);
+       spin_lock_bh(&fib_info_lock);
        if (fi && --fi->fib_treeref == 0) {
                hlist_del(&fi->fib_hash);
                if (fi->fib_prefsrc)
                        hlist_del(&fi->fib_lhash);
                change_nexthops(fi) {
-                       if (!nh->nh_dev)
+                       if (!nexthop_nh->nh_dev)
                                continue;
-                       hlist_del(&nh->nh_hash);
+                       hlist_del(&nexthop_nh->nh_hash);
                } endfor_nexthops(fi)
                fi->fib_dead = 1;
                fib_info_put(fi);
        }
-       write_unlock(&fib_info_lock);
+       spin_unlock_bh(&fib_info_lock);
 }
 
-static __inline__ int nh_comp(const struct fib_info *fi, const struct fib_info *ofi)
+static inline int nh_comp(const struct fib_info *fi, const struct fib_info *ofi)
 {
        const struct fib_nh *onh = ofi->fib_nh;
 
@@ -188,21 +203,33 @@ static __inline__ int nh_comp(const struct fib_info *fi, const struct fib_info *
 #ifdef CONFIG_NET_CLS_ROUTE
                    nh->nh_tclassid != onh->nh_tclassid ||
 #endif
-                   ((nh->nh_flags^onh->nh_flags)&~RTNH_F_DEAD))
+                   ((nh->nh_flags ^ onh->nh_flags) & ~RTNH_F_DEAD))
                        return -1;
                onh++;
        } endfor_nexthops(fi);
        return 0;
 }
 
+static inline unsigned int fib_devindex_hashfn(unsigned int val)
+{
+       unsigned int mask = DEVINDEX_HASHSIZE - 1;
+
+       return (val ^
+               (val >> DEVINDEX_HASHBITS) ^
+               (val >> (DEVINDEX_HASHBITS * 2))) & mask;
+}
+
 static inline unsigned int fib_info_hashfn(const struct fib_info *fi)
 {
        unsigned int mask = (fib_hash_size - 1);
        unsigned int val = fi->fib_nhs;
 
        val ^= fi->fib_protocol;
-       val ^= fi->fib_prefsrc;
+       val ^= (__force u32)fi->fib_prefsrc;
        val ^= fi->fib_priority;
+       for_nexthops(fi) {
+               val ^= fib_devindex_hashfn(nh->nh_oif);
+       } endfor_nexthops(fi)
 
        return (val ^ (val >> 7) ^ (val >> 12)) & mask;
 }
@@ -218,6 +245,8 @@ static struct fib_info *fib_find_info(const struct fib_info *nfi)
        head = &fib_info_hash[hash];
 
        hlist_for_each_entry(fi, node, head, fib_hash) {
+               if (!net_eq(fi->fib_net, nfi->fib_net))
+                       continue;
                if (fi->fib_nhs != nfi->fib_nhs)
                        continue;
                if (nfi->fib_protocol == fi->fib_protocol &&
@@ -225,7 +254,7 @@ static struct fib_info *fib_find_info(const struct fib_info *nfi)
                    nfi->fib_priority == fi->fib_priority &&
                    memcmp(nfi->fib_metrics, fi->fib_metrics,
                           sizeof(fi->fib_metrics)) == 0 &&
-                   ((nfi->fib_flags^fi->fib_flags)&~RTNH_F_DEAD) == 0 &&
+                   ((nfi->fib_flags ^ fi->fib_flags) & ~RTNH_F_DEAD) == 0 &&
                    (nfi->fib_nhs == 0 || nh_comp(fi, nfi) == 0))
                        return fi;
        }
@@ -233,69 +262,88 @@ static struct fib_info *fib_find_info(const struct fib_info *nfi)
        return NULL;
 }
 
-static inline unsigned int fib_devindex_hashfn(unsigned int val)
-{
-       unsigned int mask = DEVINDEX_HASHSIZE - 1;
-
-       return (val ^
-               (val >> DEVINDEX_HASHBITS) ^
-               (val >> (DEVINDEX_HASHBITS * 2))) & mask;
-}
-
 /* Check, that the gateway is already configured.
-   Used only by redirect accept routine.
+ * Used only by redirect accept routine.
  */
-
-int ip_fib_check_default(u32 gw, struct net_device *dev)
+int ip_fib_check_default(__be32 gw, struct net_device *dev)
 {
        struct hlist_head *head;
        struct hlist_node *node;
        struct fib_nh *nh;
        unsigned int hash;
 
-       read_lock(&fib_info_lock);
+       spin_lock(&fib_info_lock);
 
        hash = fib_devindex_hashfn(dev->ifindex);
        head = &fib_info_devhash[hash];
        hlist_for_each_entry(nh, node, head, nh_hash) {
                if (nh->nh_dev == dev &&
                    nh->nh_gw == gw &&
-                   !(nh->nh_flags&RTNH_F_DEAD)) {
-                       read_unlock(&fib_info_lock);
+                   !(nh->nh_flags & RTNH_F_DEAD)) {
+                       spin_unlock(&fib_info_lock);
                        return 0;
                }
        }
 
-       read_unlock(&fib_info_lock);
+       spin_unlock(&fib_info_lock);
 
        return -1;
 }
 
-void rtmsg_fib(int event, u32 key, struct fib_alias *fa,
-              int z, int tb_id,
-              struct nlmsghdr *n, struct netlink_skb_parms *req)
+static inline size_t fib_nlmsg_size(struct fib_info *fi)
 {
-       struct sk_buff *skb;
-       u32 pid = req ? req->pid : 0;
-       int size = NLMSG_SPACE(sizeof(struct rtmsg)+256);
+       size_t payload = NLMSG_ALIGN(sizeof(struct rtmsg))
+                        + nla_total_size(4) /* RTA_TABLE */
+                        + nla_total_size(4) /* RTA_DST */
+                        + nla_total_size(4) /* RTA_PRIORITY */
+                        + nla_total_size(4); /* RTA_PREFSRC */
 
-       skb = alloc_skb(size, GFP_KERNEL);
-       if (!skb)
-               return;
+       /* space for nested metrics */
+       payload += nla_total_size((RTAX_MAX * nla_total_size(4)));
 
-       if (fib_dump_info(skb, pid, n->nlmsg_seq, event, tb_id,
-                         fa->fa_type, fa->fa_scope, &key, z,
-                         fa->fa_tos,
-                         fa->fa_info, 0) < 0) {
+       if (fi->fib_nhs) {
+               /* Also handles the special case fib_nhs == 1 */
+
+               /* each nexthop is packed in an attribute */
+               size_t nhsize = nla_total_size(sizeof(struct rtnexthop));
+
+               /* may contain flow and gateway attribute */
+               nhsize += 2 * nla_total_size(4);
+
+               /* all nexthops are packed in a nested attribute */
+               payload += nla_total_size(fi->fib_nhs * nhsize);
+       }
+
+       return payload;
+}
+
+void rtmsg_fib(int event, __be32 key, struct fib_alias *fa,
+              int dst_len, u32 tb_id, struct nl_info *info,
+              unsigned int nlm_flags)
+{
+       struct sk_buff *skb;
+       u32 seq = info->nlh ? info->nlh->nlmsg_seq : 0;
+       int err = -ENOBUFS;
+
+       skb = nlmsg_new(fib_nlmsg_size(fa->fa_info), GFP_KERNEL);
+       if (skb == NULL)
+               goto errout;
+
+       err = fib_dump_info(skb, info->pid, seq, event, tb_id,
+                           fa->fa_type, fa->fa_scope, key, dst_len,
+                           fa->fa_tos, fa->fa_info, nlm_flags);
+       if (err < 0) {
+               /* -EMSGSIZE implies BUG in fib_nlmsg_size() */
+               WARN_ON(err == -EMSGSIZE);
                kfree_skb(skb);
-               return;
+               goto errout;
        }
-       NETLINK_CB(skb).dst_groups = RTMGRP_IPV4_ROUTE;
-       if (n->nlmsg_flags&NLM_F_ECHO)
-               atomic_inc(&skb->users);
-       netlink_broadcast(rtnl, skb, pid, RTMGRP_IPV4_ROUTE, GFP_KERNEL);
-       if (n->nlmsg_flags&NLM_F_ECHO)
-               netlink_unicast(rtnl, skb, pid, MSG_DONTWAIT);
+       rtnl_notify(skb, info->nl_net, info->pid, RTNLGRP_IPV4_ROUTE,
+                   info->nlh, GFP_KERNEL);
+       return;
+errout:
+       if (err < 0)
+               rtnl_set_sk_err(info->nl_net, RTNLGRP_IPV4_ROUTE, err);
 }
 
 /* Return the first fib alias matching TOS with
@@ -317,7 +365,7 @@ struct fib_alias *fib_find_alias(struct list_head *fah, u8 tos, u32 prio)
 }
 
 int fib_detect_death(struct fib_info *fi, int order,
-                    struct fib_info **last_resort, int *last_idx, int *dflt)
+                    struct fib_info **last_resort, int *last_idx, int dflt)
 {
        struct neighbour *n;
        int state = NUD_NONE;
@@ -327,12 +375,12 @@ int fib_detect_death(struct fib_info *fi, int order,
                state = n->nud_state;
                neigh_release(n);
        }
-       if (state==NUD_REACHABLE)
+       if (state == NUD_REACHABLE)
                return 0;
-       if ((state&NUD_VALID) && order != *dflt)
+       if ((state & NUD_VALID) && order != dflt)
                return 0;
-       if ((state&NUD_VALID) ||
-           (*last_idx<0 && order > *dflt)) {
+       if ((state & NUD_VALID) ||
+           (*last_idx < 0 && order > dflt)) {
                *last_resort = fi;
                *last_idx = order;
        }
@@ -341,102 +389,101 @@ int fib_detect_death(struct fib_info *fi, int order,
 
 #ifdef CONFIG_IP_ROUTE_MULTIPATH
 
-static u32 fib_get_attr32(struct rtattr *attr, int attrlen, int type)
-{
-       while (RTA_OK(attr,attrlen)) {
-               if (attr->rta_type == type)
-                       return *(u32*)RTA_DATA(attr);
-               attr = RTA_NEXT(attr, attrlen);
-       }
-       return 0;
-}
-
-static int
-fib_count_nexthops(struct rtattr *rta)
+static int fib_count_nexthops(struct rtnexthop *rtnh, int remaining)
 {
        int nhs = 0;
-       struct rtnexthop *nhp = RTA_DATA(rta);
-       int nhlen = RTA_PAYLOAD(rta);
 
-       while (nhlen >= (int)sizeof(struct rtnexthop)) {
-               if ((nhlen -= nhp->rtnh_len) < 0)
-                       return 0;
+       while (rtnh_ok(rtnh, remaining)) {
                nhs++;
-               nhp = RTNH_NEXT(nhp);
-       };
-       return nhs;
+               rtnh = rtnh_next(rtnh, &remaining);
+       }
+
+       /* leftover implies invalid nexthop configuration, discard it */
+       return remaining > 0 ? 0 : nhs;
 }
 
-static int
-fib_get_nhs(struct fib_info *fi, const struct rtattr *rta, const struct rtmsg *r)
+static int fib_get_nhs(struct fib_info *fi, struct rtnexthop *rtnh,
+                      int remaining, struct fib_config *cfg)
 {
-       struct rtnexthop *nhp = RTA_DATA(rta);
-       int nhlen = RTA_PAYLOAD(rta);
-
        change_nexthops(fi) {
-               int attrlen = nhlen - sizeof(struct rtnexthop);
-               if (attrlen < 0 || (nhlen -= nhp->rtnh_len) < 0)
+               int attrlen;
+
+               if (!rtnh_ok(rtnh, remaining))
                        return -EINVAL;
-               nh->nh_flags = (r->rtm_flags&~0xFF) | nhp->rtnh_flags;
-               nh->nh_oif = nhp->rtnh_ifindex;
-               nh->nh_weight = nhp->rtnh_hops + 1;
-               if (attrlen) {
-                       nh->nh_gw = fib_get_attr32(RTNH_DATA(nhp), attrlen, RTA_GATEWAY);
+
+               nexthop_nh->nh_flags =
+                       (cfg->fc_flags & ~0xFF) | rtnh->rtnh_flags;
+               nexthop_nh->nh_oif = rtnh->rtnh_ifindex;
+               nexthop_nh->nh_weight = rtnh->rtnh_hops + 1;
+
+               attrlen = rtnh_attrlen(rtnh);
+               if (attrlen > 0) {
+                       struct nlattr *nla, *attrs = rtnh_attrs(rtnh);
+
+                       nla = nla_find(attrs, attrlen, RTA_GATEWAY);
+                       nexthop_nh->nh_gw = nla ? nla_get_be32(nla) : 0;
 #ifdef CONFIG_NET_CLS_ROUTE
-                       nh->nh_tclassid = fib_get_attr32(RTNH_DATA(nhp), attrlen, RTA_FLOW);
+                       nla = nla_find(attrs, attrlen, RTA_FLOW);
+                       nexthop_nh->nh_tclassid = nla ? nla_get_u32(nla) : 0;
 #endif
                }
-               nhp = RTNH_NEXT(nhp);
+
+               rtnh = rtnh_next(rtnh, &remaining);
        } endfor_nexthops(fi);
+
        return 0;
 }
 
 #endif
 
-int fib_nh_match(struct rtmsg *r, struct nlmsghdr *nlh, struct kern_rta *rta,
-                struct fib_info *fi)
+int fib_nh_match(struct fib_config *cfg, struct fib_info *fi)
 {
 #ifdef CONFIG_IP_ROUTE_MULTIPATH
-       struct rtnexthop *nhp;
-       int nhlen;
+       struct rtnexthop *rtnh;
+       int remaining;
 #endif
 
-       if (rta->rta_priority &&
-           *rta->rta_priority != fi->fib_priority)
+       if (cfg->fc_priority && cfg->fc_priority != fi->fib_priority)
                return 1;
 
-       if (rta->rta_oif || rta->rta_gw) {
-               if ((!rta->rta_oif || *rta->rta_oif == fi->fib_nh->nh_oif) &&
-                   (!rta->rta_gw  || memcmp(rta->rta_gw, &fi->fib_nh->nh_gw, 4) == 0))
+       if (cfg->fc_oif || cfg->fc_gw) {
+               if ((!cfg->fc_oif || cfg->fc_oif == fi->fib_nh->nh_oif) &&
+                   (!cfg->fc_gw  || cfg->fc_gw == fi->fib_nh->nh_gw))
                        return 0;
                return 1;
        }
 
 #ifdef CONFIG_IP_ROUTE_MULTIPATH
-       if (rta->rta_mp == NULL)
+       if (cfg->fc_mp == NULL)
                return 0;
-       nhp = RTA_DATA(rta->rta_mp);
-       nhlen = RTA_PAYLOAD(rta->rta_mp);
-       
+
+       rtnh = cfg->fc_mp;
+       remaining = cfg->fc_mp_len;
+
        for_nexthops(fi) {
-               int attrlen = nhlen - sizeof(struct rtnexthop);
-               u32 gw;
+               int attrlen;
 
-               if (attrlen < 0 || (nhlen -= nhp->rtnh_len) < 0)
+               if (!rtnh_ok(rtnh, remaining))
                        return -EINVAL;
-               if (nhp->rtnh_ifindex && nhp->rtnh_ifindex != nh->nh_oif)
+
+               if (rtnh->rtnh_ifindex && rtnh->rtnh_ifindex != nh->nh_oif)
                        return 1;
-               if (attrlen) {
-                       gw = fib_get_attr32(RTNH_DATA(nhp), attrlen, RTA_GATEWAY);
-                       if (gw && gw != nh->nh_gw)
+
+               attrlen = rtnh_attrlen(rtnh);
+               if (attrlen < 0) {
+                       struct nlattr *nla, *attrs = rtnh_attrs(rtnh);
+
+                       nla = nla_find(attrs, attrlen, RTA_GATEWAY);
+                       if (nla && nla_get_be32(nla) != nh->nh_gw)
                                return 1;
 #ifdef CONFIG_NET_CLS_ROUTE
-                       gw = fib_get_attr32(RTNH_DATA(nhp), attrlen, RTA_FLOW);
-                       if (gw && gw != nh->nh_tclassid)
+                       nla = nla_find(attrs, attrlen, RTA_FLOW);
+                       if (nla && nla_get_u32(nla) != nh->nh_tclassid)
                                return 1;
 #endif
                }
-               nhp = RTNH_NEXT(nhp);
+
+               rtnh = rtnh_next(rtnh, &remaining);
        } endfor_nexthops(fi);
 #endif
        return 0;
@@ -444,138 +491,143 @@ int fib_nh_match(struct rtmsg *r, struct nlmsghdr *nlh, struct kern_rta *rta,
 
 
 /*
-   Picture
-   -------
-
-   Semantics of nexthop is very messy by historical reasons.
-   We have to take into account, that:
-   a) gateway can be actually local interface address,
-      so that gatewayed route is direct.
-   b) gateway must be on-link address, possibly
-      described not by an ifaddr, but also by a direct route.
-   c) If both gateway and interface are specified, they should not
-      contradict.
-   d) If we use tunnel routes, gateway could be not on-link.
-
-   Attempt to reconcile all of these (alas, self-contradictory) conditions
-   results in pretty ugly and hairy code with obscure logic.
-
-   I chose to generalized it instead, so that the size
-   of code does not increase practically, but it becomes
-   much more general.
-   Every prefix is assigned a "scope" value: "host" is local address,
-   "link" is direct route,
-   [ ... "site" ... "interior" ... ]
-   and "universe" is true gateway route with global meaning.
-
-   Every prefix refers to a set of "nexthop"s (gw, oif),
-   where gw must have narrower scope. This recursion stops
-   when gw has LOCAL scope or if "nexthop" is declared ONLINK,
-   which means that gw is forced to be on link.
-
-   Code is still hairy, but now it is apparently logically
-   consistent and very flexible. F.e. as by-product it allows
-   to co-exists in peace independent exterior and interior
-   routing processes.
-
-   Normally it looks as following.
-
-   {universe prefix}  -> (gw, oif) [scope link]
-                          |
-                         |-> {link prefix} -> (gw, oif) [scope local]
-                                               |
-                                               |-> {local prefix} (terminal node)
+ * Picture
+ * -------
+ *
+ * Semantics of nexthop is very messy by historical reasons.
+ * We have to take into account, that:
+ * a) gateway can be actually local interface address,
+ *    so that gatewayed route is direct.
+ * b) gateway must be on-link address, possibly
+ *    described not by an ifaddr, but also by a direct route.
+ * c) If both gateway and interface are specified, they should not
+ *    contradict.
+ * d) If we use tunnel routes, gateway could be not on-link.
+ *
+ * Attempt to reconcile all of these (alas, self-contradictory) conditions
+ * results in pretty ugly and hairy code with obscure logic.
+ *
+ * I chose to generalized it instead, so that the size
+ * of code does not increase practically, but it becomes
+ * much more general.
+ * Every prefix is assigned a "scope" value: "host" is local address,
+ * "link" is direct route,
+ * [ ... "site" ... "interior" ... ]
+ * and "universe" is true gateway route with global meaning.
+ *
+ * Every prefix refers to a set of "nexthop"s (gw, oif),
+ * where gw must have narrower scope. This recursion stops
+ * when gw has LOCAL scope or if "nexthop" is declared ONLINK,
+ * which means that gw is forced to be on link.
+ *
+ * Code is still hairy, but now it is apparently logically
+ * consistent and very flexible. F.e. as by-product it allows
+ * to co-exists in peace independent exterior and interior
+ * routing processes.
+ *
+ * Normally it looks as following.
+ *
+ * {universe prefix}  -> (gw, oif) [scope link]
+ *               |
+ *               |-> {link prefix} -> (gw, oif) [scope local]
+ *                                     |
+ *                                     |-> {local prefix} (terminal node)
  */
-
-static int fib_check_nh(const struct rtmsg *r, struct fib_info *fi, struct fib_nh *nh)
+static int fib_check_nh(struct fib_config *cfg, struct fib_info *fi,
+                       struct fib_nh *nh)
 {
        int err;
+       struct net *net;
+       struct net_device *dev;
 
+       net = cfg->fc_nlinfo.nl_net;
        if (nh->nh_gw) {
                struct fib_result res;
 
-#ifdef CONFIG_IP_ROUTE_PERVASIVE
-               if (nh->nh_flags&RTNH_F_PERVASIVE)
-                       return 0;
-#endif
-               if (nh->nh_flags&RTNH_F_ONLINK) {
-                       struct net_device *dev;
+               if (nh->nh_flags & RTNH_F_ONLINK) {
 
-                       if (r->rtm_scope >= RT_SCOPE_LINK)
+                       if (cfg->fc_scope >= RT_SCOPE_LINK)
                                return -EINVAL;
-                       if (inet_addr_type(nh->nh_gw) != RTN_UNICAST)
+                       if (inet_addr_type(net, nh->nh_gw) != RTN_UNICAST)
                                return -EINVAL;
-                       if ((dev = __dev_get_by_index(nh->nh_oif)) == NULL)
+                       dev = __dev_get_by_index(net, nh->nh_oif);
+                       if (!dev)
                                return -ENODEV;
-                       if (!(dev->flags&IFF_UP))
+                       if (!(dev->flags & IFF_UP))
                                return -ENETDOWN;
                        nh->nh_dev = dev;
                        dev_hold(dev);
                        nh->nh_scope = RT_SCOPE_LINK;
                        return 0;
                }
+               rcu_read_lock();
                {
-                       struct flowi fl = { .nl_u = { .ip4_u =
-                                                     { .daddr = nh->nh_gw,
-                                                       .scope = r->rtm_scope + 1 } },
-                                           .oif = nh->nh_oif };
+                       struct flowi fl = {
+                               .fl4_dst = nh->nh_gw,
+                               .fl4_scope = cfg->fc_scope + 1,
+                               .oif = nh->nh_oif,
+                       };
 
                        /* It is not necessary, but requires a bit of thinking */
                        if (fl.fl4_scope < RT_SCOPE_LINK)
                                fl.fl4_scope = RT_SCOPE_LINK;
-                       if ((err = fib_lookup(&fl, &res)) != 0)
+                       err = fib_lookup(net, &fl, &res);
+                       if (err) {
+                               rcu_read_unlock();
                                return err;
+                       }
                }
                err = -EINVAL;
                if (res.type != RTN_UNICAST && res.type != RTN_LOCAL)
                        goto out;
                nh->nh_scope = res.scope;
                nh->nh_oif = FIB_RES_OIF(res);
-               if ((nh->nh_dev = FIB_RES_DEV(res)) == NULL)
-                       goto out;
-               dev_hold(nh->nh_dev);
-               err = -ENETDOWN;
-               if (!(nh->nh_dev->flags & IFF_UP))
+               nh->nh_dev = dev = FIB_RES_DEV(res);
+               if (!dev)
                        goto out;
-               err = 0;
-out:
-               fib_res_put(&res);
-               return err;
+               dev_hold(dev);
+               err = (dev->flags & IFF_UP) ? 0 : -ENETDOWN;
        } else {
                struct in_device *in_dev;
 
-               if (nh->nh_flags&(RTNH_F_PERVASIVE|RTNH_F_ONLINK))
+               if (nh->nh_flags & (RTNH_F_PERVASIVE | RTNH_F_ONLINK))
                        return -EINVAL;
 
-               in_dev = inetdev_by_index(nh->nh_oif);
+               rcu_read_lock();
+               err = -ENODEV;
+               in_dev = inetdev_by_index(net, nh->nh_oif);
                if (in_dev == NULL)
-                       return -ENODEV;
-               if (!(in_dev->dev->flags&IFF_UP)) {
-                       in_dev_put(in_dev);
-                       return -ENETDOWN;
-               }
+                       goto out;
+               err = -ENETDOWN;
+               if (!(in_dev->dev->flags & IFF_UP))
+                       goto out;
                nh->nh_dev = in_dev->dev;
                dev_hold(nh->nh_dev);
                nh->nh_scope = RT_SCOPE_HOST;
-               in_dev_put(in_dev);
+               err = 0;
        }
-       return 0;
+out:
+       rcu_read_unlock();
+       return err;
 }
 
-static inline unsigned int fib_laddr_hashfn(u32 val)
+static inline unsigned int fib_laddr_hashfn(__be32 val)
 {
        unsigned int mask = (fib_hash_size - 1);
 
-       return (val ^ (val >> 7) ^ (val >> 14)) & mask;
+       return ((__force u32)val ^
+               ((__force u32)val >> 7) ^
+               ((__force u32)val >> 14)) & mask;
 }
 
 static struct hlist_head *fib_hash_alloc(int bytes)
 {
        if (bytes <= PAGE_SIZE)
-               return kmalloc(bytes, GFP_KERNEL);
+               return kzalloc(bytes, GFP_KERNEL);
        else
                return (struct hlist_head *)
-                       __get_free_pages(GFP_KERNEL, get_order(bytes));
+                       __get_free_pages(GFP_KERNEL | __GFP_ZERO,
+                                        get_order(bytes));
 }
 
 static void fib_hash_free(struct hlist_head *hash, int bytes)
@@ -593,10 +645,13 @@ static void fib_hash_move(struct hlist_head *new_info_hash,
                          struct hlist_head *new_laddrhash,
                          unsigned int new_size)
 {
+       struct hlist_head *old_info_hash, *old_laddrhash;
        unsigned int old_size = fib_hash_size;
-       unsigned int i;
+       unsigned int i, bytes;
 
-       write_lock(&fib_info_lock);
+       spin_lock_bh(&fib_info_lock);
+       old_info_hash = fib_info_hash;
+       old_laddrhash = fib_info_laddrhash;
        fib_hash_size = new_size;
 
        for (i = 0; i < old_size; i++) {
@@ -635,45 +690,32 @@ static void fib_hash_move(struct hlist_head *new_info_hash,
        }
        fib_info_laddrhash = new_laddrhash;
 
-       write_unlock(&fib_info_lock);
+       spin_unlock_bh(&fib_info_lock);
+
+       bytes = old_size * sizeof(struct hlist_head *);
+       fib_hash_free(old_info_hash, bytes);
+       fib_hash_free(old_laddrhash, bytes);
 }
 
-struct fib_info *
-fib_create_info(const struct rtmsg *r, struct kern_rta *rta,
-               const struct nlmsghdr *nlh, int *errp)
+struct fib_info *fib_create_info(struct fib_config *cfg)
 {
        int err;
        struct fib_info *fi = NULL;
        struct fib_info *ofi;
-#ifdef CONFIG_IP_ROUTE_MULTIPATH
        int nhs = 1;
-#else
-       const int nhs = 1;
-#endif
-#ifdef CONFIG_IP_ROUTE_MULTIPATH_CACHED
-       u32 mp_alg = IP_MP_ALG_NONE;
-#endif
+       struct net *net = cfg->fc_nlinfo.nl_net;
 
        /* Fast check to catch the most weird cases */
-       if (fib_props[r->rtm_type].scope > r->rtm_scope)
+       if (fib_props[cfg->fc_type].scope > cfg->fc_scope)
                goto err_inval;
 
 #ifdef CONFIG_IP_ROUTE_MULTIPATH
-       if (rta->rta_mp) {
-               nhs = fib_count_nexthops(rta->rta_mp);
+       if (cfg->fc_mp) {
+               nhs = fib_count_nexthops(cfg->fc_mp, cfg->fc_mp_len);
                if (nhs == 0)
                        goto err_inval;
        }
 #endif
-#ifdef CONFIG_IP_ROUTE_MULTIPATH_CACHED
-       if (rta->rta_mp_alg) {
-               mp_alg = *rta->rta_mp_alg;
-
-               if (mp_alg < IP_MP_ALG_NONE ||
-                   mp_alg > IP_MP_ALG_MAX)
-                       goto err_inval;
-       }
-#endif
 
        err = -ENOBUFS;
        if (fib_info_cnt >= fib_hash_size) {
@@ -690,60 +732,55 @@ fib_create_info(const struct rtmsg *r, struct kern_rta *rta,
                if (!new_info_hash || !new_laddrhash) {
                        fib_hash_free(new_info_hash, bytes);
                        fib_hash_free(new_laddrhash, bytes);
-               } else {
-                       memset(new_info_hash, 0, bytes);
-                       memset(new_laddrhash, 0, bytes);
-
+               } else
                        fib_hash_move(new_info_hash, new_laddrhash, new_size);
-               }
 
                if (!fib_hash_size)
                        goto failure;
        }
 
-       fi = kmalloc(sizeof(*fi)+nhs*sizeof(struct fib_nh), GFP_KERNEL);
+       fi = kzalloc(sizeof(*fi)+nhs*sizeof(struct fib_nh), GFP_KERNEL);
        if (fi == NULL)
                goto failure;
        fib_info_cnt++;
-       memset(fi, 0, sizeof(*fi)+nhs*sizeof(struct fib_nh));
 
-       fi->fib_protocol = r->rtm_protocol;
+       fi->fib_net = hold_net(net);
+       fi->fib_protocol = cfg->fc_protocol;
+       fi->fib_flags = cfg->fc_flags;
+       fi->fib_priority = cfg->fc_priority;
+       fi->fib_prefsrc = cfg->fc_prefsrc;
 
        fi->fib_nhs = nhs;
        change_nexthops(fi) {
-               nh->nh_parent = fi;
+               nexthop_nh->nh_parent = fi;
        } endfor_nexthops(fi)
 
-       fi->fib_flags = r->rtm_flags;
-       if (rta->rta_priority)
-               fi->fib_priority = *rta->rta_priority;
-       if (rta->rta_mx) {
-               int attrlen = RTA_PAYLOAD(rta->rta_mx);
-               struct rtattr *attr = RTA_DATA(rta->rta_mx);
-
-               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_type(nla);
+
+                       if (type) {
+                               if (type > RTAX_MAX)
                                        goto err_inval;
-                               fi->fib_metrics[flavor-1] = *(unsigned*)RTA_DATA(attr);
+                               fi->fib_metrics[type - 1] = nla_get_u32(nla);
                        }
-                       attr = RTA_NEXT(attr, attrlen);
                }
        }
-       if (rta->rta_prefsrc)
-               memcpy(&fi->fib_prefsrc, rta->rta_prefsrc, 4);
 
-       if (rta->rta_mp) {
+       if (cfg->fc_mp) {
 #ifdef CONFIG_IP_ROUTE_MULTIPATH
-               if ((err = fib_get_nhs(fi, rta->rta_mp, r)) != 0)
+               err = fib_get_nhs(fi, cfg->fc_mp, cfg->fc_mp_len, cfg);
+               if (err != 0)
                        goto failure;
-               if (rta->rta_oif && fi->fib_nh->nh_oif != *rta->rta_oif)
+               if (cfg->fc_oif && fi->fib_nh->nh_oif != cfg->fc_oif)
                        goto err_inval;
-               if (rta->rta_gw && memcmp(&fi->fib_nh->nh_gw, rta->rta_gw, 4))
+               if (cfg->fc_gw && fi->fib_nh->nh_gw != cfg->fc_gw)
                        goto err_inval;
 #ifdef CONFIG_NET_CLS_ROUTE
-               if (rta->rta_flow && memcmp(&fi->fib_nh->nh_tclassid, rta->rta_flow, 4))
+               if (cfg->fc_flow && fi->fib_nh->nh_tclassid != cfg->fc_flow)
                        goto err_inval;
 #endif
 #else
@@ -751,60 +788,56 @@ fib_create_info(const struct rtmsg *r, struct kern_rta *rta,
 #endif
        } else {
                struct fib_nh *nh = fi->fib_nh;
-               if (rta->rta_oif)
-                       nh->nh_oif = *rta->rta_oif;
-               if (rta->rta_gw)
-                       memcpy(&nh->nh_gw, rta->rta_gw, 4);
+
+               nh->nh_oif = cfg->fc_oif;
+               nh->nh_gw = cfg->fc_gw;
+               nh->nh_flags = cfg->fc_flags;
 #ifdef CONFIG_NET_CLS_ROUTE
-               if (rta->rta_flow)
-                       memcpy(&nh->nh_tclassid, rta->rta_flow, 4);
+               nh->nh_tclassid = cfg->fc_flow;
 #endif
-               nh->nh_flags = r->rtm_flags;
 #ifdef CONFIG_IP_ROUTE_MULTIPATH
                nh->nh_weight = 1;
 #endif
        }
 
-#ifdef CONFIG_IP_ROUTE_MULTIPATH_CACHED
-       fi->fib_mp_alg = mp_alg;
-#endif
-
-       if (fib_props[r->rtm_type].error) {
-               if (rta->rta_gw || rta->rta_oif || rta->rta_mp)
+       if (fib_props[cfg->fc_type].error) {
+               if (cfg->fc_gw || cfg->fc_oif || cfg->fc_mp)
                        goto err_inval;
                goto link_it;
        }
 
-       if (r->rtm_scope > RT_SCOPE_HOST)
+       if (cfg->fc_scope > RT_SCOPE_HOST)
                goto err_inval;
 
-       if (r->rtm_scope == RT_SCOPE_HOST) {
+       if (cfg->fc_scope == RT_SCOPE_HOST) {
                struct fib_nh *nh = fi->fib_nh;
 
                /* Local address is added. */
                if (nhs != 1 || nh->nh_gw)
                        goto err_inval;
                nh->nh_scope = RT_SCOPE_NOWHERE;
-               nh->nh_dev = dev_get_by_index(fi->fib_nh->nh_oif);
+               nh->nh_dev = dev_get_by_index(net, fi->fib_nh->nh_oif);
                err = -ENODEV;
                if (nh->nh_dev == NULL)
                        goto failure;
        } else {
                change_nexthops(fi) {
-                       if ((err = fib_check_nh(r, fi, nh)) != 0)
+                       err = fib_check_nh(cfg, fi, nexthop_nh);
+                       if (err != 0)
                                goto failure;
                } endfor_nexthops(fi)
        }
 
        if (fi->fib_prefsrc) {
-               if (r->rtm_type != RTN_LOCAL || rta->rta_dst == NULL ||
-                   memcmp(&fi->fib_prefsrc, rta->rta_dst, 4))
-                       if (inet_addr_type(fi->fib_prefsrc) != RTN_LOCAL)
+               if (cfg->fc_type != RTN_LOCAL || !cfg->fc_dst ||
+                   fi->fib_prefsrc != cfg->fc_dst)
+                       if (inet_addr_type(net, fi->fib_prefsrc) != RTN_LOCAL)
                                goto err_inval;
        }
 
 link_it:
-       if ((ofi = fib_find_info(fi)) != NULL) {
+       ofi = fib_find_info(fi);
+       if (ofi) {
                fi->fib_dead = 1;
                free_fib_info(fi);
                ofi->fib_treeref++;
@@ -813,7 +846,7 @@ link_it:
 
        fi->fib_treeref++;
        atomic_inc(&fi->fib_clntref);
-       write_lock(&fib_info_lock);
+       spin_lock_bh(&fib_info_lock);
        hlist_add_head(&fi->fib_hash,
                       &fib_info_hash[fib_info_hashfn(fi)]);
        if (fi->fib_prefsrc) {
@@ -826,35 +859,35 @@ link_it:
                struct hlist_head *head;
                unsigned int hash;
 
-               if (!nh->nh_dev)
+               if (!nexthop_nh->nh_dev)
                        continue;
-               hash = fib_devindex_hashfn(nh->nh_dev->ifindex);
+               hash = fib_devindex_hashfn(nexthop_nh->nh_dev->ifindex);
                head = &fib_info_devhash[hash];
-               hlist_add_head(&nh->nh_hash, head);
+               hlist_add_head(&nexthop_nh->nh_hash, head);
        } endfor_nexthops(fi)
-       write_unlock(&fib_info_lock);
+       spin_unlock_bh(&fib_info_lock);
        return fi;
 
 err_inval:
        err = -EINVAL;
 
 failure:
-        *errp = err;
-        if (fi) {
+       if (fi) {
                fi->fib_dead = 1;
                free_fib_info(fi);
        }
-       return NULL;
+
+       return ERR_PTR(err);
 }
 
+/* Note! fib_semantic_match intentionally uses  RCU list functions. */
 int fib_semantic_match(struct list_head *head, const struct flowi *flp,
-                      struct fib_result *res, __u32 zone, __u32 mask, 
-                       int prefixlen)
+                      struct fib_result *res, int prefixlen, int fib_flags)
 {
        struct fib_alias *fa;
        int nh_sel = 0;
 
-       list_for_each_entry(fa, head, fa_list) {
+       list_for_each_entry_rcu(fa, head, fa_list) {
                int err;
 
                if (fa->fa_tos &&
@@ -864,7 +897,7 @@ int fib_semantic_match(struct list_head *head, const struct flowi *flp,
                if (fa->fa_scope < flp->fl4_scope)
                        continue;
 
-               fa->fa_state |= FA_S_ACCESSED;
+               fib_alias_accessed(fa);
 
                err = fib_props[fa->fa_type].error;
                if (err == 0) {
@@ -880,7 +913,7 @@ int fib_semantic_match(struct list_head *head, const struct flowi *flp,
                        case RTN_ANYCAST:
                        case RTN_MULTICAST:
                                for_nexthops(fi) {
-                                       if (nh->nh_flags&RTNH_F_DEAD)
+                                       if (nh->nh_flags & RTNH_F_DEAD)
                                                continue;
                                        if (!flp->oif || flp->oif == nh->nh_oif)
                                                break;
@@ -891,17 +924,17 @@ int fib_semantic_match(struct list_head *head, const struct flowi *flp,
                                        goto out_fill_res;
                                }
 #else
-                               if (nhsel < 1) {
+                               if (nhsel < 1)
                                        goto out_fill_res;
-                               }
 #endif
                                endfor_nexthops(fi);
                                continue;
 
                        default:
-                               printk(KERN_DEBUG "impossible 102\n");
+                               pr_warning("fib_semantic_match bad type %#x\n",
+                                          fa->fa_type);
                                return -EINVAL;
-                       };
+                       }
                }
                return err;
        }
@@ -913,306 +946,179 @@ out_fill_res:
        res->type = fa->fa_type;
        res->scope = fa->fa_scope;
        res->fi = fa->fa_info;
-#ifdef CONFIG_IP_ROUTE_MULTIPATH_CACHED
-       res->netmask = mask;
-       res->network = zone &
-               (0xFFFFFFFF >> (32 - prefixlen));
-#endif
-       atomic_inc(&res->fi->fib_clntref);
+       if (!(fib_flags & FIB_LOOKUP_NOREF))
+               atomic_inc(&res->fi->fib_clntref);
        return 0;
 }
 
 /* Find appropriate source address to this destination */
 
-u32 __fib_res_prefsrc(struct fib_result *res)
+__be32 __fib_res_prefsrc(struct fib_result *res)
 {
        return inet_select_addr(FIB_RES_DEV(*res), FIB_RES_GW(*res), res->scope);
 }
 
-int
-fib_dump_info(struct sk_buff *skb, u32 pid, u32 seq, int event,
-             u8 tb_id, u8 type, u8 scope, void *dst, int dst_len, u8 tos,
-             struct fib_info *fi, unsigned int flags)
+int fib_dump_info(struct sk_buff *skb, u32 pid, u32 seq, int event,
+                 u32 tb_id, u8 type, u8 scope, __be32 dst, int dst_len, u8 tos,
+                 struct fib_info *fi, unsigned int flags)
 {
+       struct nlmsghdr *nlh;
        struct rtmsg *rtm;
-       struct nlmsghdr  *nlh;
-       unsigned char    *b = skb->tail;
 
-       nlh = NLMSG_NEW(skb, pid, seq, event, sizeof(*rtm), flags);
-       rtm = NLMSG_DATA(nlh);
+       nlh = nlmsg_put(skb, pid, seq, event, sizeof(*rtm), flags);
+       if (nlh == NULL)
+               return -EMSGSIZE;
+
+       rtm = nlmsg_data(nlh);
        rtm->rtm_family = AF_INET;
        rtm->rtm_dst_len = dst_len;
        rtm->rtm_src_len = 0;
        rtm->rtm_tos = tos;
-       rtm->rtm_table = tb_id;
+       if (tb_id < 256)
+               rtm->rtm_table = tb_id;
+       else
+               rtm->rtm_table = RT_TABLE_COMPAT;
+       NLA_PUT_U32(skb, RTA_TABLE, tb_id);
        rtm->rtm_type = type;
        rtm->rtm_flags = fi->fib_flags;
        rtm->rtm_scope = scope;
-       if (rtm->rtm_dst_len)
-               RTA_PUT(skb, RTA_DST, 4, dst);
        rtm->rtm_protocol = fi->fib_protocol;
+
+       if (rtm->rtm_dst_len)
+               NLA_PUT_BE32(skb, RTA_DST, dst);
+
        if (fi->fib_priority)
-               RTA_PUT(skb, RTA_PRIORITY, 4, &fi->fib_priority);
-#ifdef CONFIG_NET_CLS_ROUTE
-       if (fi->fib_nh[0].nh_tclassid)
-               RTA_PUT(skb, RTA_FLOW, 4, &fi->fib_nh[0].nh_tclassid);
-#endif
+               NLA_PUT_U32(skb, RTA_PRIORITY, fi->fib_priority);
+
        if (rtnetlink_put_metrics(skb, fi->fib_metrics) < 0)
-               goto rtattr_failure;
+               goto nla_put_failure;
+
        if (fi->fib_prefsrc)
-               RTA_PUT(skb, RTA_PREFSRC, 4, &fi->fib_prefsrc);
+               NLA_PUT_BE32(skb, RTA_PREFSRC, fi->fib_prefsrc);
+
        if (fi->fib_nhs == 1) {
                if (fi->fib_nh->nh_gw)
-                       RTA_PUT(skb, RTA_GATEWAY, 4, &fi->fib_nh->nh_gw);
+                       NLA_PUT_BE32(skb, RTA_GATEWAY, fi->fib_nh->nh_gw);
+
                if (fi->fib_nh->nh_oif)
-                       RTA_PUT(skb, RTA_OIF, sizeof(int), &fi->fib_nh->nh_oif);
+                       NLA_PUT_U32(skb, RTA_OIF, fi->fib_nh->nh_oif);
+#ifdef CONFIG_NET_CLS_ROUTE
+               if (fi->fib_nh[0].nh_tclassid)
+                       NLA_PUT_U32(skb, RTA_FLOW, fi->fib_nh[0].nh_tclassid);
+#endif
        }
 #ifdef CONFIG_IP_ROUTE_MULTIPATH
        if (fi->fib_nhs > 1) {
-               struct rtnexthop *nhp;
-               struct rtattr *mp_head;
-               if (skb_tailroom(skb) <= RTA_SPACE(0))
-                       goto rtattr_failure;
-               mp_head = (struct rtattr*)skb_put(skb, RTA_SPACE(0));
+               struct rtnexthop *rtnh;
+               struct nlattr *mp;
+
+               mp = nla_nest_start(skb, RTA_MULTIPATH);
+               if (mp == NULL)
+                       goto nla_put_failure;
 
                for_nexthops(fi) {
-                       if (skb_tailroom(skb) < RTA_ALIGN(RTA_ALIGN(sizeof(*nhp)) + 4))
-                               goto rtattr_failure;
-                       nhp = (struct rtnexthop*)skb_put(skb, RTA_ALIGN(sizeof(*nhp)));
-                       nhp->rtnh_flags = nh->nh_flags & 0xFF;
-                       nhp->rtnh_hops = nh->nh_weight-1;
-                       nhp->rtnh_ifindex = nh->nh_oif;
+                       rtnh = nla_reserve_nohdr(skb, sizeof(*rtnh));
+                       if (rtnh == NULL)
+                               goto nla_put_failure;
+
+                       rtnh->rtnh_flags = nh->nh_flags & 0xFF;
+                       rtnh->rtnh_hops = nh->nh_weight - 1;
+                       rtnh->rtnh_ifindex = nh->nh_oif;
+
                        if (nh->nh_gw)
-                               RTA_PUT(skb, RTA_GATEWAY, 4, &nh->nh_gw);
-                       nhp->rtnh_len = skb->tail - (unsigned char*)nhp;
+                               NLA_PUT_BE32(skb, RTA_GATEWAY, nh->nh_gw);
+#ifdef CONFIG_NET_CLS_ROUTE
+                       if (nh->nh_tclassid)
+                               NLA_PUT_U32(skb, RTA_FLOW, nh->nh_tclassid);
+#endif
+                       /* length of rtnetlink header + attributes */
+                       rtnh->rtnh_len = nlmsg_get_pos(skb) - (void *) rtnh;
                } endfor_nexthops(fi);
-               mp_head->rta_type = RTA_MULTIPATH;
-               mp_head->rta_len = skb->tail - (u8*)mp_head;
+
+               nla_nest_end(skb, mp);
        }
 #endif
-       nlh->nlmsg_len = skb->tail - b;
-       return skb->len;
+       return nlmsg_end(skb, nlh);
 
-nlmsg_failure:
-rtattr_failure:
-       skb_trim(skb, b - skb->data);
-       return -1;
+nla_put_failure:
+       nlmsg_cancel(skb, nlh);
+       return -EMSGSIZE;
 }
 
-#ifndef CONFIG_IP_NOSIOCRT
-
-int
-fib_convert_rtentry(int cmd, struct nlmsghdr *nl, struct rtmsg *rtm,
-                   struct kern_rta *rta, struct rtentry *r)
+/*
+ * Update FIB if:
+ * - local address disappeared -> we must delete all the entries
+ *   referring to it.
+ * - device went down -> we must shutdown all nexthops going via it.
+ */
+int fib_sync_down_addr(struct net *net, __be32 local)
 {
-       int    plen;
-       u32    *ptr;
-
-       memset(rtm, 0, sizeof(*rtm));
-       memset(rta, 0, sizeof(*rta));
-
-       if (r->rt_dst.sa_family != AF_INET)
-               return -EAFNOSUPPORT;
-
-       /* Check mask for validity:
-          a) it must be contiguous.
-          b) destination must have all host bits clear.
-          c) if application forgot to set correct family (AF_INET),
-             reject request unless it is absolutely clear i.e.
-             both family and mask are zero.
-        */
-       plen = 32;
-       ptr = &((struct sockaddr_in*)&r->rt_dst)->sin_addr.s_addr;
-       if (!(r->rt_flags&RTF_HOST)) {
-               u32 mask = ((struct sockaddr_in*)&r->rt_genmask)->sin_addr.s_addr;
-               if (r->rt_genmask.sa_family != AF_INET) {
-                       if (mask || r->rt_genmask.sa_family)
-                               return -EAFNOSUPPORT;
-               }
-               if (bad_mask(mask, *ptr))
-                       return -EINVAL;
-               plen = inet_mask_len(mask);
-       }
-
-       nl->nlmsg_flags = NLM_F_REQUEST;
-       nl->nlmsg_pid = 0;
-       nl->nlmsg_seq = 0;
-       nl->nlmsg_len = NLMSG_LENGTH(sizeof(*rtm));
-       if (cmd == SIOCDELRT) {
-               nl->nlmsg_type = RTM_DELROUTE;
-               nl->nlmsg_flags = 0;
-       } else {
-               nl->nlmsg_type = RTM_NEWROUTE;
-               nl->nlmsg_flags = NLM_F_REQUEST|NLM_F_CREATE;
-               rtm->rtm_protocol = RTPROT_BOOT;
-       }
-
-       rtm->rtm_dst_len = plen;
-       rta->rta_dst = ptr;
-
-       if (r->rt_metric) {
-               *(u32*)&r->rt_pad3 = r->rt_metric - 1;
-               rta->rta_priority = (u32*)&r->rt_pad3;
-       }
-       if (r->rt_flags&RTF_REJECT) {
-               rtm->rtm_scope = RT_SCOPE_HOST;
-               rtm->rtm_type = RTN_UNREACHABLE;
-               return 0;
-       }
-       rtm->rtm_scope = RT_SCOPE_NOWHERE;
-       rtm->rtm_type = RTN_UNICAST;
-
-       if (r->rt_dev) {
-               char *colon;
-               struct net_device *dev;
-               char   devname[IFNAMSIZ];
-
-               if (copy_from_user(devname, r->rt_dev, IFNAMSIZ-1))
-                       return -EFAULT;
-               devname[IFNAMSIZ-1] = 0;
-               colon = strchr(devname, ':');
-               if (colon)
-                       *colon = 0;
-               dev = __dev_get_by_name(devname);
-               if (!dev)
-                       return -ENODEV;
-               rta->rta_oif = &dev->ifindex;
-               if (colon) {
-                       struct in_ifaddr *ifa;
-                       struct in_device *in_dev = __in_dev_get(dev);
-                       if (!in_dev)
-                               return -ENODEV;
-                       *colon = ':';
-                       for (ifa = in_dev->ifa_list; ifa; ifa = ifa->ifa_next)
-                               if (strcmp(ifa->ifa_label, devname) == 0)
-                                       break;
-                       if (ifa == NULL)
-                               return -ENODEV;
-                       rta->rta_prefsrc = &ifa->ifa_local;
-               }
-       }
-
-       ptr = &((struct sockaddr_in*)&r->rt_gateway)->sin_addr.s_addr;
-       if (r->rt_gateway.sa_family == AF_INET && *ptr) {
-               rta->rta_gw = ptr;
-               if (r->rt_flags&RTF_GATEWAY && inet_addr_type(*ptr) == RTN_UNICAST)
-                       rtm->rtm_scope = RT_SCOPE_UNIVERSE;
-       }
+       int ret = 0;
+       unsigned int hash = fib_laddr_hashfn(local);
+       struct hlist_head *head = &fib_info_laddrhash[hash];
+       struct hlist_node *node;
+       struct fib_info *fi;
 
-       if (cmd == SIOCDELRT)
+       if (fib_info_laddrhash == NULL || local == 0)
                return 0;
 
-       if (r->rt_flags&RTF_GATEWAY && rta->rta_gw == NULL)
-               return -EINVAL;
-
-       if (rtm->rtm_scope == RT_SCOPE_NOWHERE)
-               rtm->rtm_scope = RT_SCOPE_LINK;
-
-       if (r->rt_flags&(RTF_MTU|RTF_WINDOW|RTF_IRTT)) {
-               struct rtattr *rec;
-               struct rtattr *mx = kmalloc(RTA_LENGTH(3*RTA_LENGTH(4)), GFP_KERNEL);
-               if (mx == NULL)
-                       return -ENOMEM;
-               rta->rta_mx = mx;
-               mx->rta_type = RTA_METRICS;
-               mx->rta_len  = RTA_LENGTH(0);
-               if (r->rt_flags&RTF_MTU) {
-                       rec = (void*)((char*)mx + RTA_ALIGN(mx->rta_len));
-                       rec->rta_type = RTAX_ADVMSS;
-                       rec->rta_len = RTA_LENGTH(4);
-                       mx->rta_len += RTA_LENGTH(4);
-                       *(u32*)RTA_DATA(rec) = r->rt_mtu - 40;
-               }
-               if (r->rt_flags&RTF_WINDOW) {
-                       rec = (void*)((char*)mx + RTA_ALIGN(mx->rta_len));
-                       rec->rta_type = RTAX_WINDOW;
-                       rec->rta_len = RTA_LENGTH(4);
-                       mx->rta_len += RTA_LENGTH(4);
-                       *(u32*)RTA_DATA(rec) = r->rt_window;
-               }
-               if (r->rt_flags&RTF_IRTT) {
-                       rec = (void*)((char*)mx + RTA_ALIGN(mx->rta_len));
-                       rec->rta_type = RTAX_RTT;
-                       rec->rta_len = RTA_LENGTH(4);
-                       mx->rta_len += RTA_LENGTH(4);
-                       *(u32*)RTA_DATA(rec) = r->rt_irtt<<3;
+       hlist_for_each_entry(fi, node, head, fib_lhash) {
+               if (!net_eq(fi->fib_net, net))
+                       continue;
+               if (fi->fib_prefsrc == local) {
+                       fi->fib_flags |= RTNH_F_DEAD;
+                       ret++;
                }
        }
-       return 0;
+       return ret;
 }
 
-#endif
-
-/*
-   Update FIB if:
-   - local address disappeared -> we must delete all the entries
-     referring to it.
-   - device went down -> we must shutdown all nexthops going via it.
- */
-
-int fib_sync_down(u32 local, struct net_device *dev, int force)
+int fib_sync_down_dev(struct net_device *dev, int force)
 {
        int ret = 0;
        int scope = RT_SCOPE_NOWHERE;
-       
+       struct fib_info *prev_fi = NULL;
+       unsigned int hash = fib_devindex_hashfn(dev->ifindex);
+       struct hlist_head *head = &fib_info_devhash[hash];
+       struct hlist_node *node;
+       struct fib_nh *nh;
+
        if (force)
                scope = -1;
 
-       if (local && fib_info_laddrhash) {
-               unsigned int hash = fib_laddr_hashfn(local);
-               struct hlist_head *head = &fib_info_laddrhash[hash];
-               struct hlist_node *node;
-               struct fib_info *fi;
-
-               hlist_for_each_entry(fi, node, head, fib_lhash) {
-                       if (fi->fib_prefsrc == local) {
-                               fi->fib_flags |= RTNH_F_DEAD;
-                               ret++;
-                       }
-               }
-       }
-
-       if (dev) {
-               struct fib_info *prev_fi = NULL;
-               unsigned int hash = fib_devindex_hashfn(dev->ifindex);
-               struct hlist_head *head = &fib_info_devhash[hash];
-               struct hlist_node *node;
-               struct fib_nh *nh;
-
-               hlist_for_each_entry(nh, node, head, nh_hash) {
-                       struct fib_info *fi = nh->nh_parent;
-                       int dead;
+       hlist_for_each_entry(nh, node, head, nh_hash) {
+               struct fib_info *fi = nh->nh_parent;
+               int dead;
 
-                       BUG_ON(!fi->fib_nhs);
-                       if (nh->nh_dev != dev || fi == prev_fi)
-                               continue;
-                       prev_fi = fi;
-                       dead = 0;
-                       change_nexthops(fi) {
-                               if (nh->nh_flags&RTNH_F_DEAD)
-                                       dead++;
-                               else if (nh->nh_dev == dev &&
-                                        nh->nh_scope != scope) {
-                                       nh->nh_flags |= RTNH_F_DEAD;
+               BUG_ON(!fi->fib_nhs);
+               if (nh->nh_dev != dev || fi == prev_fi)
+                       continue;
+               prev_fi = fi;
+               dead = 0;
+               change_nexthops(fi) {
+                       if (nexthop_nh->nh_flags & RTNH_F_DEAD)
+                               dead++;
+                       else if (nexthop_nh->nh_dev == dev &&
+                                nexthop_nh->nh_scope != scope) {
+                               nexthop_nh->nh_flags |= RTNH_F_DEAD;
 #ifdef CONFIG_IP_ROUTE_MULTIPATH
-                                       spin_lock_bh(&fib_multipath_lock);
-                                       fi->fib_power -= nh->nh_power;
-                                       nh->nh_power = 0;
-                                       spin_unlock_bh(&fib_multipath_lock);
+                               spin_lock_bh(&fib_multipath_lock);
+                               fi->fib_power -= nexthop_nh->nh_power;
+                               nexthop_nh->nh_power = 0;
+                               spin_unlock_bh(&fib_multipath_lock);
 #endif
-                                       dead++;
-                               }
+                               dead++;
+                       }
 #ifdef CONFIG_IP_ROUTE_MULTIPATH
-                               if (force > 1 && nh->nh_dev == dev) {
-                                       dead = fi->fib_nhs;
-                                       break;
-                               }
-#endif
-                       } endfor_nexthops(fi)
-                       if (dead == fi->fib_nhs) {
-                               fi->fib_flags |= RTNH_F_DEAD;
-                               ret++;
+                       if (force > 1 && nexthop_nh->nh_dev == dev) {
+                               dead = fi->fib_nhs;
+                               break;
                        }
+#endif
+               } endfor_nexthops(fi)
+               if (dead == fi->fib_nhs) {
+                       fi->fib_flags |= RTNH_F_DEAD;
+                       ret++;
                }
        }
 
@@ -1222,10 +1128,9 @@ int fib_sync_down(u32 local, struct net_device *dev, int force)
 #ifdef CONFIG_IP_ROUTE_MULTIPATH
 
 /*
-   Dead device goes up. We wake up dead nexthops.
-   It takes sense only on multipath routes.
+ * Dead device goes up. We wake up dead nexthops.
+ * It takes sense only on multipath routes.
  */
-
 int fib_sync_up(struct net_device *dev)
 {
        struct fib_info *prev_fi;
@@ -1235,7 +1140,7 @@ int fib_sync_up(struct net_device *dev)
        struct fib_nh *nh;
        int ret;
 
-       if (!(dev->flags&IFF_UP))
+       if (!(dev->flags & IFF_UP))
                return 0;
 
        prev_fi = NULL;
@@ -1254,18 +1159,20 @@ int fib_sync_up(struct net_device *dev)
                prev_fi = fi;
                alive = 0;
                change_nexthops(fi) {
-                       if (!(nh->nh_flags&RTNH_F_DEAD)) {
+                       if (!(nexthop_nh->nh_flags & RTNH_F_DEAD)) {
                                alive++;
                                continue;
                        }
-                       if (nh->nh_dev == NULL || !(nh->nh_dev->flags&IFF_UP))
+                       if (nexthop_nh->nh_dev == NULL ||
+                           !(nexthop_nh->nh_dev->flags & IFF_UP))
                                continue;
-                       if (nh->nh_dev != dev || __in_dev_get(dev) == NULL)
+                       if (nexthop_nh->nh_dev != dev ||
+                           !__in_dev_get_rtnl(dev))
                                continue;
                        alive++;
                        spin_lock_bh(&fib_multipath_lock);
-                       nh->nh_power = 0;
-                       nh->nh_flags &= ~RTNH_F_DEAD;
+                       nexthop_nh->nh_power = 0;
+                       nexthop_nh->nh_flags &= ~RTNH_F_DEAD;
                        spin_unlock_bh(&fib_multipath_lock);
                } endfor_nexthops(fi)
 
@@ -1279,10 +1186,9 @@ int fib_sync_up(struct net_device *dev)
 }
 
 /*
-   The algorithm is suboptimal, but it provides really
-   fair weighted route distribution.
+ * The algorithm is suboptimal, but it provides really
+ * fair weighted route distribution.
  */
-
 void fib_select_multipath(const struct flowi *flp, struct fib_result *res)
 {
        struct fib_info *fi = res->fi;
@@ -1292,9 +1198,9 @@ void fib_select_multipath(const struct flowi *flp, struct fib_result *res)
        if (fi->fib_power <= 0) {
                int power = 0;
                change_nexthops(fi) {
-                       if (!(nh->nh_flags&RTNH_F_DEAD)) {
-                               power += nh->nh_weight;
-                               nh->nh_power = nh->nh_weight;
+                       if (!(nexthop_nh->nh_flags & RTNH_F_DEAD)) {
+                               power += nexthop_nh->nh_weight;
+                               nexthop_nh->nh_power = nexthop_nh->nh_weight;
                        }
                } endfor_nexthops(fi);
                fi->fib_power = power;
@@ -1308,15 +1214,17 @@ void fib_select_multipath(const struct flowi *flp, struct fib_result *res)
 
 
        /* w should be random number [0..fi->fib_power-1],
-          it is pretty bad approximation.
+        * it is pretty bad approximation.
         */
 
        w = jiffies % fi->fib_power;
 
        change_nexthops(fi) {
-               if (!(nh->nh_flags&RTNH_F_DEAD) && nh->nh_power) {
-                       if ((w -= nh->nh_power) <= 0) {
-                               nh->nh_power--;
+               if (!(nexthop_nh->nh_flags & RTNH_F_DEAD) &&
+                   nexthop_nh->nh_power) {
+                       w -= nexthop_nh->nh_power;
+                       if (w <= 0) {
+                               nexthop_nh->nh_power--;
                                fi->fib_power--;
                                res->nh_sel = nhsel;
                                spin_unlock_bh(&fib_multipath_lock);