patch-2.3.39 linux/net/decnet/dn_fib.c

Next file: linux/net/decnet/dn_neigh.c
Previous file: linux/net/decnet/dn_dev.c
Back to the patch index
Back to the overall index

diff -u --recursive --new-file v2.3.38/linux/net/decnet/dn_fib.c linux/net/decnet/dn_fib.c
@@ -3,13 +3,15 @@
  *              operating system.  DECnet is implemented using the  BSD Socket
  *              interface as the means of communication with the user level.
  *
- *              DECnet Routing Forwarding Information Base
+ *              DECnet Routing Forwarding Information Base (Glue/Info List)
  *
  * Author:      Steve Whitehouse <SteveW@ACM.org>
  *
  *
  * Changes:
  *              Alexey Kuznetsov : SMP locking changes
+ *              Steve Whitehouse : Rewrote it... Well to be more correct, I
+ *                                 copied most of it from the ipv4 fib code.
  *
  */
 #include <linux/config.h>
@@ -30,451 +32,376 @@
 #include <net/neighbour.h>
 #include <net/dst.h>
 #include <net/dn.h>
+#include <net/dn_route.h>
 #include <net/dn_fib.h>
 #include <net/dn_neigh.h>
 #include <net/dn_dev.h>
 
-/*
- * N.B. Some of the functions here should really be inlines, but
- * I'll sort out that when its all working properly, for now the
- * stack frames will be useful for debugging.
- */
-#define DN_NUM_TABLES 255
-#define DN_MIN_TABLE  1
-#define DN_L1_TABLE 1
-#define DN_L2_TABLE 2
-
-#ifdef CONFIG_RTNETLINK
-static int dn_fib_table_dump(struct dn_fib_table *t, struct sk_buff *skb, struct netlink_callback *cb);
-static void dn_rtmsg_fib(int event, int table, struct dn_fib_action *fa, struct nlmsghdr *nlh, struct netlink_skb_parms *req);
-extern int dn_cache_dump(struct sk_buff *skb, struct netlink_callback *cb);
-#endif /* CONFIG_RTNETLINK */
-
-static void dn_fib_del_tree(struct dn_fib_table *t);
 
-static struct dn_fib_table *dn_fib_tables[DN_NUM_TABLES + 1];
-static int dn_fib_allocs = 0;
-static int dn_fib_actions = 0;
+#define for_fib_info() { struct dn_fib_info *fi;\
+	for(fi = dn_fib_info_list; fi; fi = fi->fib_next)
+#define endfor_fib_info() }
 
-static struct dn_fib_node *dn_fib_alloc(void)
-{
-	struct dn_fib_node *fn;
+#define for_nexthops(fi) { int nhsel; const struct dn_fib_nh *nh;\
+	for(nhsel = 0, nh = (fi)->fib_nh; nhsel < (fi)->fib_nhs; nh++, nhsel++)
 
-	fn = kmalloc(sizeof(struct dn_fib_node), GFP_KERNEL);
+#define change_nexthops(fi) { int nhsel; struct dn_fib_nh *nh;\
+	for(nhsel = 0, nh = (struct dn_fib_nh *)((fi)->fib_nh); nhsel < (fi)->fib_nhs; nh++, nhsel++)
 
-	if (fn) {
-		memset(fn, 0, sizeof(struct dn_fib_node));
-		dn_fib_allocs++;
-	}
+#define endfor_nexthops(fi) }
 
-	return fn;
-}
+#ifdef CONFIG_RTNETLINK
+extern int dn_cache_dump(struct sk_buff *skb, struct netlink_callback *cb);
+#endif /* CONFIG_RTNETLINK */
 
 
-static __inline__ void dn_fib_free(struct dn_fib_node *fn)
-{
-	kfree_s(fn, sizeof(struct dn_fib_node));
-	dn_fib_allocs--;
-}
+static struct dn_fib_info *dn_fib_info_list = NULL;
+static rwlock_t dn_fib_info_lock = RW_LOCK_UNLOCKED;
+int dn_fib_info_cnt;
+
+static struct
+{
+	int error;
+	u8 scope;
+} dn_fib_props[RTA_MAX+1] = {
+	{ 0, RT_SCOPE_NOWHERE },		/* RTN_UNSPEC */
+	{ 0, RT_SCOPE_UNIVERSE },		/* RTN_UNICAST */
+	{ 0, RT_SCOPE_HOST },			/* RTN_LOCAL */
+	{ -EINVAL, RT_SCOPE_NOWHERE },		/* RTN_BROADCAST */
+	{ -EINVAL, RT_SCOPE_NOWHERE },		/* RTN_ANYCAST */
+	{ -EINVAL, RT_SCOPE_NOWHERE },		/* RTN_MULTICAST */
+	{ -EINVAL, RT_SCOPE_UNIVERSE },		/* RTN_BLACKHOLE */
+	{ -EHOSTUNREACH, RT_SCOPE_UNIVERSE },	/* RTN_UNREACHABLE */
+	{ -EACCES, RT_SCOPE_UNIVERSE },		/* RTN_PROHIBIT */
+	{ -EAGAIN, RT_SCOPE_UNIVERSE },		/* RTN_THROW */
+	{ -EINVAL, RT_SCOPE_NOWHERE },		/* RTN_NAT */
+	{ -EINVAL, RT_SCOPE_NOWHERE }		/* RTN_XRESOLVE */
+};
 
