summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorCorentin Delorme <codelorme@gmail.com>2015-10-18 22:38:00 +0200
committerCorentin Delorme <codelorme@gmail.com>2015-10-18 22:38:00 +0200
commit1e497fe07a9bf64a1b47c8542ececfb1a2087109 (patch)
tree92ad44d89b1638760b0c3f2c95961f76f2624036
parentAdd missing header file fib_lookup.h (diff)
downloadkernel-routing-table-corentin-lc-trie.tar.xz
kernel-routing-table-corentin-lc-trie.zip
-rw-r--r--Makefile.kernel2
-rw-r--r--fib_semantics.c1337
-rw-r--r--lc-trie.c676
-rw-r--r--lc-trie.h27
-rw-r--r--routing-table.c133
-rw-r--r--routing-table.h9
6 files changed, 2110 insertions, 74 deletions
diff --git a/Makefile.kernel b/Makefile.kernel
index 5d47d97..e992061 100644
--- a/Makefile.kernel
+++ b/Makefile.kernel
@@ -1,7 +1,7 @@
ifneq ($(KERNELRELEASE),)
obj-m := lctrie.o
ccflags-y += -DDEBUG -g -DGIT_REVISION="KBUILD_STR($(shell git --git-dir="$(M)/.git" rev-parse --abbrev-ref HEAD):$(shell git --git-dir="$(M)/.git" rev-parse --short HEAD))"
-lctrie-y := main.o routing-table.o lc-trie.o
+lctrie-y := main.o routing-table.o lc-trie.o fib_semantics.o
else
KERNELDIR ?= /lib/modules/$(shell uname -r)/build
PWD := $(shell pwd)
diff --git a/fib_semantics.c b/fib_semantics.c
new file mode 100644
index 0000000..c91bb45
--- /dev/null
+++ b/fib_semantics.c
@@ -0,0 +1,1337 @@
+/*
+ * INET An implementation of the TCP/IP protocol suite for the LINUX
+ * operating system. INET is implemented using the BSD Socket
+ * interface as the means of communication with the user level.
+ *
+ * IPv4 Forwarding Information Base: semantics.
+ *
+ * Authors: Alexey Kuznetsov, <kuznet@ms2.inr.ac.ru>
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version
+ * 2 of the License, or (at your option) any later version.
+ */
+
+#include <asm/uaccess.h>
+#include <linux/bitops.h>
+#include <linux/types.h>
+#include <linux/kernel.h>
+#include <linux/jiffies.h>
+#include <linux/mm.h>
+#include <linux/string.h>
+#include <linux/socket.h>
+#include <linux/sockios.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/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/netlink.h>
+#include <net/nexthop.h>
+
+#include "fib_lookup.h"
+
+static DEFINE_SPINLOCK(fib_info_lock);
+static struct hlist_head *fib_info_hash;
+static struct hlist_head *fib_info_laddrhash;
+static unsigned int fib_info_hash_size;
+static unsigned int fib_info_cnt;
+
+#define DEVINDEX_HASHBITS 8
+#define DEVINDEX_HASHSIZE (1U << DEVINDEX_HASHBITS)
+static struct hlist_head fib_info_devhash[DEVINDEX_HASHSIZE];
+
+#ifdef CONFIG_IP_ROUTE_MULTIPATH
+
+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 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; const struct fib_nh *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) }
+
+
+const struct fib_prop fib_props[RTN_MAX + 1] = {
+ [RTN_UNSPEC] = {
+ .error = 0,
+ .scope = RT_SCOPE_NOWHERE,
+ },
+ [RTN_UNICAST] = {
+ .error = 0,
+ .scope = RT_SCOPE_UNIVERSE,
+ },
+ [RTN_LOCAL] = {
+ .error = 0,
+ .scope = RT_SCOPE_HOST,
+ },
+ [RTN_BROADCAST] = {
+ .error = 0,
+ .scope = RT_SCOPE_LINK,
+ },
+ [RTN_ANYCAST] = {
+ .error = 0,
+ .scope = RT_SCOPE_LINK,
+ },
+ [RTN_MULTICAST] = {
+ .error = 0,
+ .scope = RT_SCOPE_UNIVERSE,
+ },
+ [RTN_BLACKHOLE] = {
+ .error = -EINVAL,
+ .scope = RT_SCOPE_UNIVERSE,
+ },
+ [RTN_UNREACHABLE] = {
+ .error = -EHOSTUNREACH,
+ .scope = RT_SCOPE_UNIVERSE,
+ },
+ [RTN_PROHIBIT] = {
+ .error = -EACCES,
+ .scope = RT_SCOPE_UNIVERSE,
+ },
+ [RTN_THROW] = {
+ .error = -EAGAIN,
+ .scope = RT_SCOPE_UNIVERSE,
+ },
+ [RTN_NAT] = {
+ .error = -EINVAL,
+ .scope = RT_SCOPE_NOWHERE,
+ },
+ [RTN_XRESOLVE] = {
+ .error = -EINVAL,
+ .scope = RT_SCOPE_NOWHERE,
+ },
+};
+
+static void rt_fibinfo_free(struct rtable __rcu **rtp)
+{
+ struct rtable *rt = rcu_dereference_protected(*rtp, 1);
+
+ if (!rt)
+ return;
+
+ /* Not even needed : RCU_INIT_POINTER(*rtp, NULL);
+ * because we waited an RCU grace period before calling
+ * free_fib_info_rcu()
+ */
+
+ dst_free(&rt->dst);
+}
+
+static void free_nh_exceptions(struct fib_nh *nh)
+{
+ struct fnhe_hash_bucket *hash = nh->nh_exceptions;
+ int i;
+
+ for (i = 0; i < FNHE_HASH_SIZE; i++) {
+ struct fib_nh_exception *fnhe;
+
+ fnhe = rcu_dereference_protected(hash[i].chain, 1);
+ while (fnhe) {
+ struct fib_nh_exception *next;
+
+ next = rcu_dereference_protected(fnhe->fnhe_next, 1);
+
+ rt_fibinfo_free(&fnhe->fnhe_rth_input);
+ rt_fibinfo_free(&fnhe->fnhe_rth_output);
+
+ kfree(fnhe);
+
+ fnhe = next;
+ }
+ }
+ kfree(hash);
+}
+
+static void rt_fibinfo_free_cpus(struct rtable __rcu * __percpu *rtp)
+{
+ int cpu;
+
+ if (!rtp)
+ return;
+
+ for_each_possible_cpu(cpu) {
+ struct rtable *rt;
+
+ rt = rcu_dereference_protected(*per_cpu_ptr(rtp, cpu), 1);
+ if (rt)
+ dst_free(&rt->dst);
+ }
+ free_percpu(rtp);
+}
+
+/* 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);
+
+ change_nexthops(fi) {
+ if (nexthop_nh->nh_dev)
+ dev_put(nexthop_nh->nh_dev);
+ if (nexthop_nh->nh_exceptions)
+ free_nh_exceptions(nexthop_nh);
+ rt_fibinfo_free_cpus(nexthop_nh->nh_pcpu_rth_output);
+ rt_fibinfo_free(&nexthop_nh->nh_rth_input);
+ } endfor_nexthops(fi);
+
+ release_net(fi->fib_net);
+ // TODO
+ //if (fi->fib_metrics != (u32 *) dst_default_metrics)
+ // kfree(fi->fib_metrics);
+ kfree(fi);
+}
+
+void free_fib_info(struct fib_info *fi)
+{
+ if (fi->fib_dead == 0) {
+ pr_warn("Freeing alive fib_info %p\n", fi);
+ return;
+ }
+ fib_info_cnt--;
+#ifdef CONFIG_IP_ROUTE_CLASSID
+ change_nexthops(fi) {
+ if (nexthop_nh->nh_tclassid)
+ fi->fib_net->ipv4.fib_num_tclassid_users--;
+ } endfor_nexthops(fi);
+#endif
+ call_rcu(&fi->rcu, free_fib_info_rcu);
+}
+
+void fib_release_info(struct fib_info *fi)
+{
+ 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 (!nexthop_nh->nh_dev)
+ continue;
+ hlist_del(&nexthop_nh->nh_hash);
+ } endfor_nexthops(fi)
+ fi->fib_dead = 1;
+ fib_info_put(fi);
+ }
+ spin_unlock_bh(&fib_info_lock);
+}
+
+static inline int nh_comp(const struct fib_info *fi, const struct fib_info *ofi)
+{
+ const struct fib_nh *onh = ofi->fib_nh;
+
+ for_nexthops(fi) {
+ if (nh->nh_oif != onh->nh_oif ||
+ nh->nh_gw != onh->nh_gw ||
+ nh->nh_scope != onh->nh_scope ||
+#ifdef CONFIG_IP_ROUTE_MULTIPATH
+ nh->nh_weight != onh->nh_weight ||
+#endif
+#ifdef CONFIG_IP_ROUTE_CLASSID
+ nh->nh_tclassid != onh->nh_tclassid ||
+#endif
+ ((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_info_hash_size - 1);
+ unsigned int val = fi->fib_nhs;
+
+ val ^= (fi->fib_protocol << 8) | fi->fib_scope;
+ 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;
+}
+
+static struct fib_info *fib_find_info(const struct fib_info *nfi)
+{
+ struct hlist_head *head;
+ struct fib_info *fi;
+ unsigned int hash;
+
+ hash = fib_info_hashfn(nfi);
+ head = &fib_info_hash[hash];
+
+ hlist_for_each_entry(fi, 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 &&
+ nfi->fib_scope == fi->fib_scope &&
+ nfi->fib_prefsrc == fi->fib_prefsrc &&
+ nfi->fib_priority == fi->fib_priority &&
+ nfi->fib_type == fi->fib_type &&
+ memcmp(nfi->fib_metrics, fi->fib_metrics,
+ sizeof(u32) * RTAX_MAX) == 0 &&
+ ((nfi->fib_flags ^ fi->fib_flags) & ~RTNH_F_DEAD) == 0 &&
+ (nfi->fib_nhs == 0 || nh_comp(fi, nfi) == 0))
+ return fi;
+ }
+
+ return NULL;
+}
+
+/* Check, that the gateway is already configured.
+ * Used only by redirect accept routine.
+ */
+int ip_fib_check_default(__be32 gw, struct net_device *dev)
+{
+ struct hlist_head *head;
+ struct fib_nh *nh;
+ unsigned int hash;
+
+ spin_lock(&fib_info_lock);
+
+ hash = fib_devindex_hashfn(dev->ifindex);
+ head = &fib_info_devhash[hash];
+ hlist_for_each_entry(nh, head, nh_hash) {
+ if (nh->nh_dev == dev &&
+ nh->nh_gw == gw &&
+ !(nh->nh_flags & RTNH_F_DEAD)) {
+ spin_unlock(&fib_info_lock);
+ return 0;
+ }
+ }
+
+ spin_unlock(&fib_info_lock);
+
+ return -1;
+}
+
+static inline size_t fib_nlmsg_size(struct fib_info *fi)
+{
+ 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 */
+
+ /* space for nested metrics */
+ payload += nla_total_size((RTAX_MAX * nla_total_size(4)));
+
+ 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, const 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->portid, seq, event, tb_id,
+ fa->fa_type, 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);
+ goto errout;
+ }
+ rtnl_notify(skb, info->nl_net, info->portid, 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
+ * priority less than or equal to PRIO.
+ */
+struct fib_alias *fib_find_alias(struct list_head *fah, u8 tos, u32 prio)
+{
+ if (fah) {
+ struct fib_alias *fa;
+ list_for_each_entry(fa, fah, fa_list) {
+ if (fa->fa_tos > tos)
+ continue;
+ if (fa->fa_info->fib_priority >= prio ||
+ fa->fa_tos < tos)
+ return fa;
+ }
+ }
+ return NULL;
+}
+
+static int fib_detect_death(struct fib_info *fi, int order,
+ struct fib_info **last_resort, int *last_idx,
+ int dflt)
+{
+ struct neighbour *n;
+ int state = NUD_NONE;
+
+ n = neigh_lookup(&arp_tbl, &fi->fib_nh[0].nh_gw, fi->fib_dev);
+ if (n) {
+ state = n->nud_state;
+ neigh_release(n);
+ }
+ if (state == NUD_REACHABLE)
+ return 0;
+ if ((state & NUD_VALID) && order != dflt)
+ return 0;
+ if ((state & NUD_VALID) ||
+ (*last_idx < 0 && order > dflt)) {
+ *last_resort = fi;
+ *last_idx = order;
+ }
+ return 1;
+}
+
+#ifdef CONFIG_IP_ROUTE_MULTIPATH
+
+static int fib_count_nexthops(struct rtnexthop *rtnh, int remaining)
+{
+ int nhs = 0;
+
+ while (rtnh_ok(rtnh, remaining)) {
+ 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, struct rtnexthop *rtnh,
+ int remaining, struct fib_config *cfg)
+{
+ change_nexthops(fi) {
+ int attrlen;
+
+ if (!rtnh_ok(rtnh, remaining))
+ return -EINVAL;
+
+ 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_IP_ROUTE_CLASSID
+ nla = nla_find(attrs, attrlen, RTA_FLOW);
+ nexthop_nh->nh_tclassid = nla ? nla_get_u32(nla) : 0;
+ if (nexthop_nh->nh_tclassid)
+ fi->fib_net->ipv4.fib_num_tclassid_users++;
+#endif
+ }
+
+ rtnh = rtnh_next(rtnh, &remaining);
+ } endfor_nexthops(fi);
+
+ return 0;
+}
+
+#endif
+
+int fib_nh_match(struct fib_config *cfg, struct fib_info *fi)
+{
+#ifdef CONFIG_IP_ROUTE_MULTIPATH
+ struct rtnexthop *rtnh;
+ int remaining;
+#endif
+
+ if (cfg->fc_priority && cfg->fc_priority != fi->fib_priority)
+ return 1;
+
+ 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 (cfg->fc_mp == NULL)
+ return 0;
+
+ rtnh = cfg->fc_mp;
+ remaining = cfg->fc_mp_len;
+
+ for_nexthops(fi) {
+ int attrlen;
+
+ if (!rtnh_ok(rtnh, remaining))
+ return -EINVAL;
+
+ if (rtnh->rtnh_ifindex && rtnh->rtnh_ifindex != nh->nh_oif)
+ return 1;
+
+ 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_IP_ROUTE_CLASSID
+ nla = nla_find(attrs, attrlen, RTA_FLOW);
+ if (nla && nla_get_u32(nla) != nh->nh_tclassid)
+ return 1;
+#endif
+ }
+
+ rtnh = rtnh_next(rtnh, &remaining);
+ } endfor_nexthops(fi);
+#endif
+ return 0;
+}
+
+
+/*
+ * 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(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;
+
+ if (nh->nh_flags & RTNH_F_ONLINK) {
+
+ if (cfg->fc_scope >= RT_SCOPE_LINK)
+ return -EINVAL;
+ if (inet_addr_type(net, nh->nh_gw) != RTN_UNICAST)
+ return -EINVAL;
+ dev = __dev_get_by_index(net, nh->nh_oif);
+ if (!dev)
+ return -ENODEV;
+ 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 flowi4 fl4 = {
+ .daddr = nh->nh_gw,
+ .flowi4_scope = cfg->fc_scope + 1,
+ .flowi4_oif = nh->nh_oif,
+ .flowi4_iif = LOOPBACK_IFINDEX,
+ };
+
+ /* It is not necessary, but requires a bit of thinking */
+ if (fl4.flowi4_scope < RT_SCOPE_LINK)
+ fl4.flowi4_scope = RT_SCOPE_LINK;
+ err = fib_lookup(net, &fl4, &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);
+ nh->nh_dev = dev = FIB_RES_DEV(res);
+ if (!dev)
+ goto out;
+ 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))
+ return -EINVAL;
+
+ rcu_read_lock();
+ err = -ENODEV;
+ in_dev = inetdev_by_index(net, nh->nh_oif);
+ if (in_dev == NULL)
+ 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;
+ err = 0;
+ }
+out:
+ rcu_read_unlock();
+ return err;
+}
+
+static inline unsigned int fib_laddr_hashfn(__be32 val)
+{
+ unsigned int mask = (fib_info_hash_size - 1);
+
+ return ((__force u32)val ^
+ ((__force u32)val >> 7) ^
+ ((__force u32)val >> 14)) & mask;
+}
+
+static struct hlist_head *fib_info_hash_alloc(int bytes)
+{
+ if (bytes <= PAGE_SIZE)
+ return kzalloc(bytes, GFP_KERNEL);
+ else
+ return (struct hlist_head *)
+ __get_free_pages(GFP_KERNEL | __GFP_ZERO,
+ get_order(bytes));
+}
+
+static void fib_info_hash_free(struct hlist_head *hash, int bytes)
+{
+ if (!hash)
+ return;
+
+ if (bytes <= PAGE_SIZE)
+ kfree(hash);
+ else
+ free_pages((unsigned long) hash, get_order(bytes));
+}
+
+static void fib_info_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_info_hash_size;
+ unsigned int i, bytes;
+
+ spin_lock_bh(&fib_info_lock);
+ old_info_hash = fib_info_hash;
+ old_laddrhash = fib_info_laddrhash;
+ fib_info_hash_size = new_size;
+
+ for (i = 0; i < old_size; i++) {
+ struct hlist_head *head = &fib_info_hash[i];
+ struct hlist_node *n;
+ struct fib_info *fi;
+
+ hlist_for_each_entry_safe(fi, n, head, fib_hash) {
+ struct hlist_head *dest;
+ unsigned int new_hash;
+
+ hlist_del(&fi->fib_hash);
+
+ new_hash = fib_info_hashfn(fi);
+ dest = &new_info_hash[new_hash];
+ hlist_add_head(&fi->fib_hash, dest);
+ }
+ }
+ fib_info_hash = new_info_hash;
+
+ for (i = 0; i < old_size; i++) {
+ struct hlist_head *lhead = &fib_info_laddrhash[i];
+ struct hlist_node *n;
+ struct fib_info *fi;
+
+ hlist_for_each_entry_safe(fi, n, lhead, fib_lhash) {
+ struct hlist_head *ldest;
+ unsigned int new_hash;
+
+ hlist_del(&fi->fib_lhash);
+
+ new_hash = fib_laddr_hashfn(fi->fib_prefsrc);
+ ldest = &new_laddrhash[new_hash];
+ hlist_add_head(&fi->fib_lhash, ldest);
+ }
+ }
+ fib_info_laddrhash = new_laddrhash;
+
+ spin_unlock_bh(&fib_info_lock);
+
+ bytes = old_size * sizeof(struct hlist_head *);
+ fib_info_hash_free(old_info_hash, bytes);
+ fib_info_hash_free(old_laddrhash, bytes);
+}
+
+__be32 fib_info_update_nh_saddr(struct net *net, struct fib_nh *nh)
+{
+ nh->nh_saddr = inet_select_addr(nh->nh_dev,
+ nh->nh_gw,
+ nh->nh_parent->fib_scope);
+ nh->nh_saddr_genid = atomic_read(&net->ipv4.dev_addr_genid);
+
+ return nh->nh_saddr;
+}
+
+struct fib_info *fib_create_info(struct fib_config *cfg)
+{
+ int err;
+ struct fib_info *fi = NULL;
+ struct fib_info *ofi;
+ int nhs = 1;
+ struct net *net = cfg->fc_nlinfo.nl_net;
+
+ if (cfg->fc_type > RTN_MAX)
+ goto err_inval;
+
+ /* Fast check to catch the most weird cases */
+ if (fib_props[cfg->fc_type].scope > cfg->fc_scope)
+ goto err_inval;
+
+#ifdef CONFIG_IP_ROUTE_MULTIPATH
+ if (cfg->fc_mp) {
+ nhs = fib_count_nexthops(cfg->fc_mp, cfg->fc_mp_len);
+ if (nhs == 0)
+ goto err_inval;
+ }
+#endif
+
+ err = -ENOBUFS;
+ if (fib_info_cnt >= fib_info_hash_size) {
+ unsigned int new_size = fib_info_hash_size << 1;
+ struct hlist_head *new_info_hash;
+ struct hlist_head *new_laddrhash;
+ unsigned int bytes;
+
+ if (!new_size)
+ new_size = 16;
+ bytes = new_size * sizeof(struct hlist_head *);
+ new_info_hash = fib_info_hash_alloc(bytes);
+ new_laddrhash = fib_info_hash_alloc(bytes);
+ if (!new_info_hash || !new_laddrhash) {
+ fib_info_hash_free(new_info_hash, bytes);
+ fib_info_hash_free(new_laddrhash, bytes);
+ } else
+ fib_info_hash_move(new_info_hash, new_laddrhash, new_size);
+
+ if (!fib_info_hash_size)
+ goto failure;
+ }
+
+ fi = kzalloc(sizeof(*fi)+nhs*sizeof(struct fib_nh), GFP_KERNEL);
+ if (fi == NULL)
+ goto failure;
+ fib_info_cnt++;
+ if (cfg->fc_mx) {
+ fi->fib_metrics = kzalloc(sizeof(u32) * RTAX_MAX, GFP_KERNEL);
+ if (!fi->fib_metrics)
+ goto failure;
+ }
+ // TODO
+ //else
+ // fi->fib_metrics = (u32 *) dst_default_metrics;
+
+ fi->fib_net = hold_net(net);
+ fi->fib_protocol = cfg->fc_protocol;
+ fi->fib_scope = cfg->fc_scope;
+ fi->fib_flags = cfg->fc_flags;
+ fi->fib_priority = cfg->fc_priority;
+ fi->fib_prefsrc = cfg->fc_prefsrc;
+ fi->fib_type = cfg->fc_type;
+
+ fi->fib_nhs = nhs;
+ change_nexthops(fi) {
+ nexthop_nh->nh_parent = fi;
+ nexthop_nh->nh_pcpu_rth_output = alloc_percpu(struct rtable __rcu *);
+ if (!nexthop_nh->nh_pcpu_rth_output)
+ goto failure;
+ } endfor_nexthops(fi)
+
+ 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) {
+ u32 val;
+
+ if (type > RTAX_MAX)
+ goto err_inval;
+ val = nla_get_u32(nla);
+ if (type == RTAX_ADVMSS && val > 65535 - 40)
+ val = 65535 - 40;
+ if (type == RTAX_MTU && val > 65535 - 15)
+ val = 65535 - 15;
+ fi->fib_metrics[type - 1] = val;
+ }
+ }
+ }
+
+ if (cfg->fc_mp) {
+#ifdef CONFIG_IP_ROUTE_MULTIPATH
+ err = fib_get_nhs(fi, cfg->fc_mp, cfg->fc_mp_len, cfg);
+ if (err != 0)
+ goto failure;
+ if (cfg->fc_oif && fi->fib_nh->nh_oif != cfg->fc_oif)
+ goto err_inval;
+ if (cfg->fc_gw && fi->fib_nh->nh_gw != cfg->fc_gw)
+ goto err_inval;
+#ifdef CONFIG_IP_ROUTE_CLASSID
+ if (cfg->fc_flow && fi->fib_nh->nh_tclassid != cfg->fc_flow)
+ goto err_inval;
+#endif
+#else
+ goto err_inval;
+#endif
+ } else {
+ struct fib_nh *nh = fi->fib_nh;
+
+ nh->nh_oif = cfg->fc_oif;
+ nh->nh_gw = cfg->fc_gw;
+ nh->nh_flags = cfg->fc_flags;
+#ifdef CONFIG_IP_ROUTE_CLASSID
+ nh->nh_tclassid = cfg->fc_flow;
+ if (nh->nh_tclassid)
+ fi->fib_net->ipv4.fib_num_tclassid_users++;
+#endif
+#ifdef CONFIG_IP_ROUTE_MULTIPATH
+ nh->nh_weight = 1;
+#endif
+ }
+
+ if (fib_props[cfg->fc_type].error) {
+ if (cfg->fc_gw || cfg->fc_oif || cfg->fc_mp)
+ goto err_inval;
+ goto link_it;
+ } else {
+ switch (cfg->fc_type) {
+ case RTN_UNICAST:
+ case RTN_LOCAL:
+ case RTN_BROADCAST:
+ case RTN_ANYCAST:
+ case RTN_MULTICAST:
+ break;
+ default:
+ goto err_inval;
+ }
+ }
+
+ if (cfg->fc_scope > RT_SCOPE_HOST)
+ goto err_inval;
+
+ 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(net, fi->fib_nh->nh_oif);
+ err = -ENODEV;
+ if (nh->nh_dev == NULL)
+ goto failure;
+ } else {
+ change_nexthops(fi) {
+ err = fib_check_nh(cfg, fi, nexthop_nh);
+ if (err != 0)
+ goto failure;
+ } endfor_nexthops(fi)
+ }
+
+ if (fi->fib_prefsrc) {
+ 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;
+ }
+
+ change_nexthops(fi) {
+ fib_info_update_nh_saddr(net, nexthop_nh);
+ } endfor_nexthops(fi)
+
+link_it:
+ ofi = fib_find_info(fi);
+ if (ofi) {
+ fi->fib_dead = 1;
+ free_fib_info(fi);
+ ofi->fib_treeref++;
+ return ofi;
+ }
+
+ fi->fib_treeref++;
+ atomic_inc(&fi->fib_clntref);
+ spin_lock_bh(&fib_info_lock);
+ hlist_add_head(&fi->fib_hash,
+ &fib_info_hash[fib_info_hashfn(fi)]);
+ if (fi->fib_prefsrc) {
+ struct hlist_head *head;
+
+ head = &fib_info_laddrhash[fib_laddr_hashfn(fi->fib_prefsrc)];
+ hlist_add_head(&fi->fib_lhash, head);
+ }
+ change_nexthops(fi) {
+ struct hlist_head *head;
+ unsigned int hash;
+
+ if (!nexthop_nh->nh_dev)
+ continue;
+ hash = fib_devindex_hashfn(nexthop_nh->nh_dev->ifindex);
+ head = &fib_info_devhash[hash];
+ hlist_add_head(&nexthop_nh->nh_hash, head);
+ } endfor_nexthops(fi)
+ spin_unlock_bh(&fib_info_lock);
+ return fi;
+
+err_inval:
+ err = -EINVAL;
+
+failure:
+ if (fi) {
+ fi->fib_dead = 1;
+ free_fib_info(fi);
+ }
+
+ return ERR_PTR(err);
+}
+
+int fib_dump_info(struct sk_buff *skb, u32 portid, u32 seq, int event,
+ u32 tb_id, u8 type, __be32 dst, int dst_len, u8 tos,
+ struct fib_info *fi, unsigned int flags)
+{
+ struct nlmsghdr *nlh;
+ struct rtmsg *rtm;
+
+ nlh = nlmsg_put(skb, portid, 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;
+ if (tb_id < 256)
+ rtm->rtm_table = tb_id;
+ else
+ rtm->rtm_table = RT_TABLE_COMPAT;
+ if (nla_put_u32(skb, RTA_TABLE, tb_id))
+ goto nla_put_failure;
+ rtm->rtm_type = type;
+ rtm->rtm_flags = fi->fib_flags;
+ rtm->rtm_scope = fi->fib_scope;
+ rtm->rtm_protocol = fi->fib_protocol;
+
+ if (rtm->rtm_dst_len &&
+ nla_put_be32(skb, RTA_DST, dst))
+ goto nla_put_failure;
+ if (fi->fib_priority &&
+ nla_put_u32(skb, RTA_PRIORITY, fi->fib_priority))
+ goto nla_put_failure;
+ if (rtnetlink_put_metrics(skb, fi->fib_metrics) < 0)
+ goto nla_put_failure;
+
+ if (fi->fib_prefsrc &&
+ nla_put_be32(skb, RTA_PREFSRC, fi->fib_prefsrc))
+ goto nla_put_failure;
+ if (fi->fib_nhs == 1) {
+ if (fi->fib_nh->nh_gw &&
+ nla_put_be32(skb, RTA_GATEWAY, fi->fib_nh->nh_gw))
+ goto nla_put_failure;
+ if (fi->fib_nh->nh_oif &&
+ nla_put_u32(skb, RTA_OIF, fi->fib_nh->nh_oif))
+ goto nla_put_failure;
+#ifdef CONFIG_IP_ROUTE_CLASSID
+ if (fi->fib_nh[0].nh_tclassid &&
+ nla_put_u32(skb, RTA_FLOW, fi->fib_nh[0].nh_tclassid))
+ goto nla_put_failure;
+#endif
+ }
+#ifdef CONFIG_IP_ROUTE_MULTIPATH
+ if (fi->fib_nhs > 1) {
+ struct rtnexthop *rtnh;
+ struct nlattr *mp;
+
+ mp = nla_nest_start(skb, RTA_MULTIPATH);
+ if (mp == NULL)
+ goto nla_put_failure;
+
+ for_nexthops(fi) {
+ 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 &&
+ nla_put_be32(skb, RTA_GATEWAY, nh->nh_gw))
+ goto nla_put_failure;
+#ifdef CONFIG_IP_ROUTE_CLASSID
+ if (nh->nh_tclassid &&
+ nla_put_u32(skb, RTA_FLOW, nh->nh_tclassid))
+ goto nla_put_failure;
+#endif
+ /* length of rtnetlink header + attributes */
+ rtnh->rtnh_len = nlmsg_get_pos(skb) - (void *) rtnh;
+ } endfor_nexthops(fi);
+
+ nla_nest_end(skb, mp);
+ }
+#endif
+ return nlmsg_end(skb, nlh);
+
+nla_put_failure:
+ nlmsg_cancel(skb, nlh);
+ return -EMSGSIZE;
+}
+
+/*
+ * 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 ret = 0;
+ unsigned int hash = fib_laddr_hashfn(local);
+ struct hlist_head *head = &fib_info_laddrhash[hash];
+ struct fib_info *fi;
+
+ if (fib_info_laddrhash == NULL || local == 0)
+ return 0;
+
+ hlist_for_each_entry(fi, head, fib_lhash) {
+ if (!net_eq(fi->fib_net, net))
+ continue;
+ if (fi->fib_prefsrc == local) {
+ fi->fib_flags |= RTNH_F_DEAD;
+ ret++;
+ }
+ }
+ return ret;
+}
+
+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 fib_nh *nh;
+
+ if (force)
+ scope = -1;
+
+ hlist_for_each_entry(nh, 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 (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 -= nexthop_nh->nh_power;
+ nexthop_nh->nh_power = 0;
+ spin_unlock_bh(&fib_multipath_lock);
+#endif
+ dead++;
+ }
+#ifdef CONFIG_IP_ROUTE_MULTIPATH
+ 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++;
+ }
+ }
+
+ return ret;
+}
+
+/* Must be invoked inside of an RCU protected region. */
+void fib_select_default(struct fib_result *res)
+{
+ struct fib_info *fi = NULL, *last_resort = NULL;
+ struct list_head *fa_head = res->fa_head;
+ struct fib_table *tb = res->table;
+ int order = -1, last_idx = -1;
+ struct fib_alias *fa;
+
+ list_for_each_entry_rcu(fa, fa_head, fa_list) {
+ struct fib_info *next_fi = fa->fa_info;
+
+ if (next_fi->fib_scope != res->scope ||
+ fa->fa_type != RTN_UNICAST)
+ continue;
+
+ if (next_fi->fib_priority > res->fi->fib_priority)
+ break;
+ if (!next_fi->fib_nh[0].nh_gw ||
+ next_fi->fib_nh[0].nh_scope != RT_SCOPE_LINK)
+ continue;
+
+ fib_alias_accessed(fa);
+
+ if (fi == NULL) {
+ if (next_fi != res->fi)
+ break;
+ } else if (!fib_detect_death(fi, order, &last_resort,
+ &last_idx, tb->tb_default)) {
+ fib_result_assign(res, fi);
+ tb->tb_default = order;
+ goto out;
+ }
+ fi = next_fi;
+ order++;
+ }
+
+ if (order <= 0 || fi == NULL) {
+ tb->tb_default = -1;
+ goto out;
+ }
+
+ if (!fib_detect_death(fi, order, &last_resort, &last_idx,
+ tb->tb_default)) {
+ fib_result_assign(res, fi);
+ tb->tb_default = order;
+ goto out;
+ }
+
+ if (last_idx >= 0)
+ fib_result_assign(res, last_resort);
+ tb->tb_default = last_idx;
+out:
+ return;
+}
+
+#ifdef CONFIG_IP_ROUTE_MULTIPATH
+
+/*
+ * 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;
+ unsigned int hash;
+ struct hlist_head *head;
+ struct fib_nh *nh;
+ int ret;
+
+ if (!(dev->flags & IFF_UP))
+ return 0;
+
+ prev_fi = NULL;
+ hash = fib_devindex_hashfn(dev->ifindex);
+ head = &fib_info_devhash[hash];
+ ret = 0;
+
+ hlist_for_each_entry(nh, head, nh_hash) {
+ struct fib_info *fi = nh->nh_parent;
+ int alive;
+
+ BUG_ON(!fi->fib_nhs);
+ if (nh->nh_dev != dev || fi == prev_fi)
+ continue;
+
+ prev_fi = fi;
+ alive = 0;
+ change_nexthops(fi) {
+ if (!(nexthop_nh->nh_flags & RTNH_F_DEAD)) {
+ alive++;
+ continue;
+ }
+ if (nexthop_nh->nh_dev == NULL ||
+ !(nexthop_nh->nh_dev->flags & IFF_UP))
+ continue;
+ if (nexthop_nh->nh_dev != dev ||
+ !__in_dev_get_rtnl(dev))
+ continue;
+ alive++;
+ spin_lock_bh(&fib_multipath_lock);
+ nexthop_nh->nh_power = 0;
+ nexthop_nh->nh_flags &= ~RTNH_F_DEAD;
+ spin_unlock_bh(&fib_multipath_lock);
+ } endfor_nexthops(fi)
+
+ if (alive > 0) {
+ fi->fib_flags &= ~RTNH_F_DEAD;
+ ret++;
+ }
+ }
+
+ return ret;
+}
+
+/*
+ * The algorithm is suboptimal, but it provides really
+ * fair weighted route distribution.
+ */
+void fib_select_multipath(struct fib_result *res)
+{
+ struct fib_info *fi = res->fi;
+ int w;
+
+ spin_lock_bh(&fib_multipath_lock);
+ if (fi->fib_power <= 0) {
+ int power = 0;
+ change_nexthops(fi) {
+ 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;
+ if (power <= 0) {
+ spin_unlock_bh(&fib_multipath_lock);
+ /* Race condition: route has just become dead. */
+ res->nh_sel = 0;
+ return;
+ }
+ }
+
+
+ /* w should be random number [0..fi->fib_power-1],
+ * it is pretty bad approximation.
+ */
+
+ w = jiffies % fi->fib_power;
+
+ change_nexthops(fi) {
+ 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);
+ return;
+ }
+ }
+ } endfor_nexthops(fi);
+
+ /* Race condition: route has just become dead. */
+ res->nh_sel = 0;
+ spin_unlock_bh(&fib_multipath_lock);
+}
+#endif
diff --git a/lc-trie.c b/lc-trie.c
index f9b4b55..acce5fd 100644
--- a/lc-trie.c
+++ b/lc-trie.c
@@ -74,12 +74,14 @@
#include <linux/export.h>
#include <net/net_namespace.h>
#include <net/ip.h>
+#include <net/dst.h>
#include <net/protocol.h>
#include <net/route.h>
#include <net/tcp.h>
#include <net/sock.h>
#include <net/ip_fib.h>
#include "fib_lookup.h"
+
#include "lc-trie.h"
#include "bits.h"
@@ -232,6 +234,18 @@ static inline struct lc_trie_key mask_pfx(struct lc_trie_key key, unsigned int c
return mask;
}
+static inline int key_null(struct lc_trie_key key)
+{
+ switch(key.version) {
+ case 4: return NULLv4(key);
+ case 6: return NULLv6(key);
+ }
+
+ pr_err("invalid version: %d\n", key.version);
+ // FIXME find better return value in case of error
+ return -1;
+}
+
static inline struct lc_trie_key key_neg(struct lc_trie_key key)
{
switch(key.version) {
@@ -247,7 +261,7 @@ static inline struct lc_trie_key key_neg(struct lc_trie_key key)
static inline struct lc_trie_key key_or(struct lc_trie_key key1, struct lc_trie_key key2)
{
if (key1.version != key2.version) {
- pr_err("key_subequal: something very wrong here: can't compare IPv4 with IPv6.\n");
+ pr_err("key_or: something very wrong here: can't compare IPv4 with IPv6.\n");
pr_err("key1.version = %d, key2.version = %d\n", key1.version, key2.version);
// FIXME find better return value in case of error
return key1;
@@ -266,7 +280,7 @@ static inline struct lc_trie_key key_or(struct lc_trie_key key1, struct lc_trie_
static inline struct lc_trie_key key_and(struct lc_trie_key key1, struct lc_trie_key key2)
{
if (key1.version != key2.version) {
- pr_err("key_subequal: something very wrong here: can't compare IPv4 with IPv6.\n");
+ pr_err("key_and: something very wrong here: can't compare IPv4 with IPv6.\n");
pr_err("key1.version = %d, key2.version = %d\n", key1.version, key2.version);
// FIXME find better return value in case of error
return key1;
@@ -282,6 +296,25 @@ static inline struct lc_trie_key key_and(struct lc_trie_key key1, struct lc_trie
return key1;
}
+static inline struct lc_trie_key key_xor(struct lc_trie_key key1, struct lc_trie_key key2)
+{
+ if (key1.version != key2.version) {
+ pr_err("key_xor: something very wrong here: can't compare IPv4 with IPv6.\n");
+ pr_err("key1.version = %d, key2.version = %d\n", key1.version, key2.version);
+ // FIXME find better return value in case of error
+ return key1;
+ }
+
+ switch(key1.version) {
+ case 4: return XORv4(key1, key2);
+ case 6: return XORv6(key1, key2);
+ }
+
+ pr_err("invalid version: %d\n", key1.version);
+ // FIXME find better return value in case of error
+ return key1;
+}
+
static inline unsigned int key_extract_bits(struct lc_trie_key key, unsigned int pos, unsigned int bits)
{
switch(key.version) {
@@ -450,6 +483,10 @@ static int key_mismatch(unsigned int pos, struct lc_trie_key key1, struct lc_tri
static inline void check_tnode(const struct lc_trie_inode *tn)
{
+ if (tn && tn->pos + tn->bits > 32) {
+ pr_info("check_tnode: node too big: tn->pos = %u, tn->bits = %u, tn->pos+tn->bits = %u", tn->pos, tn->bits, tn->pos+tn->bits);
+ }
+
WARN_ON(tn && tn->pos+tn->bits > 32);
}
@@ -553,7 +590,7 @@ static struct lc_trie_leaf_info *leaf_info_new(int plen)
struct lc_trie_leaf_info *li = kmalloc(sizeof(struct lc_trie_leaf_info), GFP_KERNEL);
if (li) {
li->plen = plen;
- li->mask_plen = ntohl(inet_make_mask(plen));
+ li->mask_plen = cidr_to_mask_v4(plen);
INIT_LIST_HEAD(&li->falh);
}
return li;
@@ -573,8 +610,9 @@ static struct lc_trie_inode *tnode_new(struct lc_trie_key key, int pos, int bits
tn->empty_children = 1<<bits;
}
- pr_debug("AT %p s=%zu %zu\n", tn, sizeof(struct lc_trie_inode),
- sizeof(struct lc_trie_node *) << bits);
+ // TODO re-enable this debug statement
+ //pr_debug("AT %p s=%zu %zu\n", tn, sizeof(struct lc_trie_inode),
+ // sizeof(struct lc_trie_node *) << bits);
return tn;
}
@@ -644,8 +682,9 @@ static struct lc_trie_node *resize(struct lc_trie *t, struct lc_trie_inode *tn)
if (!tn)
return NULL;
- pr_debug("In tnode_resize %p inflate_threshold=%d threshold=%d\n",
- tn, inflate_threshold, halve_threshold);
+ // TODO re-enable this debug statement
+ //pr_debug("In tnode_resize %p inflate_threshold=%d threshold=%d\n",
+ // tn, inflate_threshold, halve_threshold);
/* No children */
if (tn->empty_children == tnode_child_length(tn)) {
@@ -1131,7 +1170,7 @@ struct list_head *fib_insert_node(struct lc_trie *t, struct lc_trie_key key, int
n = rtnl_dereference(t->trie);
/* If we point to NULL, stop. Either the tree is empty and we should
- * just put a new leaf in if, or we have reached an empty child slot,
+ * just put a new leaf in it, or we have reached an empty child slot,
* and we should just put our new leaf in that.
* If we point to a T_TNODE, check if it matches our key. Note that
* a T_TNODE might be skipping any number of bits - its 'pos' need
@@ -1263,24 +1302,438 @@ done:
return fa_head;
}
+/*
+ * Caller must hold RTNL.
+ */
+int fib_table_insert(struct fib_table *tb, struct fib_config *cfg)
+{
+ struct in_addr ip;
+ struct lc_trie *t = (struct lc_trie *) tb->tb_data;
+ struct fib_alias *fa, *new_fa;
+ struct list_head *fa_head = NULL;
+ struct fib_info *fi;
+ int plen = cfg->fc_dst_len;
+ u8 tos = cfg->fc_tos;
+ struct lc_trie_key key, mask;
+ int err;
+ struct lc_trie_leaf *l;
+ if (plen > 32)
+ return -EINVAL;
-/* ... snip snip ... */
+ ip.s_addr = cfg->fc_dst;
+ key = ip_to_key_v4(ip);
+
+ // TODO
+ //pr_debug("Insert table=%u %08x/%d\n", tb->tb_id, key, plen);
+
+ mask = cidr_to_mask_v4(plen);
+
+ if (!key_null(key_and(key, key_neg(mask))))
+ return -EINVAL;
+
+ key = key_and(key, mask);
+
+ fi = fib_create_info(cfg);
+ if (IS_ERR(fi)) {
+ err = PTR_ERR(fi);
+ goto err;
+ }
+
+ l = fib_find_node(t, key);
+ fa = NULL;
+
+ if (l) {
+ fa_head = get_fa_head(l, plen);
+ fa = fib_find_alias(fa_head, tos, fi->fib_priority);
+ }
+
+ /* Now fa, if non-NULL, points to the first fib alias
+ * with the same keys [prefix,tos,priority], if such key already
+ * exists or to the node before which we will insert new one.
+ *
+ * If fa is NULL, we will need to allocate a new one and
+ * insert to the head of f.
+ *
+ * If f is NULL, no fib node matched the destination key
+ * and we need to allocate a new one of those as well.
+ */
+
+ if (fa && fa->fa_tos == tos &&
+ fa->fa_info->fib_priority == fi->fib_priority) {
+ struct fib_alias *fa_first, *fa_match;
+
+ err = -EEXIST;
+ if (cfg->fc_nlflags & NLM_F_EXCL)
+ goto out;
+
+ /* We have 2 goals:
+ * 1. Find exact match for type, scope, fib_info to avoid
+ * duplicate routes
+ * 2. Find next 'fa' (or head), NLM_F_APPEND inserts before it
+ */
+ fa_match = NULL;
+ fa_first = fa;
+ fa = list_entry(fa->fa_list.prev, struct fib_alias, fa_list);
+ list_for_each_entry_continue(fa, fa_head, fa_list) {
+ if (fa->fa_tos != tos)
+ break;
+ if (fa->fa_info->fib_priority != fi->fib_priority)
+ break;
+ if (fa->fa_type == cfg->fc_type &&
+ fa->fa_info == fi) {
+ fa_match = fa;
+ break;
+ }
+ }
+
+ if (cfg->fc_nlflags & NLM_F_REPLACE) {
+ struct fib_info *fi_drop;
+ u8 state;
+
+ fa = fa_first;
+ if (fa_match) {
+ if (fa == fa_match)
+ err = 0;
+ goto out;
+ }
+ err = -ENOBUFS;
+ new_fa = kmem_cache_alloc(fn_alias_kmem, GFP_KERNEL);
+ if (new_fa == NULL)
+ goto out;
+
+ fi_drop = fa->fa_info;
+ new_fa->fa_tos = fa->fa_tos;
+ new_fa->fa_info = fi;
+ new_fa->fa_type = cfg->fc_type;
+ state = fa->fa_state;
+ new_fa->fa_state = state & ~FA_S_ACCESSED;
+
+ list_replace_rcu(&fa->fa_list, &new_fa->fa_list);
+ alias_free_mem_rcu(fa);
+
+ fib_release_info(fi_drop);
+ // TODO
+ //if (state & FA_S_ACCESSED)
+ // rt_cache_flush(cfg->fc_nlinfo.nl_net);
+ // TODO
+ //rtmsg_fib(RTM_NEWROUTE, htonl(key), new_fa, plen,
+ // tb->tb_id, &cfg->fc_nlinfo, NLM_F_REPLACE);
+
+ goto succeeded;
+ }
+ /* Error if we find a perfect match which
+ * uses the same scope, type, and nexthop
+ * information.
+ */
+ if (fa_match)
+ goto out;
+
+ if (!(cfg->fc_nlflags & NLM_F_APPEND))
+ fa = fa_first;
+ }
+ err = -ENOENT;
+ if (!(cfg->fc_nlflags & NLM_F_CREATE))
+ goto out;
+
+ err = -ENOBUFS;
+ new_fa = kmem_cache_alloc(fn_alias_kmem, GFP_KERNEL);
+ if (new_fa == NULL)
+ goto out;
+
+ new_fa->fa_info = fi;
+ new_fa->fa_tos = tos;
+ new_fa->fa_type = cfg->fc_type;
+ new_fa->fa_state = 0;
+ /*
+ * Insert new entry to the list.
+ */
+
+ if (!fa_head) {
+ fa_head = fib_insert_node(t, key, plen);
+ if (unlikely(!fa_head)) {
+ err = -ENOMEM;
+ goto out_free_new_fa;
+ }
+ }
+
+ if (!plen)
+ tb->tb_num_default++;
+
+ list_add_tail_rcu(&new_fa->fa_list,
+ (fa ? &fa->fa_list : fa_head));
+
+ // TODO
+ //rt_cache_flush(cfg->fc_nlinfo.nl_net);
+ // TODO
+ //rtmsg_fib(RTM_NEWROUTE, htonl(key), new_fa, plen, tb->tb_id,
+ // &cfg->fc_nlinfo, 0);
+succeeded:
+ return 0;
+
+out_free_new_fa:
+ kmem_cache_free(fn_alias_kmem, new_fa);
+out:
+ fib_release_info(fi);
+err:
+ return err;
+}
+
+/* should be called with rcu_read_lock */
+static int check_leaf(struct fib_table *tb, struct lc_trie *t, struct lc_trie_leaf *l,
+ struct lc_trie_key key, const struct flowi4 *flp,
+ struct fib_result *res, int fib_flags)
+{
+ struct lc_trie_leaf_info *li;
+ struct hlist_head *hhead = &l->list;
+
+ hlist_for_each_entry_rcu(li, hhead, hlist) {
+ struct fib_alias *fa;
+
+ if (!key_equal(l->key, (key_and(key, li->mask_plen))))
+ continue;
+
+ list_for_each_entry_rcu(fa, &li->falh, fa_list) {
+ struct fib_info *fi = fa->fa_info;
+ int nhsel, err;
+
+ if (fa->fa_tos && fa->fa_tos != flp->flowi4_tos)
+ continue;
+ if (fi->fib_dead)
+ continue;
+ if (fa->fa_info->fib_scope < flp->flowi4_scope)
+ continue;
+ fib_alias_accessed(fa);
+ err = fib_props[fa->fa_type].error;
+ if (err) {
+ return err;
+ }
+ if (fi->fib_flags & RTNH_F_DEAD)
+ continue;
+ for (nhsel = 0; nhsel < fi->fib_nhs; nhsel++) {
+ const struct fib_nh *nh = &fi->fib_nh[nhsel];
+
+ if (nh->nh_flags & RTNH_F_DEAD)
+ continue;
+ if (flp->flowi4_oif && flp->flowi4_oif != nh->nh_oif)
+ continue;
+
+ res->prefixlen = li->plen;
+ res->nh_sel = nhsel;
+ res->type = fa->fa_type;
+ res->scope = fa->fa_info->fib_scope;
+ res->fi = fi;
+ res->table = tb;
+ res->fa_head = &li->falh;
+ if (!(fib_flags & FIB_LOOKUP_NOREF))
+ atomic_inc(&fi->fib_clntref);
+ return 0;
+ }
+ }
+ }
+
+ return 1;
+}
+
+int fib_table_lookup(struct fib_table *tb, const struct flowi4 *flp,
+ struct fib_result *res, int fib_flags)
+{
+ struct in_addr ip;
+ struct lc_trie *t = (struct lc_trie *) tb->tb_data;
+ int ret;
+ struct lc_trie_node *n;
+ struct lc_trie_inode *pn;
+ unsigned int pos, bits;
+ struct lc_trie_key key;
+ unsigned int chopped_off;
+ unsigned int cindex = 0;
+ unsigned int current_prefix_length = KEYLENGTH;
+ struct lc_trie_inode *cn;
+ struct lc_trie_key pref_mismatch;
+
+ ip.s_addr = flp->daddr;
+ key = ip_to_key_v4(ip);
+
+ rcu_read_lock();
+
+ n = rcu_dereference(t->trie);
+ if (!n)
+ goto failed;
+
+ /* Just a leaf? */
+ if (IS_LEAF(n)) {
+ ret = check_leaf(tb, t, (struct lc_trie_leaf *)n, key, flp, res, fib_flags);
+ goto found;
+ }
+
+ pn = (struct lc_trie_inode *) n;
+ chopped_off = 0;
+
+ while (pn) {
+ pos = pn->pos;
+ bits = pn->bits;
+
+ if (!chopped_off)
+ cindex = key_extract_bits(mask_pfx(key, current_prefix_length),
+ pos, bits);
+
+ n = tnode_get_child_rcu(pn, cindex);
+
+ if (n == NULL) {
+ goto backtrace;
+ }
+
+ if (IS_LEAF(n)) {
+ ret = check_leaf(tb, t, (struct lc_trie_leaf *)n, key, flp, res, fib_flags);
+ if (ret > 0)
+ goto backtrace;
+ goto found;
+ }
+
+ cn = (struct lc_trie_inode *)n;
+
+ /*
+ * It's a tnode, and we can do some extra checks here if we
+ * like, to avoid descending into a dead-end branch.
+ * This tnode is in the parent's child array at index
+ * key[p_pos..p_pos+p_bits] but potentially with some bits
+ * chopped off, so in reality the index may be just a
+ * subprefix, padded with zero at the end.
+ * We can also take a look at any skipped bits in this
+ * tnode - everything up to p_pos is supposed to be ok,
+ * and the non-chopped bits of the index (se previous
+ * paragraph) are also guaranteed ok, but the rest is
+ * considered unknown.
+ *
+ * The skipped bits are key[pos+bits..cn->pos].
+ */
+ /* If current_prefix_length < pos+bits, we are already doing
+ * actual prefix matching, which means everything from
+ * pos+(bits-chopped_off) onward must be zero along some
+ * branch of this subtree - otherwise there is *no* valid
+ * prefix present. Here we can only check the skipped
+ * bits. Remember, since we have already indexed into the
+ * parent's child array, we know that the bits we chopped of
+ * *are* zero.
+ */
+
+ /* NOTA BENE: Checking only skipped bits
+ for the new node here */
+
+ if (current_prefix_length < pos+bits) {
+ if (key_extract_bits(cn->key, current_prefix_length,
+ cn->pos - current_prefix_length)
+ || !(cn->child[0]))
+ goto backtrace;
+ }
+ /*
+ * If chopped_off=0, the index is fully validated and we
+ * only need to look at the skipped bits for this, the new,
+ * tnode. What we actually want to do is to find out if
+ * these skipped bits match our key perfectly, or if we will
+ * have to count on finding a matching prefix further down,
+ * because if we do, we would like to have some way of
+ * verifying the existence of such a prefix at this point.
+ */
+
+ /* The only thing we can do at this point is to verify that
+ * any such matching prefix can indeed be a prefix to our
+ * key, and if the bits in the node we are inspecting that
+ * do not match our key are not ZERO, this cannot be true.
+ * Thus, find out where there is a mismatch (before cn->pos)
+ * and verify that all the mismatching bits are zero in the
+ * new tnode's key.
+ */
+
+ /*
+ * Note: We aren't very concerned about the piece of
+ * the key that precede pn->pos+pn->bits, since these
+ * have already been checked. The bits after cn->pos
+ * aren't checked since these are by definition
+ * "unknown" at this point. Thus, what we want to see
+ * is if we are about to enter the "prefix matching"
+ * state, and in that case verify that the skipped
+ * bits that will prevail throughout this subtree are
+ * zero, as they have to be if we are to find a
+ * matching prefix.
+ */
+
+ pref_mismatch = mask_pfx(key_xor(cn->key, key), cn->pos);
+
+ /*
+ * In short: If skipped bits in this node do not match
+ * the search key, enter the "prefix matching"
+ * state.directly.
+ */
+ if (!key_null(pref_mismatch)) {
+ /* fls(x) = __fls(x) + 1 */
+ // FIXME
+ //int mp = KEYLENGTH - __fls(pref_mismatch) - 1;
+ int mp = KEYLENGTH - 1;
+
+ if (key_extract_bits(cn->key, mp, cn->pos - mp) != 0)
+ goto backtrace;
+
+ if (current_prefix_length >= cn->pos)
+ current_prefix_length = mp;
+ }
+
+ pn = (struct lc_trie_inode *)n; /* Descend */
+ chopped_off = 0;
+ continue;
+
+backtrace:
+ chopped_off++;
+
+ /* As zero don't change the child key (cindex) */
+ while ((chopped_off <= pn->bits)
+ && !(cindex & (1<<(chopped_off-1))))
+ chopped_off++;
+
+ /* Decrease current_... with bits chopped off */
+ if (current_prefix_length > pn->pos + pn->bits - chopped_off)
+ current_prefix_length = pn->pos + pn->bits
+ - chopped_off;
+
+ /*
+ * Either we do the actual chop off according or if we have
+ * chopped off all bits in this tnode walk up to our parent.
+ */
+
+ if (chopped_off <= pn->bits) {
+ cindex &= ~(1 << (chopped_off-1));
+ } else {
+ struct lc_trie_inode *parent = node_parent_rcu((struct lc_trie_node *) pn);
+ if (!parent)
+ goto failed;
+
+ /* Get Child's index */
+ cindex = key_extract_bits(pn->key, parent->pos, parent->bits);
+ pn = parent;
+ chopped_off = 0;
+ goto backtrace;
+ }
+ }
+failed:
+ ret = 1;
+found:
+ rcu_read_unlock();
+ return ret;
+}
/*
* Remove the leaf and return parent.
*/
-/*
-void trie_leaf_remove(struct lc_trie *t, struct lc_trie_leaf *l)
+static void trie_leaf_remove(struct lc_trie *t, struct lc_trie_leaf *l)
{
struct lc_trie_inode *tp = node_parent((struct lc_trie_node *) l);
pr_debug("entering trie_leaf_remove(%p)\n", l);
if (tp) {
- struct lc_trie_key cindex = key_extract_bits(l->key, tp->pos, tp->bits);
+ unsigned int cindex = key_extract_bits(l->key, tp->pos, tp->bits);
put_child(tp, cindex, NULL);
trie_rebalance(t, tp);
} else
@@ -1288,9 +1741,103 @@ void trie_leaf_remove(struct lc_trie *t, struct lc_trie_leaf *l)
free_leaf(l);
}
-*/
/*
+ * Caller must hold RTNL.
+ */
+int fib_table_delete(struct fib_table *tb, struct fib_config *cfg)
+{
+ struct in_addr ip;
+ struct lc_trie *t = (struct lc_trie *) tb->tb_data;
+ struct lc_trie_key key, mask;
+ int plen = cfg->fc_dst_len;
+ u8 tos = cfg->fc_tos;
+ struct fib_alias *fa, *fa_to_delete;
+ struct list_head *fa_head;
+ struct lc_trie_leaf *l;
+ struct lc_trie_leaf_info *li;
+
+ if (plen > 32)
+ return -EINVAL;
+
+ ip.s_addr = cfg->fc_dst;
+ key = ip_to_key_v4(ip);
+ mask = cidr_to_mask_v4(plen);
+
+ if (!key_null(key_and(key, key_neg(mask))))
+ return -EINVAL;
+
+ key = ANDv4(key, mask);
+ l = fib_find_node(t, key);
+
+ if (!l)
+ return -ESRCH;
+
+ li = find_leaf_info(l, plen);
+
+ if (!li)
+ return -ESRCH;
+
+ fa_head = &li->falh;
+ fa = fib_find_alias(fa_head, tos, 0);
+
+ if (!fa)
+ return -ESRCH;
+
+ // TODO
+ //pr_debug("Deleting %08x/%d tos=%d t=%p\n", key, plen, tos, t);
+
+ fa_to_delete = NULL;
+ fa = list_entry(fa->fa_list.prev, struct fib_alias, fa_list);
+ list_for_each_entry_continue(fa, fa_head, fa_list) {
+ struct fib_info *fi = fa->fa_info;
+
+ if (fa->fa_tos != tos)
+ break;
+
+ if ((!cfg->fc_type || fa->fa_type == cfg->fc_type) &&
+ (cfg->fc_scope == RT_SCOPE_NOWHERE ||
+ fa->fa_info->fib_scope == cfg->fc_scope) &&
+ (!cfg->fc_prefsrc ||
+ fi->fib_prefsrc == cfg->fc_prefsrc) &&
+ (!cfg->fc_protocol ||
+ fi->fib_protocol == cfg->fc_protocol) &&
+ fib_nh_match(cfg, fi) == 0) {
+ fa_to_delete = fa;
+ break;
+ }
+ }
+
+ if (!fa_to_delete)
+ return -ESRCH;
+
+ fa = fa_to_delete;
+ // FIXME
+ //rtmsg_fib(RTM_DELROUTE, htonl(key), fa, plen, tb->tb_id,
+ // &cfg->fc_nlinfo, 0);
+
+ list_del_rcu(&fa->fa_list);
+
+ if (!plen)
+ tb->tb_num_default--;
+
+ if (list_empty(fa_head)) {
+ hlist_del_rcu(&li->hlist);
+ free_leaf_info(li);
+ }
+
+ if (hlist_empty(&l->list))
+ trie_leaf_remove(t, l);
+
+ // TODO
+ //if (fa->fa_state & FA_S_ACCESSED)
+ // rt_cache_flush(cfg->fc_nlinfo.nl_net);
+
+ fib_release_info(fa->fa_info);
+ alias_free_mem_rcu(fa);
+ return 0;
+}
+
static int trie_flush_list(struct list_head *head)
{
struct fib_alias *fa, *fa_node;
@@ -1308,9 +1855,7 @@ static int trie_flush_list(struct list_head *head)
}
return found;
}
-*/
-/*
static int trie_flush_leaf(struct lc_trie_leaf *l)
{
int found = 0;
@@ -1328,17 +1873,15 @@ static int trie_flush_leaf(struct lc_trie_leaf *l)
}
return found;
}
-*/
/*
* Scan for the next right leaf starting at node p->child[idx]
* Since we have back pointer, no recursion necessary.
*/
-/*
static struct lc_trie_leaf *leaf_walk_rcu(struct lc_trie_inode *p, struct lc_trie_node *c)
{
do {
- struct lc_trie_key idx;
+ unsigned int idx;
if (c)
idx = key_extract_bits(c->key, p->pos, p->bits) + 1;
@@ -1353,15 +1896,102 @@ static struct lc_trie_leaf *leaf_walk_rcu(struct lc_trie_inode *p, struct lc_tri
if (IS_LEAF(c))
return (struct lc_trie_leaf *) c;
- // Rescan start scanning in new node
- p = (struct lc_trie_inode *) c;
+ /* Rescan start scanning in new node */
+ p = (struct lc_trie_inode *)c;
idx = 0;
}
- // Node empty, walk back up to parent
+ /* Node empty, walk back up to parent */
c = (struct lc_trie_node *) p;
} while ((p = node_parent_rcu(c)) != NULL);
- return NULL; // Root of trie
+ return NULL; /* Root of trie */
}
-*/
+
+static struct lc_trie_leaf *trie_firstleaf(struct lc_trie *t)
+{
+ struct lc_trie_inode *n = (struct lc_trie_inode *)rcu_dereference_rtnl(t->trie);
+
+ if (!n)
+ return NULL;
+
+ if (IS_LEAF(n)) /* trie is just a leaf */
+ return (struct lc_trie_leaf *) n;
+
+ return leaf_walk_rcu(n, NULL);
+}
+
+static struct lc_trie_leaf *trie_nextleaf(struct lc_trie_leaf *l)
+{
+ struct lc_trie_node *c = (struct lc_trie_node *) l;
+ struct lc_trie_inode *p = node_parent_rcu(c);
+
+ if (!p)
+ return NULL; /* trie with just one leaf */
+
+ return leaf_walk_rcu(p, c);
+}
+
+/*
+ * Caller must hold RTNL.
+ */
+int fib_table_flush(struct fib_table *tb)
+{
+ struct lc_trie *t = (struct lc_trie *) tb->tb_data;
+ struct lc_trie_leaf *l, *ll = NULL;
+ int found = 0;
+
+ for (l = trie_firstleaf(t); l; l = trie_nextleaf(l)) {
+ found += trie_flush_leaf(l);
+
+ if (ll && hlist_empty(&ll->list))
+ trie_leaf_remove(t, ll);
+ ll = l;
+ }
+
+ if (ll && hlist_empty(&ll->list))
+ trie_leaf_remove(t, ll);
+
+ pr_debug("trie_flush found=%d\n", found);
+ return found;
+}
+
+void fib_free_table(struct fib_table *tb)
+{
+ kfree(tb);
+}
+
+void __init fib_trie_init(void)
+{
+ fn_alias_kmem = kmem_cache_create("ip_fib_alias",
+ sizeof(struct fib_alias),
+ 0, SLAB_PANIC, NULL);
+
+ trie_leaf_kmem = kmem_cache_create("ip_fib_trie",
+ max(sizeof(struct lc_trie_leaf),
+ sizeof(struct lc_trie_leaf_info)),
+ 0, SLAB_PANIC, NULL);
+}
+
+
+struct fib_table *fib_trie_table(u32 id)
+{
+ struct fib_table *tb;
+ struct lc_trie *t;
+
+ tb = kmalloc(sizeof(struct fib_table) + sizeof(struct lc_trie),
+ GFP_KERNEL);
+ if (tb == NULL)
+ return NULL;
+
+ tb->tb_id = id;
+ tb->tb_default = -1;
+ tb->tb_num_default = 0;
+
+ t = (struct lc_trie *) tb->tb_data;
+ memset(t, 0, sizeof(*t));
+
+ return tb;
+}
+
+/* ... snip snip ... */
diff --git a/lc-trie.h b/lc-trie.h
index 117bcdc..ff44ad5 100644
--- a/lc-trie.h
+++ b/lc-trie.h
@@ -5,7 +5,7 @@
#include "userland.h"
#endif
-#include "routing-table.h"
+//#include "routing-table.h"
#define LC_TRIE_NODE 1
#define LC_TRIE_INTERNAL_NODE 2
@@ -43,7 +43,7 @@ struct lc_trie_leaf {
struct lc_trie_leaf_info {
struct hlist_node hlist;
int plen;
- u32 mask_plen; /* ntohl(inet_make_mask(plen)) */
+ struct lc_trie_key mask_plen;
struct list_head falh;
struct rcu_head rcu;
};
@@ -66,10 +66,27 @@ struct lc_trie {
struct lc_trie_node __rcu *trie;
};
-struct list_head *fib_insert_node(struct lc_trie *t, struct lc_trie_key key, int plen);
+void __init fib_trie_init(void);
-struct lc_trie_leaf *fib_find_node(struct lc_trie *t, struct lc_trie_key key);
+static inline struct lc_trie_key ip_to_key_v4(struct in_addr addr)
+{
+ struct lc_trie_key key;
+ memset(&key, 0, sizeof(key));
+ key.version = 4;
+ key.ip.v4.addr = ntohl(addr.s_addr);
+ return key;
+}
-void trie_leaf_remove(struct lc_trie *t, struct lc_trie_leaf *l);
+static inline struct lc_trie_key ip_to_key_v6(struct in6_addr addr)
+{
+ struct lc_trie_key key;
+ uint64_t *ptr = (void *)&addr.s6_addr;
+ memset(&key, 0, sizeof(key));
+ key.version = 6;
+ key.ip.v6.hi = ntohl(*ptr);
+ ptr++;
+ key.ip.v6.lo = ntohl(*ptr);
+ return key;
+}
#endif /* LC_TRIE_H */
diff --git a/routing-table.c b/routing-table.c
index 6a7229f..242d29b 100644
--- a/routing-table.c
+++ b/routing-table.c
@@ -10,26 +10,8 @@
#include "lc-trie.h"
#include "routing-table.h"
-static inline struct lc_trie_key ip_to_key_v4(struct in_addr addr)
-{
- struct lc_trie_key key;
- memset(&key, 0, sizeof(key));
- key.version = 4;
- key.ip.v4.addr = ntohl(addr.s_addr);
- return key;
-}
-
-static inline struct lc_trie_key ip_to_key_v6(struct in6_addr addr)
-{
- struct lc_trie_key key;
- uint64_t *ptr = (void *)&addr.s6_addr;
- memset(&key, 0, sizeof(key));
- key.version = 6;
- key.ip.v6.hi = ntohl(*ptr);
- ptr++;
- key.ip.v6.lo = ntohl(*ptr);
- return key;
-}
+#define TRIE_v4 1
+#define TRIE_v6 2
static inline uint8_t mask_to_cidr_v4(struct lc_trie_key mask)
{
@@ -54,10 +36,13 @@ static inline uint8_t mask_to_cidr_v6(struct lc_trie_key mask)
return cidr;
}
-void routing_table_init(struct routing_table *table)
+void __init routing_table_init(struct routing_table *table)
{
memset(table, 0, sizeof(*table));
INIT_HLIST_HEAD(&table->head);
+ fib_trie_init();
+ table->table_v4 = fib_trie_table(TRIE_v4);
+ table->table_v6 = fib_trie_table(TRIE_v6);
}
void routing_table_free(struct routing_table *table)
@@ -86,9 +71,24 @@ int routing_table_insert_v4(struct routing_table *table, struct in_addr ip, uint
leaf->value = value;
mask_self(leaf);
*/
- fib_insert_node(table->trie_v4, ip_to_key_v4(ip), 0);
- pr_info("Trie at end is: %p\n", table->trie_v4);
- return 0;
+ int err;
+ struct fib_config cfg;
+
+ memset(&cfg, 0, sizeof(cfg));
+
+ cfg.fc_dst_len = cidr;
+ cfg.fc_dst = ip.s_addr;
+ cfg.fc_type = RTN_UNICAST;
+ cfg.fc_scope = RT_SCOPE_NOWHERE;
+ cfg.fc_nlflags = NLM_F_CREATE;
+ cfg.fc_protocol = RTPROT_BOOT;
+
+ err = fib_table_insert(table->table_v4, &cfg);
+ if (err)
+ pr_err("fib_table_insert failed: %d\n", err);
+
+ //pr_info("Trie at end is: %p\n", table->trie_v4.trie);
+ return err;
}
int routing_table_insert_v6(struct routing_table *table, struct in6_addr ip, uint8_t cidr, void *value)
@@ -108,19 +108,54 @@ int routing_table_insert_v6(struct routing_table *table, struct in6_addr ip, uin
leaf->value = value;
mask_self(leaf);
*/
- fib_insert_node(table->trie_v6, ip_to_key_v6(ip), 0);
- pr_info("Trie at end is: %p\n", table->trie_v6);
- return 0;
+ // TODO
+ //fib_insert_node(&table->trie_v6, ip_to_key_v6(ip), 0);
+ //pr_info("Trie at end is: %p\n", table->trie_v6.trie);
+
+ /*
+ int err;
+ struct fib_config cfg;
+
+ memset(&cfg, 0, sizeof(cfg));
+
+ cfg.fc_dst_len = cidr;
+ // TODO
+ //cfg.fc_dst = ip.s_addr;
+ cfg.fc_type = RTN_UNICAST;
+ cfg.fc_scope = RT_SCOPE_NOWHERE;
+ cfg.fc_nlflags = NLM_F_CREATE;
+ cfg.fc_protocol = RTPROT_BOOT;
+
+ err = fib_table_insert(table->table_v6, &cfg);
+ if (err)
+ pr_err("fib_table_insert failed");
+
+ //pr_info("Trie at end is: %p\n", table->trie_v4.trie);
+ return err;
+*/
+ return -1;
}
void *routing_table_lookup_v4(struct routing_table *table, struct in_addr ip)
{
- return fib_find_node(table->trie_v4, ip_to_key_v4(ip));
+ int err;
+ struct fib_result res;
+ struct flowi4 fl4 = { .daddr = ip.s_addr };
+
+ memset(&res, 0, sizeof(res));
+ err = fib_table_lookup(table->table_v4, &fl4, &res, FIB_LOOKUP_NOREF);
+ if (err)
+ pr_err("fib_table_lookup failed");
+
+ // TODO extract value from fib_result
+ return NULL;
}
void *routing_table_lookup_v6(struct routing_table *table, struct in6_addr ip)
{
- return fib_find_node(table->trie_v6, ip_to_key_v6(ip));
+ // TODO
+ //return fib_find_node(&table->trie_v6, ip_to_key_v6(ip));
+ return NULL;
}
int routing_table_remove_v4(struct routing_table *table, struct in_addr ip, uint8_t cidr)
@@ -133,11 +168,11 @@ int routing_table_remove_v4(struct routing_table *table, struct in_addr ip, uint
key = ip_to_key_v4(ip);
- leaf = lc_trie_lookup(table->trie_v4, key);
+ leaf = lc_trie_lookup(&table->trie_v4, key);
if (!leaf || leaf->cidr != cidr)
return -1;
- node = lc_trie_delete(table->trie_v4, key);
+ node = lc_trie_delete(&table->trie_v4, key);
if (!node)
return -1;
@@ -252,7 +287,7 @@ static inline struct in6_addr ip6(uint32_t a, uint32_t b, uint32_t c, uint32_t d
return ip;
}
-void routing_table_selftest(void)
+void __init routing_table_selftest(void)
{
struct routing_table t;
char a, b, c, d, e, f, g, h, k;
@@ -280,6 +315,7 @@ void routing_table_selftest(void)
k = 9;
#define insert(version, mem, ipa, ipb, ipc, ipd, cidr) routing_table_insert_v##version(&t, ip##version(ipa, ipb, ipc, ipd), cidr, &mem)
+#ifdef CONF_IPV4
insert(4, a, 192, 168, 4, 0, 24);
insert(4, b, 192, 168, 4, 4, 32);
insert(4, c, 192, 168, 0, 0, 16);
@@ -497,6 +533,8 @@ void routing_table_selftest(void)
insert(4, k, 78, 57, 105, 214, 29);
insert(4, k, 253, 165, 46, 100, 30);
insert(4, k, 233, 43, 96, 95, 31);
+#endif /* CONF_IPV4 */
+#ifdef CONF_IPV6
insert(6, k, 0x05c9998b, 0x0013423f, 0xd6cadac5, 0x01f8cfde, 116);
insert(6, k, 0x8d76562f, 0xaad97417, 0x99ce0339, 0xa358a058, 120);
insert(6, k, 0xcf4c59a9, 0x1561f798, 0xf0fcf5ff, 0xc9c16f53, 123);
@@ -697,6 +735,7 @@ void routing_table_selftest(void)
insert(6, k, 0x483185e3, 0xeeaa53f4, 0x81eb96d9, 0xa2164d8c, 121);
insert(6, k, 0xaef25e44, 0x7ad30ebc, 0x7df9cf62, 0xb4c1c51b, 128);
insert(6, k, 0x50f3ff5a, 0x71a6f8e7, 0x78a1158c, 0x92025a4c, 125);
+#endif /* CONF_IPV6 */
#undef insert
#define test(version, mem, ipa, ipb, ipc, ipd) do { \
@@ -708,12 +747,20 @@ void routing_table_selftest(void)
success = false; \
} \
} while (0)
+#ifdef CONF_IPV4
test(4, a, 192, 168, 4, 20);
test(4, a, 192, 168, 4, 0);
test(4, b, 192, 168, 4, 4);
test(4, c, 192, 168, 200, 182);
test(4, c, 192, 95, 5, 68);
test(4, e, 192, 95, 5, 96);
+ test(4, g, 64, 15, 116, 26);
+ test(4, g, 64, 15, 127, 3);
+ test(4, g, 64, 15, 123, 1);
+ test(4, h, 64, 15, 123, 128);
+ test(4, h, 64, 15, 123, 129);
+#endif /* CONF_IPV4 */
+#ifdef CONF_IPV6
test(6, d, 0x26075300, 0x60006b00, 0, 0xc05f0543);
test(6, c, 0x26075300, 0x60006b00, 0, 0xc02e01ee);
test(6, f, 0x26075300, 0x60006b01, 0, 0);
@@ -725,11 +772,7 @@ void routing_table_selftest(void)
test(6, h, 0x24046800, 0x40040800, 0, 0);
test(6, h, 0x24046800, 0x40040800, 0x10101010, 0x10101010);
test(6, a, 0x24046800, 0x40040800, 0xdeadbeef, 0xdeadbeef);
- test(4, g, 64, 15, 116, 26);
- test(4, g, 64, 15, 127, 3);
- test(4, g, 64, 15, 123, 1);
- test(4, h, 64, 15, 123, 128);
- test(4, h, 64, 15, 123, 129);
+#endif /* CONF_IPV6 */
#undef test
if (success)
@@ -750,12 +793,21 @@ void routing_table_selftest(void)
#endif
#define test(version, mem, ipa, ipb, ipc, ipd) routing_table_lookup_v##version(&t, ip##version(ipa, ipb, ipc, ipd))
for (i = 0; i < 10000000; ++i) {
+#ifdef CONF_IPV4
test(4, a, 192, 168, 4, 20);
test(4, a, 192, 168, 4, 0);
test(4, b, 192, 168, 4, 4);
test(4, c, 192, 168, 200, 182);
test(4, c, 192, 95, 5, 68);
test(4, e, 192, 95, 5, 96);
+ test(4, g, 64, 15, 116, 26);
+ test(4, g, 64, 15, 127, 3);
+ test(4, g, 64, 15, 123, 1);
+ test(4, h, 64, 15, 123, 128);
+ test(4, h, 64, 15, 123, 129);
+ test(4, h, 4, 1, 4, 8);
+#endif /* CONF_IPV4 */
+#ifdef CONF_IPV6
test(6, d, 0x26075300, 0x60006b00, 0, 0xc05f0543);
test(6, c, 0x26075300, 0x60006b00, 0, 0xc02e01ee);
test(6, f, 0x26075300, 0x60006b01, 0, 0);
@@ -767,13 +819,8 @@ void routing_table_selftest(void)
test(6, h, 0x24046800, 0x40040800, 0, 0);
test(6, h, 0x24046800, 0x40040800, 0x10101010, 0x10101010);
test(6, a, 0x24046800, 0x40040800, 0xdeadbeef, 0xdeadbeef);
- test(4, g, 64, 15, 116, 26);
- test(4, g, 64, 15, 127, 3);
- test(4, g, 64, 15, 123, 1);
- test(4, h, 64, 15, 123, 128);
- test(4, h, 64, 15, 123, 129);
- test(4, h, 4, 1, 4, 8);
test(6, h, 0xabcdef, 0xe2323, 0x121212, 0x3333);
+#endif /* CONF_IPV6 */
}
#undef test
#if defined(USERLAND)
diff --git a/routing-table.h b/routing-table.h
index e8b0661..81b572f 100644
--- a/routing-table.h
+++ b/routing-table.h
@@ -4,6 +4,8 @@
//#define DEBUG
//#define GIT_REVISION "1.0"
+#define CONF_IPV4
+
#if defined(USERLAND)
#include "userland.h"
#else
@@ -19,12 +21,15 @@
#include <linux/kernel.h>
#include <linux/string.h>
#include <linux/sort.h>
+#include <net/ip_fib.h>
#endif
+#include "lc-trie.h"
+
struct routing_table {
struct hlist_head head;
- struct lc_trie *trie_v4;
- struct lc_trie *trie_v6;
+ struct fib_table *table_v4;
+ struct fib_table *table_v6;
};
void routing_table_init(struct routing_table *table);