[PATCH netifd v2] interface-ip: fix ipv6 routing loop

Hans Dedecker dedeckeh at gmail.com
Sun Jan 3 15:07:44 EST 2021


In case of prefix delegation an upstream ISP will route the complete
delegated prefix (e.g 2001:DB8:BEEF::/56) to an OpenWrt device, OpenWrt
will route back the complete /56 not matching a local or subdelegated
prefix and with as source an address from the delegated prefix
causing a routing loop.
Fix this by using an ip rule which directs traffic matching the
subdelegated prefix and coming from the wan interface to the main or
user configured routing table.
An ip rule with lower priority will make sure the traffic not matching
the subdelegated prefix(es) will be dropped with an ICMPv6 unreachable
fixing the potential routing loop.

This will result into the following typical IPv6 rules :

0:      from all lookup local
30000:  from all to 2001:DB8:BEEF::/64 iif eth4 lookup main
30001:  from all to 2001:DB8:BEEF::/56 iif eth4 unreachable
32766:  from all lookup main
4200000000:     from 2001:DB8:BEEF::1/64 iif br-lan unreachable
4200000001:     from all iif lo failed_policy
4200000011:     from all iif eth0 failed_policy
4200000015:     from all iif eth4 failed_policy
4200000015:     from all iif eth4 failed_policy
4200000019:     from all iif br-lan failed_policy

Signed-off-by: Hans Dedecker <dedeckeh at gmail.com>
---
v2: Keep unreachable route in the routing table dropping traffic from the lan 
not matching any routing rules with an ICMPv6 unreachable

 interface-ip.c | 77 ++++++++++++++++++++++++++++++++++++++++++--------
 iprule.h       | 10 ++++---
 2 files changed, 71 insertions(+), 16 deletions(-)

diff --git a/interface-ip.c b/interface-ip.c
index 024c5b8..8a68340 100644
--- a/interface-ip.c
+++ b/interface-ip.c
@@ -903,6 +903,8 @@ interface_set_prefix_address(struct device_prefix_assignment *assignment,
 		const struct device_prefix *prefix, struct interface *iface, bool add)
 {
 	const struct interface *uplink = prefix->iface;
+	unsigned int rt_table_id;
+
 	if (!iface->l3_dev.dev)
 		return;
 
@@ -935,9 +937,21 @@ interface_set_prefix_address(struct device_prefix_assignment *assignment,
 					addr.mask < 64 ? 64 : addr.mask, iface->ip6table, NULL, NULL, false);
 
 		if (prefix->iface) {
-			if (prefix->iface->ip6table)
+			rt_table_id = prefix->iface->ip6table;
+
+			if (rt_table_id)
 				set_ip_source_policy(false, true, IPRULE_PRIORITY_NW, &addr.addr,
-						addr.mask, prefix->iface->ip6table, iface, NULL, true);
+						addr.mask, rt_table_id, iface, NULL, true);
+
+			if (!rt_table_id)
+				system_resolve_rt_table("main", &rt_table_id);
+
+			/*
+			 * Remove ip rule allowing traffic coming from the prefix
+			 * interface and directed to the sub delegated prefix
+			 */
+			set_ip_source_policy(false, true, IPRULE_PRIORITY_SUB_PREFIX, &addr.addr,
+						addr.mask, rt_table_id, prefix->iface, NULL, false);
 
 			set_ip_source_policy(false, true, IPRULE_PRIORITY_REJECT, &addr.addr,
 							addr.mask, 0, iface, "unreachable", true);
@@ -972,12 +986,25 @@ interface_set_prefix_address(struct device_prefix_assignment *assignment,
 						addr.mask < 64 ? 64 : addr.mask, iface->ip6table, NULL, NULL, false);
 
 			if (prefix->iface) {
+				rt_table_id = prefix->iface->ip6table;
+
 				set_ip_source_policy(true, true, IPRULE_PRIORITY_REJECT, &addr.addr,
 						addr.mask, 0, iface, "unreachable", true);
 
-				if (prefix->iface->ip6table)
+				if (rt_table_id)
 					set_ip_source_policy(true, true, IPRULE_PRIORITY_NW, &addr.addr,
-							addr.mask, prefix->iface->ip6table, iface, NULL, true);
+							addr.mask, rt_table_id, iface, NULL, true);
+
+				if (!rt_table_id)
+					system_resolve_rt_table("main", &rt_table_id);
+
+				/*
+				 * Add ip rule allowing traffic coming from the prefix
+				 * interface and directed to the sub delegated prefix
+				 */
+				set_ip_source_policy(true, true, IPRULE_PRIORITY_SUB_PREFIX, &addr.addr,
+						addr.mask, rt_table_id, prefix->iface, NULL, false);
+
 			}
 		}
 