-static struct dn_fib_action *dn_fib_new_action(void)
+void dn_fib_free_info(struct dn_fib_info *fi)
 {
-	struct dn_fib_action *fa;
-
-	fa = kmalloc(sizeof(struct dn_fib_action), GFP_KERNEL);
-
-	if (fa) {
-		memset(fa, 0, sizeof(struct dn_fib_action));
-		dn_fib_actions++;
+	if (fi->fib_dead == 0) {
+		printk(KERN_DEBUG "DECnet: BUG! Attempt to free alive dn_fib_info\n");
+		return;
 	}
 
-	return fa;
+	change_nexthops(fi) {
+		if (nh->nh_dev)
+			dev_put(nh->nh_dev);
+		nh->nh_dev = NULL;
+	} endfor_nexthops(fi);
+	dn_fib_info_cnt--;
+	kfree(fi);
 }
 
-static __inline__ void dn_fib_del_action(struct dn_fib_action *fa)
+void dn_fib_release_info(struct dn_fib_info *fi)
 {
-	if ((fa->fa_type == RTN_UNICAST) && fa->fa_neigh)
-		neigh_release(fa->fa_neigh);
-
-	kfree_s(fa, sizeof(struct dn_fib_action));
-	dn_fib_actions--;
+	write_lock(&dn_fib_info_lock);
+	if (fi && --fi->fib_treeref == 0) {
+		if (fi->fib_next)
+			fi->fib_next->fib_prev = fi->fib_prev;
+		if (fi->fib_prev)
+			fi->fib_prev->fib_next = fi->fib_next;
+		if (fi == dn_fib_info_list)
+			dn_fib_info_list = fi->fib_next;
+		fi->fib_dead = 1;
+		dn_fib_info_put(fi);
+	}
+	write_unlock(&dn_fib_info_lock);
 }
 
-static struct dn_fib_node *dn_fib_follow(struct dn_fib_node *fn, dn_address key)
+static __inline__ int dn_fib_nh_comp(const struct dn_fib_info *fi, const struct dn_fib_info *ofi)
 {
-	while(fn->fn_action == NULL)
-		fn = DN_FIB_NEXT(fn, key);
+	const struct dn_fib_nh *onh = ofi->fib_nh;
 
-	return fn;
+	for_nexthops(fi) {
+		if (nh->nh_oif != onh->nh_oif ||
+			nh->nh_gw != onh->nh_gw ||
+			nh->nh_scope != onh->nh_scope ||
+			nh->nh_weight != onh->nh_weight ||
+			((nh->nh_flags^onh->nh_flags)&~RTNH_F_DEAD))
+				return -1;
+		onh++;
+	} endfor_nexthops(fi);
+	return 0;
 }
 
-
-static struct dn_fib_node *dn_fib_follow1(struct dn_fib_node *fn, dn_address key)
+static __inline__ struct dn_fib_info *dn_fib_find_info(const struct dn_fib_info *nfi)
 {
-	while((fn->fn_action == NULL) && (((key ^ fn->fn_key) >> fn->fn_shift) == 0))
-		fn = DN_FIB_NEXT(fn, key);
-
-	return fn;
+	for_fib_info() {
+		if (fi->fib_nhs != nfi->fib_nhs)
+			continue;
+		if (nfi->fib_protocol == fi->fib_protocol &&
+			nfi->fib_prefsrc == fi->fib_prefsrc &&
+			nfi->fib_priority == fi->fib_priority &&
+			((nfi->fib_flags^fi->fib_flags)&~RTNH_F_DEAD) == 0 &&
+			(nfi->fib_nhs == 0 || dn_fib_nh_comp(fi, nfi) == 0))
+				return fi;
+	} endfor_fib_info();
+	return NULL;
 }
 
-
-static int dn_fib_table_insert1(struct dn_fib_table *t, struct dn_fib_node *leaf)
+u16 dn_fib_get_attr16(struct rtattr *attr, int attrlen, int type)
 {
-	struct dn_fib_node *fn, *fn1, *fn2;
-	int shift = -1;
-	dn_address match;
-	dn_address cmpmask = 1;
-
-	if (!t->root) {
-		t->root = leaf;
-		t->count++;
-		return 0;
-	}
-
-	fn1 = dn_fib_follow1(t->root, leaf->fn_key);
-	fn2 = fn1->fn_up;
-
-	if (fn1->fn_key == leaf->fn_key)
-		return -EEXIST;
-
-	if ((fn = dn_fib_alloc()) == NULL)
-		return -ENOBUFS;
-
-	fn->fn_key = leaf->fn_key;
-	match = fn1->fn_key ^ fn->fn_key;
-
-	while(match) {
-		match >>= 1;
-		shift++;
-	}
-	cmpmask <<= shift;
-
-	fn->fn_cmpmask = cmpmask;
-	fn->fn_shift   = shift;
-
-	if (fn2) {
-		DN_FIB_NEXT(fn2, fn->fn_key) = fn;
-	} else {
-		t->root = fn;
+	while(RTA_OK(attr,attrlen)) {
+		if (attr->rta_type == type)
+			return *(u16*)RTA_DATA(attr);
+		attr = RTA_NEXT(attr, attrlen);
 	}
 
-	t->count++;
-	fn->fn_up = fn2;
-	DN_FIB_NEXT(fn, fn1->fn_key)  = fn1;
-	DN_FIB_NEXT(fn, leaf->fn_key) = leaf;
-
 	return 0;
 }
 
-static __inline__ int dn_maskcmp(dn_address m1, dn_address m2)
+static int dn_fib_count_nhs(struct rtattr *rta)
 {
-	int cmp = 0;
+	int nhs = 0;
+	struct rtnexthop *nhp = RTA_DATA(rta);
+	int nhlen = RTA_PAYLOAD(rta);
 
-	while(m1 || m2) {
-		if (m1 & 0x8000)
-			cmp++;
-		if (m2 & 0x8000)
-			cmp--;
-		m1 <<= 1;
-		m2 <<= 1;
+	while(nhlen >= (int)sizeof(struct rtnexthop)) {
+		if ((nhlen -= nhp->rtnh_len) < 0)
+			return 0;
+		nhs++;
+		nhp = RTNH_NEXT(nhp);
 	}
 
-	return cmp;
+	return nhs;
 }
 
-
-static int dn_fib_table_insert(struct dn_fib_table *t, struct dn_fib_action *fa)
+static int dn_fib_get_nhs(struct dn_fib_info *fi, const struct rtattr *rta, const struct rtmsg *r)
 {
-	struct dn_fib_node *fn;
-	struct dn_fib_action **fap;
-	int err;
-	int cmp;
-
-	if (t->root && ((fn = dn_fib_follow(t->root, fa->fa_key)) != NULL) && 
-			(fn->fn_key == fa->fa_key))
-		goto add_action;
+	struct rtnexthop *nhp = RTA_DATA(rta);
+	int nhlen = RTA_PAYLOAD(rta);
 
-	if ((fn = dn_fib_alloc()) == NULL)
-		return -ENOBUFS;
+	change_nexthops(fi) {
+		int attrlen = nhlen - sizeof(struct rtnexthop);
+		if (attrlen < 0 || (nhlen -= nhp->rtnh_len) < 0)
+			return -EINVAL;
 
-	fn->fn_key = fa->fa_key;
-	fn->fn_action = fa;
+		nh->nh_flags  = (r->rtm_flags&~0xFF) | nhp->rtnh_flags;
+		nh->nh_oif    = nhp->rtnh_ifindex;
+		nh->nh_weight = nhp->rtnh_hops + 1;
 
-	if ((err = dn_fib_table_insert1(t, fn)) < 0)
-		dn_fib_free(fn);
-
-#ifdef CONFIG_RTNETLINK
-	if (!err)
-		dn_rtmsg_fib(RTM_NEWROUTE, t->n, fa, NULL, NULL);
-#endif /* CONFIG_RTNETLINK */
-
-	return err;
-
-add_action:
-	fap = &fn->fn_action;
-
-	for(; *fap; fap = &((*fap)->fa_next)) {
-		if ((cmp = dn_maskcmp((*fap)->fa_mask, fa->fa_mask)) > 0)
-			break;
-		if (cmp < 0)
-			continue;
-		if ((*fap)->fa_cost > fa->fa_cost)
-			break;
-	}
-
-	fa->fa_next = *fap;
-	*fap = fa;
-
-#ifdef CONFIG_RTNETLINK
-	dn_rtmsg_fib(RTM_NEWROUTE, t->n, fa, NULL, NULL);
-#endif /* CONFIG_RTNETLINK */
+		if (attrlen) {
+			nh->nh_gw = dn_fib_get_attr16(RTNH_DATA(nhp), attrlen, RTA_GATEWAY);
+		}
+		nhp = RTNH_NEXT(nhp);
+	} endfor_nexthops(fi);
 
 	return 0;
 }
 
-static int dn_fib_table_delete1(struct dn_fib_table *t, struct dn_fib_node *fn)
-{
-	struct dn_fib_node *fn1 = fn->fn_up;
-	struct dn_fib_node *fn2;
-	struct dn_fib_node *fn3;
-
-	if (fn == t->root) {
-		t->root = NULL;
-		t->count--;
-		return 0;
-	}
-
-	if (fn1 == NULL)
-		return -EINVAL;
-
-	fn2 = fn1->fn_up;
-	fn3 = DN_FIB_NEXT(fn1, ~fn->fn_key);
 
-	if (fn2)
-		DN_FIB_NEXT(fn2, fn1->fn_key) = fn3;
-	else
-		t->root = fn3;
-
-	fn3->fn_up = fn2;
-
-	dn_fib_free(fn1);
-	t->count--;
-	return 0;
-}
-
-static int dn_fib_table_delete(struct dn_fib_table *t, struct dn_fib_action *fa)
+static int dn_fib_check_nh(const struct rtmsg *r, struct dn_fib_info *fi, struct dn_fib_nh *nh)
 {
-	struct dn_fib_res res;
-	struct dn_fib_node *fn;
-	struct dn_fib_action **fap, *old;
 	int err;
 
-	res.res_type = 0;
-	res.res_addr = fa->fa_key;
-	res.res_mask = fa->fa_mask;
-	res.res_ifindex = fa->fa_ifindex;
-	res.res_proto = fa->fa_proto;
-	res.res_cost = fa->fa_cost;
-
-	if ((err = t->lookup(t, &res)) < 0)
-		return err;
-
-	fn = res.res_fn;
-	fap = &fn->fn_action; 
-	while((*fap) != res.res_fa)
-		 fap = &((*fap)->fa_next);
-	old = *fap;
-	*fap = (*fap)->fa_next;
-
-	if (fn->fn_action == NULL)
-		dn_fib_table_delete1(t, fn);
-
-	if (t->root == NULL)
-		dn_fib_del_tree(t);
+	if (nh->nh_gw) {
+		struct dn_fib_key key;
+		struct dn_fib_res res;
+
+		if (nh->nh_flags&RTNH_F_ONLINK) {
+			struct net_device *dev;
+
+			if (r->rtm_scope >= RT_SCOPE_LINK)
+				return -EINVAL;
+			if ((dev = __dev_get_by_index(nh->nh_oif)) == NULL)
+				return -ENODEV;
+			if (!(dev->flags&IFF_UP))
+				return -ENETDOWN;
+			nh->nh_dev = dev;
+			atomic_inc(&dev->refcnt);
+			nh->nh_scope = RT_SCOPE_LINK;
+			return 0;
+		}
 
-#ifdef CONFIG_RTNETLINK
-	dn_rtmsg_fib(RTM_DELROUTE, t->n, old, NULL, NULL);
-#endif /* CONFIG_RTNETLINK */
+		memset(&key, 0, sizeof(key));
+		key.dst = nh->nh_gw;
+		key.oif = nh->nh_oif;
+		key.scope = r->rtm_scope + 1;
 
-	dn_fib_del_action(old);
+		if (key.scope < RT_SCOPE_LINK)
+			key.scope = RT_SCOPE_LINK;
 
-	return 0;
-}
+		if ((err = dn_fib_lookup(&key, &res)) != 0)
+			return err;
 
-static int dn_fib_search(struct dn_fib_node *fn, struct dn_fib_res *res)
-{
-	struct dn_fib_action *fa = fn->fn_action;
+		nh->nh_scope = res.scope;
+		nh->nh_oif = DN_FIB_RES_OIF(res);
+		nh->nh_dev = DN_FIB_RES_DEV(res);
+		if (nh->nh_dev)
+			atomic_inc(&nh->nh_dev->refcnt);
+		dn_fib_res_put(&res);
+	} else {
+		struct net_device *dev;
 
-	for(; fa; fa = fa->fa_next) {
-		if ((fa->fa_key ^ res->res_addr) & fa->fa_mask)
-			continue;
-		if (res->res_ifindex && (res->res_ifindex != fa->fa_ifindex))
-			continue;
-		if (res->res_mask && (res->res_mask != fa->fa_mask))
-			continue;
-		if (res->res_proto && (res->res_proto != fa->fa_proto))
-			continue;
-		if (res->res_cost && (res->res_cost != fa->fa_cost))
-			continue;
+		if (nh->nh_flags&(RTNH_F_PERVASIVE|RTNH_F_ONLINK))
+			return -EINVAL;
 
-		res->res_fn = fn;
-		res->res_fa = fa;
-		return 1;
+		dev = __dev_get_by_index(nh->nh_oif);
+		if (dev == NULL || dev->dn_ptr == NULL)
+			return -ENODEV;
+		if (!(dev->flags&IFF_UP))
+			return -ENETDOWN;
+		nh->nh_dev = dev;
+		atomic_inc(&nh->nh_dev->refcnt);
+		nh->nh_scope = RT_SCOPE_HOST;
 	}
 
 	return 0;
 }
 
-static int dn_fib_recurse(struct dn_fib_node *fn, struct dn_fib_res *res)
-{
-	struct dn_fib_node *fn1;
-	int err = -ENOENT;
-
-	fn1 = dn_fib_follow(fn, res->res_addr);
-
-	if (dn_fib_search(fn1, res))
-		return 0;
-
-	while((fn1 = fn1->fn_up) != fn)
-		if ((err = dn_fib_recurse(DN_FIB_NEXT(fn1, ~res->res_addr), res)) == 0)
-			break;
-
-	return err;
-}
-
-static int dn_fib_table_lookup(struct dn_fib_table *t, struct dn_fib_res *res)
-{
-	struct dn_fib_node *fn = t->root;
-	int err = -ENOENT;
-
-	if (t->root == NULL)
-		return err;
-
-	fn = dn_fib_follow(t->root, res->res_addr);
-
-	if (dn_fib_search(fn, res))
-		return 0;
-
-	while((fn = fn->fn_up) != NULL)
-		if ((err = dn_fib_recurse(DN_FIB_NEXT(fn, ~res->res_addr), res)) == 0)
-			break;
-
-	return err;
-}
 
-static int dn_fib_table_walk_recurse(struct dn_fib_walker_t *fwt, struct dn_fib_node *fn)
+struct dn_fib_info *dn_fib_create_info(const struct rtmsg *r, struct dn_kern_rta *rta, const struct nlmsghdr *nlh, int *errp)
 {
-	struct dn_fib_table *t = fwt->table;
-
-	if (fn->fn_action) {
-		fwt->fxn(fwt, fn);
+	int err;
+	struct dn_fib_info *fi = NULL;
+	struct dn_fib_info *ofi;
+	int nhs = 1;
+
+	if (dn_fib_props[r->rtm_type].scope > r->rtm_scope)
+		goto err_inval;
+
+	if (rta->rta_mp) {
+		nhs = dn_fib_count_nhs(rta->rta_mp);
+		if (nhs == 0)
+			goto err_inval;
+	}
+
+	fi = kmalloc(sizeof(*fi)+nhs*sizeof(struct dn_fib_nh), GFP_KERNEL);
+	err = -ENOBUFS;
+	if (fi == NULL)
+		goto failure;
+	memset(fi, 0, sizeof(*fi)+nhs*sizeof(struct dn_fib_nh));
+
+	fi->fib_protocol = r->rtm_protocol;
+	fi->fib_nhs = nhs;
+	fi->fib_flags = r->rtm_flags;
+	if (rta->rta_priority)
+		fi->fib_priority = *rta->rta_priority;
+	if (rta->rta_prefsrc)
+		memcpy(&fi->fib_prefsrc, rta->rta_prefsrc, 2);
+
+	if (rta->rta_mp) {
+		if ((err = dn_fib_get_nhs(fi, rta->rta_mp, r)) != 0)
+			goto failure;
+		if (rta->rta_oif && fi->fib_nh->nh_oif != *rta->rta_oif)
+			goto err_inval;
+		if (rta->rta_gw && memcmp(&fi->fib_nh->nh_gw, rta->rta_gw, 2))
+			goto err_inval;
+	} else {
+		struct dn_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, 2);
+		nh->nh_flags = r->rtm_flags;
+		nh->nh_weight = 1;
+	}
+
+	if (dn_fib_props[r->rtm_type].error) {
+		if (rta->rta_gw || rta->rta_oif || rta->rta_mp)
+			goto err_inval;
+		goto link_it;
+	}
+
+	if (r->rtm_scope > RT_SCOPE_HOST)
+		goto err_inval;
+
+	if (r->rtm_scope == RT_SCOPE_HOST) {
+		struct dn_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);
+		err = -ENODEV;
+		if (nh->nh_dev == NULL)
+			goto failure;
 	} else {
-		dn_fib_table_walk_recurse(fwt, t->root->fn_children[0]);
-		dn_fib_table_walk_recurse(fwt, t->root->fn_children[1]);
+		change_nexthops(fi) {
+			if ((err = dn_fib_check_nh(r, fi, nh)) != 0)
+				goto failure;
+		} endfor_nexthops(fi)
 	}
 
-	return 0;
-}
-
-static int dn_fib_table_walk(struct dn_fib_walker_t *fwt)
-{
-	struct dn_fib_table *t = fwt->table;
-
-	if (t->root != NULL) {
-		if (t->root->fn_action) {
-			fwt->fxn(fwt, t->root);
-		} else {
-			dn_fib_table_walk_recurse(fwt, t->root->fn_children[0]);
-			dn_fib_table_walk_recurse(fwt, t->root->fn_children[1]);
-		}
+#if I_GET_AROUND_TO_FIXING_PREFSRC
+	if (fi->fib_prefsrc) {
+		if (r->rtm_type != RTN_LOCAL || rta->rta_dst == NULL ||
+		    memcmp(&fi->fib_prefsrc, rta->rta_dst, 2))
+			if (dn_addr_type(fi->fib_prefsrc) != RTN_LOCAL)
+				goto err_inval;
 	}
-
-	return 0;
-}
-
-static struct dn_fib_table *dn_fib_get_tree(int n, int create)
-{
-	struct dn_fib_table *t;
-
-	if (n < DN_MIN_TABLE)
-		return NULL;
-
-	if (n > DN_NUM_TABLES)
-		return NULL;
-
-	if (dn_fib_tables[n])
-		return dn_fib_tables[n];
-
-	if (!create)
-		return NULL;
-
-	if ((t = kmalloc(sizeof(struct dn_fib_table), GFP_KERNEL)) == NULL)
-		return NULL;
-
-	dn_fib_tables[n] = t;
-	memset(t, 0, sizeof(struct dn_fib_table));
-
-	t->n = n;
-	t->insert = dn_fib_table_insert;
-	t->delete = dn_fib_table_delete;
-	t->lookup = dn_fib_table_lookup;
-	t->walk   = dn_fib_table_walk;
-#ifdef CONFIG_RTNETLINK
-	t->dump = dn_fib_table_dump;
 #endif
 
-	return t;
-}
-
-static void dn_fib_del_tree(struct dn_fib_table *t)
-{
-	dn_fib_tables[t->n] = NULL;
-
-	if (t) {
-		kfree_s(t, sizeof(struct dn_fib_table));
-	}
+link_it:
+	if ((ofi = dn_fib_find_info(fi)) != NULL) {
+		fi->fib_dead = 1;
+		dn_fib_free_info(fi);
+		ofi->fib_treeref++;
+		return ofi;
+	}
+
+	fi->fib_treeref++;
+	atomic_inc(&fi->fib_clntref);
+	write_lock(&dn_fib_info_lock);
+	fi->fib_next = dn_fib_info_list;
+	fi->fib_prev = NULL;
+	if (dn_fib_info_list)
+		dn_fib_info_list->fib_prev = fi;
+	dn_fib_info_list = fi;
+	dn_fib_info_cnt++;
+	write_unlock(&dn_fib_info_lock);
+	return fi;
+
+err_inval:
+	err = -EINVAL;
+
+failure:
+	*errp = err;
+	if (fi) {
+		fi->fib_dead = 1;
+		dn_fib_free_info(fi);
+	}
+
+	return NULL;
 }
 
 
-int dn_fib_resolve(struct dn_fib_res *res)
-{
-	int table = DN_L1_TABLE;
-	int count = 0;
-	struct dn_fib_action *fa;
-	int err;
-
-	if ((res->res_addr ^ dn_ntohs(decnet_address)) & 0xfc00)
-		table = DN_L2_TABLE;
-
-	for(;;) {
-		struct dn_fib_table *t = dn_fib_get_tree(table, 0);
-
-		if (t == NULL)
-			return -ENOENT;
-
-		if ((err = t->lookup(t, res)) < 0)
-			return err;
-
-		if ((fa = res->res_fa) == NULL)
-			return -ENOENT;
-
-		if (fa->fa_type != RTN_THROW)
-			break;
-
-		table = fa->fa_table;
+void dn_fib_select_multipath(const struct dn_fib_key *key, struct dn_fib_res *res)
+{
+	struct dn_fib_info *fi = res->fi;
+	int w;
+
+	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;
+			}
+		} endfor_nexthops(fi);
+		fi->fib_power = power;
+	}
+
+	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--;
+				fi->fib_power--;
+				res->nh_sel = nhsel;
+				return;
+			}
+		}
+	} endfor_nexthops(fi);
 
-		if (count++ > DN_NUM_TABLES)
-			return -ENOENT;
-	}
+	printk(KERN_DEBUG "DECnet: BUG! dn_fib_select_multipath\n");
+}
 
