Subject: [PATCH] new "fromarp" route flag

From: Michael Schroeder <[email-masked]>
Date: Mon, 22 Mar 2004 18:27:16 +0100

The following patch implements a new route flag, "fromarp".

Problem description:
--------------------

Zeroconf hardware automatically assigns an ip address from the
link local range (169.254.1.0 - 169.254.254.255). This ip addresses
don't get routed and are local to the interfaces. To address such
a component, one can add a link local route:

    ip route add 169.254.0.0/16 dev eth0

Things get ugly if a host is multihomed and zeroconf hardware is
conected to more than one interface. It is not possible to add
a route in that case.

Solution:
---------

My patch adds a new route flag, "fromarp", to the kernel. Example:

    ip route add 169.254.0.0/16 fromarp nexthop dev eth0 nexthop dev eth1

If a packet is to be send with such a route, the kernel
checks the arp cache of the interfaces specified in the route.
If a match is found, the packet is send to the matching interface.
Otherwise an arp request is sent from all of the interfaces.

Comments anyone?

--- ./include/linux/in_route.h.orig	2004-03-19 11:20:12.000000000 +0000
+++ ./include/linux/in_route.h	2004-03-19 11:21:18.000000000 +0000
@@ -18,6 +18,7 @@
 #define RTCF_MASQ	0x00400000
 #define RTCF_SNAT	0x00800000
 #define RTCF_DOREDIRECT 0x01000000
+#define RTCF_FROMARP    0x02000000
 #define RTCF_DIRECTSRC	0x04000000
 #define RTCF_DNAT	0x08000000
 #define RTCF_BROADCAST	0x10000000
--- ./include/linux/rtnetlink.h.orig	2004-03-19 11:20:12.000000000 +0000
+++ ./include/linux/rtnetlink.h	2004-03-19 11:21:18.000000000 +0000
@@ -170,6 +170,7 @@
 #define RTM_F_CLONED		0x200	/* This route is cloned		*/
 #define RTM_F_EQUALIZE		0x400	/* Multipath equalizer: NI	*/
 #define RTM_F_PREFIX		0x800	/* Prefix addresses		*/
+#define RTM_F_FROMARP		0x1000	/* Select if from arpcache      */
 
 /* Reserved table identifiers */
 
--- ./net/ipv4/arp.c.orig	2004-03-19 11:19:43.000000000 +0000
+++ ./net/ipv4/arp.c	2004-03-19 11:21:18.000000000 +0000
@@ -952,6 +952,12 @@
 	return 0;
 }
 
+struct neighbour *arp_lookup(u32 ip, struct net_device *dev, int creat)
+{
+        return __neigh_lookup(&arp_tbl, &ip, dev, creat);
+}
+
+
 /*
  *	User level interface (ioctl)
  */
--- ./net/ipv4/fib_semantics.c.orig	2004-03-19 11:20:18.000000000 +0000
+++ ./net/ipv4/fib_semantics.c	2004-03-19 11:21:18.000000000 +0000
@@ -1035,4 +1035,52 @@
 	res->nh_sel = 0;
 	spin_unlock_bh(&fib_multipath_lock);
 }
+
+extern struct neighbour *arp_lookup(u32 ip, struct net_device *dev, int creat);
+
+int fib_select_fromarp(const struct flowi *flp, struct fib_result *res)
+{
+	struct net_device *dev;
+	struct fib_info *fi = res->fi;
+	struct neighbour *n;
+	u32 ip;
+	int i, new;
+
+	res->nh_sel = 0;
+	ip = flp->nl_u.ip4_u.daddr;
+	if (ip == 0xffffffff || MULTICAST(ip))
+		return 1;
+	spin_lock_bh(&fib_multipath_lock);
+	for (i = 0, new = -1; i < fi->fib_nhs; i++) {
+		dev = fi->fib_nh[i].nh_dev;
+		n = arp_lookup(ip, dev, 0);
+		if (!n) {
+			if (new < 0)
+				new = i;
+			continue;
+		}
+		if ((n->nud_state&(NUD_CONNECTED|NUD_DELAY|NUD_PROBE)) != 0) {
+			res->nh_sel = i;
+			neigh_release(n);
+			spin_unlock_bh(&fib_multipath_lock);
+			return 1;
+		}
+		neigh_release(n);
+	}
+	if (new < 0)
+		new = 0;
+	res->nh_sel = new;
+	for (i = 0; i < fi->fib_nhs; i++) {
+		if (i == new)
+			continue;
+		dev = fi->fib_nh[i].nh_dev;
+		if ((n = arp_lookup(ip, dev, 1)) != 0) {
+			neigh_event_send(n, (struct sk_buff *)0);
+			neigh_release(n);
+		}
+	}
+	spin_unlock_bh(&fib_multipath_lock);
+	return 0;
+}
+
 #endif
--- ./net/ipv4/route.c.orig	2004-03-19 11:20:18.000000000 +0000
+++ ./net/ipv4/route.c	2004-03-19 11:21:18.000000000 +0000
@@ -1332,7 +1332,9 @@
 {
 	struct rtable *rt;
 
-	icmp_send(skb, ICMP_DEST_UNREACH, ICMP_HOST_UNREACH, 0);
+	rt = (struct rtable *) skb->dst;
+	if (!rt || !(rt->rt_flags & RTCF_FROMARP))
+		icmp_send(skb, ICMP_DEST_UNREACH, ICMP_HOST_UNREACH, 0);
 
 	rt = (struct rtable *) skb->dst;
 	if (rt)
@@ -2077,9 +2079,13 @@
 	}
 
 #ifdef CONFIG_IP_ROUTE_MULTIPATH
-	if (res.fi->fib_nhs > 1 && fl.oif == 0)
-		fib_select_multipath(&fl, &res);
-	else
+	if (res.fi->fib_nhs > 1 && fl.oif == 0) {
+		if (res.fi->fib_flags & RTM_F_FROMARP) {
+			if (!fib_select_fromarp(&fl, &res))
+				flags |= RTCF_FROMARP;
+		} else
+			fib_select_multipath(&fl, &res);
+	}
 #endif
 	if (!res.prefixlen && res.type == RTN_UNICAST && !fl.oif)
 		fib_select_default(&fl, &res);
@@ -2190,8 +2196,18 @@
 
 	rth->rt_flags = flags;
 
-	hash = rt_hash_code(oldflp->fl4_dst, oldflp->fl4_src ^ (oldflp->oif << 5), tos);
-	err = rt_intern_hash(hash, rth, rp);
+	if (flags & RTCF_FROMARP) {
+		err = 0;
+		if (rth->rt_type == RTN_UNICAST || rth->fl.iif == 0) {
+			if ((err = arp_bind_neighbour(&rth->u.dst)) != 0)
+				rt_drop(rth);
+		}
+		if (!err)
+			*rp = rth;
+	} else {
+		hash = rt_hash_code(oldflp->fl4_dst, oldflp->fl4_src ^ (oldflp->oif << 5), tos);
+		err = rt_intern_hash(hash, rth, rp);
+	}
 done:
 	if (free_res)
 		fib_res_put(&res);

Cheers,
  Michael.

-- 
Michael Schroeder                                   [email-masked]
main(_){while(_=~getchar())putchar(~_-1/(~(_|32)/13*2-11)*13);}