@@ -1213,6 +1240,10 @@ interface_update_prefix(struct vlist_tree *tree,
 			     struct vlist_node *node_old)
 {
 	struct device_prefix *prefix_old, *prefix_new;
+	struct device_prefix_assignment *c;
+	struct interface *iface;
+	struct device_route route;
+
 	prefix_old = container_of(node_old, struct device_prefix, node);
 	prefix_new = container_of(node_new, struct device_prefix, node);
 
@@ -1220,16 +1251,9 @@ interface_update_prefix(struct vlist_tree *tree,
 	if (tree && (!node_new || !node_old))
 		ip->iface->updated |= IUF_PREFIX;
 
-	struct device_route route;
 	memset(&route, 0, sizeof(route));
 	route.flags = DEVADDR_INET6;
 	route.metric = INT32_MAX;
-	route.mask = (node_new) ? prefix_new->length : prefix_old->length;
-	route.addr.in6 = (node_new) ? prefix_new->addr : prefix_old->addr;
-
-
-	struct device_prefix_assignment *c;
-	struct interface *iface;
 
 	if (node_old && node_new) {
 		/* Move assignments and refresh addresses to update valid times */
@@ -1244,14 +1268,43 @@ interface_update_prefix(struct vlist_tree *tree,
 			ip->iface->updated |= IUF_PREFIX;
 	} else if (node_new) {
 		/* Set null-route to avoid routing loops */
+		route.mask = prefix_new->length;
+		route.addr.in6 = prefix_new->addr;
+
 		system_add_route(NULL, &route);
 
+		/*
+		 * Set unreachable rule for the delegated prefix with
+		 * as incoming interface the interface on which the
+		 * delegated prefix is received to avoid routing loops
+		 */
+		if (prefix_new->iface)
+		{
+			union if_addr prefix = { .in6 = prefix_new->addr, };
+
+			set_ip_source_policy(true, true, IPRULE_PRIORITY_PREFIX_UNREACHABLE, &prefix,
+					prefix_new->length, 0, prefix_new->iface, "unreachable", true);
+		}
+
 		if (!prefix_new->iface || !prefix_new->iface->proto_ip.no_delegation)
 			interface_update_prefix_assignments(prefix_new, true);
 	} else if (node_old) {
-		/* Remove null-route */
 		interface_update_prefix_assignments(prefix_old, false);
+
+		/* Remove null-route */
+		route.mask = prefix_old->length;
+		route.addr.in6 = prefix_old->addr;
+
 		system_del_route(NULL, &route);
+
+		/* Delete unreachable rule added to avoid routing loops */
+		if (prefix_old->iface)
+		{
+			union if_addr prefix = { .in6 = prefix_old->addr, };
+
+			set_ip_source_policy(false, true, IPRULE_PRIORITY_PREFIX_UNREACHABLE, &prefix,
+					prefix_old->length, 0, prefix_old->iface, "unreachable", true);
+		}
 	}
 
 	if (node_old) {
diff --git a/iprule.h b/iprule.h
index 89b94b4..b6e1953 100644
--- a/iprule.h
+++ b/iprule.h
@@ -17,10 +17,12 @@
 
 #include "interface-ip.h"
 
-#define IPRULE_PRIORITY_ADDR		10000
-#define IPRULE_PRIORITY_ADDR_MASK	20000
-#define IPRULE_PRIORITY_NW		90000
-#define IPRULE_PRIORITY_REJECT		4200000000
+#define IPRULE_PRIORITY_ADDR			10000
+#define IPRULE_PRIORITY_ADDR_MASK		20000
+#define IPRULE_PRIORITY_SUB_PREFIX		30000
+#define IPRULE_PRIORITY_PREFIX_UNREACHABLE	30001
+#define IPRULE_PRIORITY_NW			90000
+#define IPRULE_PRIORITY_REJECT			4200000000
 
 enum iprule_flags {
 	/* address family for rule */
-- 
2.25.1




More information about the openwrt-devel mailing list