-	switch(fa->fa_type) {
-		case RTN_PROHIBIT:
-		case RTN_UNREACHABLE:
-			return -fa->fa_error;
-	}
 
-	return 0;
-}
 
 /*
  * Punt to user via netlink for example, but for now
@@ -489,72 +416,19 @@
 
 
 #ifdef CONFIG_RTNETLINK
-static int dn_fib_convert_rtm(struct dn_fib_action *fa,
-					struct rtmsg *r, struct rtattr **rta, 
-					struct nlmsghdr *n, 
-					struct netlink_skb_parms *req)
-{
-	dn_address dst, gw, mask = 0xffff;
-	int ifindex = 0;
-	struct neighbour *neigh;
-	struct net_device *dev;
-	unsigned char addr[ETH_ALEN];
-
-	if (r->rtm_family != AF_DECnet)
-		return -EINVAL;
-
-	if (rta[RTA_DST-1])
-		memcpy(&dst, RTA_DATA(rta[RTA_DST-1]), 2);
-
-	if (rta[RTA_OIF-1])
-		memcpy(&ifindex, RTA_DATA(rta[RTA_OIF-1]), sizeof(int));
-
-	if (rta[RTA_GATEWAY-1])
-		memcpy(&gw, RTA_DATA(rta[RTA_GATEWAY-1]), 2);
-
-	fa->fa_key      = dn_ntohs(dst);
-	fa->fa_mask     = dn_ntohs(mask);
-	fa->fa_ifindex  = ifindex;
-	fa->fa_proto    = r->rtm_protocol;
-	fa->fa_type     = r->rtm_type;
-
-	switch(fa->fa_type) {
-		case RTN_UNICAST:
-			if ((dev = __dev_get_by_index(ifindex)) == NULL)
-				return -ENODEV;
-			dn_dn2eth(addr, dn_ntohs(gw));
-			if ((neigh = __neigh_lookup(&dn_neigh_table, &addr, dev, 1)) == NULL)
-				return -EHOSTUNREACH;
-			fa->fa_neigh = neigh;
-			break;
-		case RTN_THROW:
-			fa->fa_table = 0;
-			break;
-		case RTN_PROHIBIT:
-			fa->fa_error = EPERM;
-			break;
-		case RTN_UNREACHABLE:
-			fa->fa_error = EHOSTUNREACH;
-			break;
-		case RTN_BLACKHOLE:
-			fa->fa_error = EINVAL;
-			break;
-	}
-
-	return 0;
-}
 
 static int dn_fib_check_attr(struct rtmsg *r, struct rtattr **rta)
 {
-	switch(r->rtm_type) {
-		case RTN_UNICAST:
-		case RTN_BLACKHOLE:
-		case RTN_PROHIBIT:
-		case RTN_UNREACHABLE:
-		case RTN_THROW:
-			break;
-		default:
-			return -1;
+	int i;
+
+	for(i = 1; i <= RTA_MAX; i++) {
+		struct rtattr *attr = rta[i-1];
+		if (attr) {
+			if (RTA_PAYLOAD(attr) < 4 && RTA_PAYLOAD(attr) != 2)
+				return -EINVAL;
+			if (i != RTA_MULTIPATH && i != RTA_METRICS)
+				rta[i-1] = (struct rtattr *)RTA_DATA(attr);
+		}
 	}
 
 	return 0;
@@ -562,114 +436,36 @@
 
 int dn_fib_rtm_delroute(struct sk_buff *skb, struct nlmsghdr *nlh, void *arg)
 {
-	struct dn_fib_table *t;
+	struct dn_fib_table *tb;
 	struct rtattr **rta = arg;
 	struct rtmsg *r = NLMSG_DATA(nlh);
-	struct dn_fib_action *fa;
-	int err;
 
 	if (dn_fib_check_attr(r, rta))
 		return -EINVAL;
 
-	if ((fa = dn_fib_new_action()) == NULL)
-		return -ENOBUFS;
+	tb = dn_fib_get_table(r->rtm_table, 0);
+	if (tb)
+		return tb->delete(tb, r, (struct dn_kern_rta *)rta, nlh, &NETLINK_CB(skb));
 
-	t = dn_fib_get_tree(r->rtm_table, 0);
-	if (t) {
-		if ((err = dn_fib_convert_rtm(fa, r, rta, nlh, &NETLINK_CB(skb))) < 0)  {
-			dn_fib_del_action(fa);
-			return err;
-		}
-		err = t->delete(t, fa);
-		dn_fib_del_action(fa);
-		return err;
-	}
 	return -ESRCH;
 }
 
 int dn_fib_rtm_newroute(struct sk_buff *skb, struct nlmsghdr *nlh, void *arg)
 {
-	struct dn_fib_table *t;
+	struct dn_fib_table *tb;
 	struct rtattr **rta = arg;
 	struct rtmsg *r = NLMSG_DATA(nlh);
-	struct dn_fib_action *fa;
-	int err;
 
 	if (dn_fib_check_attr(r, rta))
 		return -EINVAL;
 
-	if ((fa = dn_fib_new_action()) == NULL)
-		return -ENOBUFS;
+	tb = dn_fib_get_table(r->rtm_table, 1);
+	if (tb) 
+		return tb->insert(tb, r, (struct dn_kern_rta *)rta, nlh, &NETLINK_CB(skb));
 
-	t = dn_fib_get_tree(r->rtm_table, 1);
-	if (t) {
-		if ((err = dn_fib_convert_rtm(fa, r, rta, nlh, &NETLINK_CB(skb))) < 0) {
-			dn_fib_del_action(fa);
-			return err;
-		}
-		return t->insert(t, fa);
-	}
 	return -ENOBUFS;
 }
 
-int dn_fib_dump_info(struct sk_buff *skb, u32 pid, u32 seq, int event,
-			int table, struct dn_fib_action *fa)
-{
-	struct rtmsg *rtm;
-	struct nlmsghdr *nlh;
-	unsigned char *b = skb->tail;
-
-	nlh = NLMSG_PUT(skb, pid, seq, event, sizeof(*rtm));
-	rtm = NLMSG_DATA(nlh);
-	rtm->rtm_family = AF_DECnet;
-	rtm->rtm_dst_len = 16;
-	rtm->rtm_src_len = 16;
-	rtm->rtm_tos = 0;
-	rtm->rtm_table = table;
-	rtm->rtm_type = fa->fa_type;
-	rtm->rtm_flags = 0;
-	rtm->rtm_protocol = fa->fa_proto;
-	RTA_PUT(skb, RTA_DST, 2, &fa->fa_key);
-	if (fa->fa_ifindex)
-		RTA_PUT(skb, RTA_OIF, sizeof(int), &fa->fa_ifindex);
-
-	nlh->nlmsg_len = skb->tail - b;
-	return skb->len;
-
-nlmsg_failure:
-rtattr_failure:
-        skb_trim(skb, b - skb->data);
-        return -1;
-}
-
-static void dn_rtmsg_fib(int event, int table, struct dn_fib_action *fa,
-			struct nlmsghdr *nlh, struct netlink_skb_parms *req)
-{
-	struct sk_buff *skb;
-	u32 pid = req ? req->pid : 0;
-	int size = NLMSG_SPACE(sizeof(struct rtmsg) + 256);
-
-	skb = alloc_skb(size, GFP_KERNEL);
-	if (!skb)
-		return;
-
-	if (dn_fib_dump_info(skb, pid, nlh->nlmsg_seq, event, table, fa) < 0) {
-		kfree_skb(skb);
-		return;
-	}
-	NETLINK_CB(skb).dst_groups = RTMGRP_DECnet_ROUTE;
-	if (nlh->nlmsg_flags & NLM_F_ECHO)
-		atomic_inc(&skb->users);
-	netlink_broadcast(rtnl, skb, pid, RTMGRP_DECnet_ROUTE, GFP_KERNEL);
-	if (nlh->nlmsg_flags & NLM_F_ECHO)
-		netlink_unicast(rtnl, skb, pid, MSG_DONTWAIT);
-}
-
-static int dn_fib_table_dump(struct dn_fib_table *t, struct sk_buff *skb, struct netlink_callback *cb)
-{
-
-	return skb->len;
-}
 
 int dn_fib_dump(struct sk_buff *skb, struct netlink_callback *cb)
 {
@@ -690,7 +486,7 @@
 			continue;
 		if (t > s_t)
 			memset(&cb->args[1], 0, sizeof(cb->args)-sizeof(int));
-		tb = dn_fib_get_tree(t, 0);
+		tb = dn_fib_get_table(t, 0);
 		if (tb == NULL)
 			continue;
 		if (tb->dump(tb, skb, cb) < 0)
@@ -703,6 +499,96 @@
 }
 #endif /* CONFIG_RTNETLINK */
 
