This patch implements the comments received for version 1 (see https://lists.openwrt.org/pipermail/openwrt-devel/2013-May/020119.html), and improves upon version 2 by respecting metrics.
* Routes are only added to the specified table. The only exception are the IPv4-routes Linux automatically adds to the default table when an address with a mask less than 32 comes up. In order to respect the metrics, each automatic routes is replaced by one containing the correct metric (if metric is set). Prefsrc is set as the source hint is needed in case of overlapping subnets. The automatic routes, or equivalent, needed to be present (in the default table) for for example adding default routes to interfaces to work. * The rules are divided into three 'parts', in order to support the case where a client is connected to networks with overlapping subnets. The first part consists of the "from IP-address"-rules, so that packets from sockets bound to IPs on the device go through the intended interface. The order here does not matter. Then follows lookups in main and default, before the networks and "from lo"-rules are checked. If metric is set, the priority of this rules is 65535 + metric, otherwise the priority is 65535 + ifindex. The "from lo .." rules are needed so that traffic from sockets not bound to an IP also is routed correctly. * The "from lo"-rules all have different priorities. When there are multiple rules with the same priority and same source, the kernel seems to display undefined behavior. Even though table and priority was specified when netifd sends DELRULE, it seems random which rule is actually deleted. I have been testing this patch on my router for the last two days and it behaves as intended. However, all my WAN-connections are IPv4 and I was only able to test IPv6 in a small, private network. If anyone has time to check the behavior in a full IPv6-network, I would be very grateful. --- interface-ip.c | 108 ++++++++++++++++++++++++++++++++++++++++++++------------- interface-ip.h | 3 +- iprule.h | 3 ++ proto.c | 6 ++-- system-linux.c | 3 ++ 5 files changed, 94 insertions(+), 29 deletions(-) diff --git a/interface-ip.c b/interface-ip.c index e265563..05806af 100644 --- a/interface-ip.c +++ b/interface-ip.c @@ -90,28 +90,39 @@ match_if_addr(union if_addr *a1, union if_addr *a2, int mask) return !memcmp(p1, p2, sizeof(*p1)); } -static int set_ipv6_source_policy(bool add, const union if_addr *addr, uint8_t mask, int ifindex) +static int set_ip_source_policy(bool add, bool v4, unsigned int priority, + const union if_addr *addr, uint8_t mask, int ifindex) { struct iprule rule = { - .flags = IPRULE_INET6 | IPRULE_SRC | IPRULE_LOOKUP | IPRULE_PRIORITY, - .priority = 65535, - .lookup = interface_ip_resolve_v6_rtable(ifindex), + .flags = IPRULE_SRC | IPRULE_LOOKUP | IPRULE_PRIORITY, + .priority = priority, + .lookup = interface_ip_resolve_rtable(ifindex), .src_addr = *addr, .src_mask = mask, }; + if(v4) + rule.flags |= IPRULE_INET4; + else + rule.flags |= IPRULE_INET6; + return (add) ? system_add_iprule(&rule) : system_del_iprule(&rule); } -static int set_ipv6_lo_policy(bool add, int ifindex) +static int set_ip_lo_policy(bool add, bool v4, int ifindex, unsigned int pri) { struct iprule rule = { - .flags = IPRULE_INET6 | IPRULE_IN | IPRULE_LOOKUP | IPRULE_PRIORITY, - .priority = 65535, - .lookup = interface_ip_resolve_v6_rtable(ifindex), + .flags = IPRULE_IN | IPRULE_LOOKUP | IPRULE_PRIORITY, + .priority = pri, + .lookup = interface_ip_resolve_rtable(ifindex), .in_dev = "lo" }; + if(v4) + rule.flags |= IPRULE_INET4; + else + rule.flags |= IPRULE_INET6; + return (add) ? system_add_iprule(&rule) : system_del_iprule(&rule); } @@ -246,7 +257,6 @@ interface_ip_add_route(struct interface *iface, struct blob_attr *attr, bool v6) struct blob_attr *tb[__ROUTE_MAX], *cur; struct device_route *route; int af = v6 ? AF_INET6 : AF_INET; - bool is_v6_proto_route = v6 && iface; blobmsg_parse(route_attr, __ROUTE_MAX, tb, blobmsg_data(attr), blobmsg_data_len(attr)); @@ -300,10 +310,8 @@ interface_ip_add_route(struct interface *iface, struct blob_attr *attr, bool v6) } // Use source-based routing - if (is_v6_proto_route) { - route->table = interface_ip_resolve_v6_rtable(iface->l3_dev.dev->ifindex); - route->flags |= DEVROUTE_SRCTABLE; - } + route->table = interface_ip_resolve_rtable(iface->l3_dev.dev->ifindex); + route->flags |= DEVROUTE_SRCTABLE; if ((cur = tb[ROUTE_TABLE]) != NULL) { if (!system_resolve_rt_table(blobmsg_data(cur), &route->table)) { @@ -364,9 +372,11 @@ interface_handle_subnet_route(struct interface *iface, struct device_addr *addr, memset(&route, 0, sizeof(route)); route.iface = iface; - route.flags = addr->flags; + route.flags = addr->flags | DEVROUTE_SRCTABLE; route.mask = addr->mask; + route.table = interface_ip_resolve_rtable(dev->ifindex); memcpy(&route.addr, &addr->addr, sizeof(route.addr)); + clear_if_addr(&route.addr, route.mask); if (add) { @@ -394,6 +404,8 @@ interface_update_proto_addr(struct vlist_tree *tree, struct device *dev; struct device_addr *a_new = NULL, *a_old = NULL; bool keep = false; + bool v4 = false; + unsigned int nw_rule_priority; ip = container_of(tree, struct interface_ip_settings, addr); iface = ip->iface; @@ -429,12 +441,27 @@ interface_update_proto_addr(struct vlist_tree *tree, keep = false; } + if(iface->metric) + nw_rule_priority = IPRULE_PRIORITY_NW + iface->metric; + else + nw_rule_priority = IPRULE_PRIORITY_NW + + interface_ip_resolve_rtable(dev->ifindex); + if (node_old) { if (!(a_old->flags & DEVADDR_EXTERNAL) && a_old->enabled && !keep) { interface_handle_subnet_route(iface, a_old, false); - if ((a_old->flags & DEVADDR_FAMILY) == DEVADDR_INET6) - set_ipv6_source_policy(false, &a_old->addr, a_old->mask, dev->ifindex); + if ((a_old->flags & DEVADDR_FAMILY) == DEVADDR_INET4) + v4 = true; + + //This is needed for source routing to work correctly. If a device + //has two connections to a network using the same subnet, adding + //only the network-rule will cause packets to be routed through the + //first matching network (source IP matches both masks). + set_ip_source_policy(false, v4, IPRULE_PRIORITY_ADDR, &a_old->addr, + 32, dev->ifindex); + set_ip_source_policy(false, v4, nw_rule_priority, &a_old->addr, + a_old->mask, dev->ifindex); system_del_address(dev, a_old); } @@ -446,11 +473,36 @@ interface_update_proto_addr(struct vlist_tree *tree, if (!(a_new->flags & DEVADDR_EXTERNAL) && !keep) { system_add_address(dev, a_new); - if ((a_new->flags & DEVADDR_FAMILY) == DEVADDR_INET6) - set_ipv6_source_policy(true, &a_new->addr, a_new->mask, dev->ifindex); + if ((a_new->flags & DEVADDR_FAMILY) == DEVADDR_INET4) + v4 = true; + + set_ip_source_policy(true, v4, IPRULE_PRIORITY_ADDR, &a_new->addr, + 32, dev->ifindex); + + set_ip_source_policy(true, v4, nw_rule_priority, &a_new->addr, + a_new->mask, dev->ifindex); + + //Replace automatic route in default table, to ensure that metric is + //respected + if(v4 && iface->metric){ + struct device_route route; + + memset(&route, 0, sizeof(route)); + route.iface = iface; + route.flags = a_new->flags | DEVADDR_KERNEL; + route.mask = a_new->mask; + memcpy(&route.addr, &a_new->addr, sizeof(route.addr)); + clear_if_addr(&route.addr, route.mask); + system_del_route(dev, &route); + + route.metric = iface->metric; + route.prefsrc = a_new->addr; + system_add_route(dev, &route); + } if ((a_new->flags & DEVADDR_OFFLINK) || iface->metric) interface_handle_subnet_route(iface, a_new, true); + } } } @@ -758,8 +810,8 @@ interface_update_prefix(struct vlist_tree *tree, // Set null-route to avoid routing loops and set routing policy system_add_route(NULL, &route); if (prefix_new->iface) - set_ipv6_source_policy(true, &route.addr, route.mask, - prefix_new->iface->l3_dev.dev->ifindex); + set_ip_source_policy(true, false, IPRULE_PRIORITY_NW ,&route.addr, + route.mask, prefix_new->iface->l3_dev.dev->ifindex); interface_update_prefix_assignments(prefix_new, true); @@ -768,8 +820,8 @@ interface_update_prefix(struct vlist_tree *tree, // Remove null-route if (prefix_old->iface) - set_ipv6_source_policy(false, &route.addr, route.mask, - prefix_old->iface->l3_dev.dev->ifindex); + set_ip_source_policy(false, false, IPRULE_PRIORITY_NW, &route.addr, + route.mask, prefix_old->iface->l3_dev.dev->ifindex); system_del_route(NULL, &route); } @@ -841,7 +893,7 @@ interface_ip_set_ula_prefix(const char *prefix) } } -int interface_ip_resolve_v6_rtable(int ifindex) +int interface_ip_resolve_rtable(int ifindex) { return ifindex + 1000; } @@ -1041,7 +1093,15 @@ void interface_ip_set_enabled(struct interface_ip_settings *ip, bool enabled) if (!strcmp(a->name, ip->iface->name)) interface_set_prefix_address(a, c, ip->iface, enabled); - set_ipv6_lo_policy(enabled, dev->ifindex); + unsigned int iprule_priority; + if(ip->iface->metric) + iprule_priority = IPRULE_PRIORITY_NW + ip->iface->metric; + else + iprule_priority = IPRULE_PRIORITY_NW + + interface_ip_resolve_rtable(dev->ifindex); + + set_ip_lo_policy(enabled, true, dev->ifindex, iprule_priority); + set_ip_lo_policy(enabled, false, dev->ifindex, iprule_priority); } void diff --git a/interface-ip.h b/interface-ip.h index c2213fd..1c5dde7 100644 --- a/interface-ip.h +++ b/interface-ip.h @@ -107,6 +107,7 @@ struct device_route { unsigned int table; unsigned int mask; union if_addr addr; + union if_addr prefsrc; }; struct dns_server { @@ -144,6 +145,6 @@ struct device_prefix* interface_ip_add_device_prefix(struct interface *iface, struct in6_addr *excl_addr, uint8_t excl_length); void interface_ip_set_ula_prefix(const char *prefix); void interface_refresh_assignments(bool hint); -int interface_ip_resolve_v6_rtable(int ifindex); +int interface_ip_resolve_rtable(int ifindex); #endif diff --git a/iprule.h b/iprule.h index 4b10760..1e7cbe7 100644 --- a/iprule.h +++ b/iprule.h @@ -17,6 +17,9 @@ #include "interface-ip.h" +#define IPRULE_PRIORITY_ADDR 32764 +#define IPRULE_PRIORITY_NW 65535 + enum iprule_flags { /* address family for rule */ IPRULE_INET4 = (0 << 0), diff --git a/proto.c b/proto.c index 676852d..fc49458 100644 --- a/proto.c +++ b/proto.c @@ -252,10 +252,8 @@ parse_gateway_option(struct interface *iface, struct blob_attr *attr, bool v6) route->mask = 0; route->flags = (v6 ? DEVADDR_INET6 : DEVADDR_INET4); - if (v6) { - route->table = interface_ip_resolve_v6_rtable(iface->l3_dev.dev->ifindex); - route->flags |= DEVROUTE_SRCTABLE; - } + route->table = interface_ip_resolve_rtable(iface->l3_dev.dev->ifindex); + route->flags |= DEVROUTE_SRCTABLE; vlist_add(&iface->proto_ip.route, &route->node, route); diff --git a/system-linux.c b/system-linux.c index f5c900d..a14806f 100644 --- a/system-linux.c +++ b/system-linux.c @@ -1009,6 +1009,9 @@ static int system_rt(struct device *dev, struct device_route *route, int cmd) if (table >= 256) nla_put_u32(msg, RTA_TABLE, table); + if(alen == 4 && !!route->prefsrc.in.s_addr) + nla_put(msg, RTA_PREFSRC, alen, &route->prefsrc); + return system_rtnl_call(msg); } -- 1.8.1.2 _______________________________________________ openwrt-devel mailing list openwrt-devel@lists.openwrt.org https://lists.openwrt.org/mailman/listinfo/openwrt-devel