+int dn_fib_sync_down(dn_address local, struct net_device *dev, int force)
+{
+        int ret = 0;
+        int scope = RT_SCOPE_NOWHERE;
+
+        if (force)
+                scope = -1;
+
+        for_fib_info() {
+                /* 
+                 * This makes no sense for DECnet.... we will almost
+                 * certainly have more than one local address the same
+                 * over all our interfaces. It needs thinking about
+                 * some more.
+                 */
+                if (local && fi->fib_prefsrc == local) {
+                        fi->fib_flags |= RTNH_F_DEAD;
+                        ret++;
+                } else if (dev && fi->fib_nhs) {
+                        int 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;
+                                        fi->fib_power -= nh->nh_power;
+                                        nh->nh_power = 0;
+                                        dead++;
+                                }
+                        } endfor_nexthops(fi)
+                        if (dead == fi->fib_nhs) {
+                                fi->fib_flags |= RTNH_F_DEAD;
+                                ret++;
+                        }
+                }
+        } endfor_fib_info();
+        return ret;
+}
+
+
+int dn_fib_sync_up(struct net_device *dev)
+{
+        int ret = 0;
+
+        if (!(dev->flags&IFF_UP))
+                return 0;
+
+        for_fib_info() {
+                int alive = 0;
+
+                change_nexthops(fi) {
+                        if (!(nh->nh_flags&RTNH_F_DEAD)) {
+                                alive++;
+                                continue;
+                        }
+                        if (nh->nh_dev == NULL || !(nh->nh_dev->flags&IFF_UP))
+                                continue;
+                        if (nh->nh_dev != dev || dev->dn_ptr == NULL)
+                                continue;
+                        alive++;
+                        nh->nh_power = 0;
+                        nh->nh_flags &= ~RTNH_F_DEAD;
+                } endfor_nexthops(fi);
+
+                if (alive == fi->fib_nhs) {
+                        fi->fib_flags &= ~RTNH_F_DEAD;
+                        ret++;
+                }
+        } endfor_fib_info();
+        return ret;
+}
+
+void dn_fib_flush(void)
+{
+        int flushed = 0;
+        struct dn_fib_table *tb;
+        int id;
+
+        for(id = DN_NUM_TABLES; id > 0; id--) {
+                if ((tb = dn_fib_get_table(id, 0)) == NULL)
+                        continue;
+                flushed += tb->flush(tb);
+        }
+
+        if (flushed)
+                dn_rt_cache_flush(-1);
+}
+
 int dn_fib_ioctl(struct socket *sock, unsigned int cmd, unsigned long arg)
 {
 
@@ -720,104 +606,65 @@
 
 #ifdef CONFIG_PROC_FS
 
-struct dn_fib_procfs {
-	int len;
-	off_t pos;
-	off_t begin;
-	off_t offset;
-	int length;
-	char *buffer;
-};
-
-static int dn_proc_action_list(struct dn_fib_walker_t *fwt, struct dn_fib_node *fn)
-{
-	struct dn_fib_procfs *pinfo = (struct dn_fib_procfs *)fwt->arg;
-	struct dn_fib_action *fa;
-	char ab[DN_ASCBUF_LEN];
-
-	if (pinfo->pos > pinfo->offset + pinfo->length)
-		return 0;
-
-	for(fa = fn->fn_action; fa; fa = fa->fa_next) {
-
-		pinfo->len += sprintf(pinfo->buffer + pinfo->len,
-					"%s/%04hx %02x %02x\n", 
-					dn_addr2asc(fa->fa_key, ab),
-					fa->fa_mask,
-					fa->fa_type,
-					fa->fa_proto);
-
-		pinfo->pos = pinfo->begin + pinfo->len;
-		if (pinfo->pos < pinfo->offset) {
-			pinfo->len = 0;
-			pinfo->begin = pinfo->pos;
-		}
-		if (pinfo->pos > pinfo->offset + pinfo->length)
-			break;
-	}
-
-	return 0;
-}
-
 static int decnet_rt_get_info(char *buffer, char **start, off_t offset, int length)
 {
-	struct dn_fib_procfs pinfo;
-	int i;
-	struct dn_fib_table *t;
-	struct dn_fib_walker_t fwt;
+        int first = offset / 128;
+        char *ptr = buffer;
+        int count = (length + 127) / 128;
+        int len;
+        int i;
+        struct dn_fib_table *tb;
+
+        *start = buffer + (offset % 128);
+
+        if (--first < 0) {
+                sprintf(buffer, "%-127s\n", "Iface\tDest\tGW  \tFlags\tRefCnt\tUse\tMetric\tMask\t\tMTU\tWindow\tIRTT");
+                --count;
+                ptr += 128;
+                first = 0;
+        }
+
+
+        for(i = DN_MIN_TABLE; (i <= DN_NUM_TABLES) && (count > 0); i++) {
+                if ((tb = dn_fib_get_table(i, 0)) != NULL) {
+                        int n = tb->get_info(tb, ptr, first, count);
+                        count -= n;
+                        ptr += n * 128;
+                }
+        }
+
+        len = ptr - *start;
+        if (len >= length)
+                return length;
+        if (len >= 0)
+                return len;
 
-	pinfo.pos = 0;
-	pinfo.len = 0;
-	pinfo.begin = 0;
-	pinfo.offset = offset;
-	pinfo.length = length;
-	pinfo.buffer = buffer;
-
-	fwt.arg = &pinfo;
-	fwt.fxn = dn_proc_action_list;
-
-	start_bh_atomic();
-	for(i = 0; i < DN_NUM_TABLES; i++) {
-		if ((t = dn_fib_get_tree(i, 0)) == NULL)
-			continue;
-
-		fwt.table = t;
-		t->walk(&fwt);
-
-		if (pinfo.pos > pinfo.offset + pinfo.length)
-			break;
-	}
-	end_bh_atomic();
-
-	*start = pinfo.buffer + (pinfo.offset - pinfo.begin);
-	pinfo.len -= (pinfo.offset - pinfo.begin);
-
-	if (pinfo.len > pinfo.length) 
-		pinfo.len = pinfo.length;
-
-	return pinfo.len;
+        return 0;
 }
 
 #endif /* CONFIG_PROC_FS */
 
-#ifdef CONFIG_DECNET_MODULE
-void dn_fib_cleanup(void)
+
+void __exit dn_fib_cleanup(void)
 {
 #ifdef CONFIG_PROC_FS
-	proc_net_create("decnet_route",0,decnet_rt_get_info);
-#endif /* CONFIG_PROC_FS */
+	proc_net_remove("decnet_route");
+#endif
+
+	dn_fib_table_cleanup();
+	dn_fib_rules_cleanup();
 }
-#endif /* CONFIG_DECNET_MODULE */
 
 
 void __init dn_fib_init(void)
 {
-	memset(dn_fib_tables, 0, DN_NUM_TABLES * sizeof(struct dn_fib_table *));
 
 #ifdef CONFIG_PROC_FS
-	proc_net_remove("decnet_route");
+	proc_net_create("decnet_route", 0, decnet_rt_get_info);
 #endif
 
+	dn_fib_table_init();
+	dn_fib_rules_init();
 }
 
 

FUNET's LINUX-ADM group, linux-adm@nic.funet.fi
TCL-scripts by Sam Shen (who was at: slshen@lbl.gov)