From: Kristian Evensen <kristian.even...@gmail.com> Multi-wan support was recently added to netifd, but limited to IPv6. This patch enables multi-wan for IPv4 as well. In addition, the patch introduces some changes that make the multi-wan support more robust.
1) Instead of using the interface index to decide on interface metric, a table-option is added to interfaces. This way, users are sure which tables will be used for policy routing and can avoid overlaps. The table-option must be set for an interface to be 'multi-wan', and all routes belonging to the interface will be added to this table. 2) Routes are added both to the original table (for example main) and the interface-specific table. This is done to ensure networked applications running on the node will behave as intended. If routes are only added to the interface-specific tables, traffic from applications not binding to an interface will not be routed correctly. The IPv6 multi-wan support has been converted to use the generic functions. --- interface-ip.c | 35 +++++++++++++++++++++++------------ interface.c | 10 ++++++++++ interface.h | 4 ++++ proto.c | 1 + system-linux.c | 28 ++++++++++++++++++++++------ 5 files changed, 60 insertions(+), 18 deletions(-) diff --git a/interface-ip.c b/interface-ip.c index e265563..f88162b 100644 --- a/interface-ip.c +++ b/interface-ip.c @@ -90,16 +90,19 @@ 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, const union if_addr *addr, + uint8_t mask, int ifindex, unsigned int table) { struct iprule rule = { - .flags = IPRULE_INET6 | IPRULE_SRC | IPRULE_LOOKUP | IPRULE_PRIORITY, + .flags = IPRULE_SRC | IPRULE_LOOKUP | IPRULE_PRIORITY, .priority = 65535, - .lookup = interface_ip_resolve_v6_rtable(ifindex), + .lookup = table, .src_addr = *addr, .src_mask = mask, }; + rule.flags |= (v4) ? IPRULE_INET4 : IPRULE_INET6; + return (add) ? system_add_iprule(&rule) : system_del_iprule(&rule); } @@ -267,6 +270,7 @@ interface_ip_add_route(struct interface *iface, struct blob_attr *attr, bool v6) if (!route) return; + route->iface = iface; route->flags = v6 ? DEVADDR_INET6 : DEVADDR_INET4; route->mask = v6 ? 128 : 32; if ((cur = tb[ROUTE_MASK]) != NULL) { @@ -433,8 +437,11 @@ interface_update_proto_addr(struct vlist_tree *tree, 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(iface->interface_table > 0){ + bool v4 = (a_old->flags & DEVADDR_FAMILY) == DEVADDR_INET4; + set_ip_source_policy(false, v4, &a_old->addr, a_old->mask, + dev->ifindex, iface->interface_table); + } system_del_address(dev, a_old); } @@ -446,8 +453,11 @@ 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(iface->interface_table > 0){ + bool v4 = (a_new->flags & DEVADDR_FAMILY) == DEVADDR_INET4; + set_ip_source_policy(true, v4, &a_new->addr, a_new->mask, + dev->ifindex, iface->interface_table); + } if ((a_new->flags & DEVADDR_OFFLINK) || iface->metric) interface_handle_subnet_route(iface, a_new, true); @@ -758,8 +768,9 @@ 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, &route.addr, route.mask, + prefix_new->iface->l3_dev.dev->ifindex, + prefix_new->iface->interface_table); interface_update_prefix_assignments(prefix_new, true); @@ -768,8 +779,9 @@ 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, &route.addr, route.mask, + prefix_old->iface->l3_dev.dev->ifindex, + prefix_old->iface->interface_table); system_del_route(NULL, &route); } @@ -968,7 +980,6 @@ interface_write_resolv_conf(void) vlist_simple_empty(&iface->config_ip.dns_servers)) continue; - fprintf(f, "# Interface %s\n", iface->name); write_resolv_conf_entries(f, &iface->config_ip); if (!iface->proto_ip.no_dns) write_resolv_conf_entries(f, &iface->proto_ip); diff --git a/interface.c b/interface.c index a2c3f44..30ef9f8 100644 --- a/interface.c +++ b/interface.c @@ -39,6 +39,7 @@ enum { IFACE_ATTR_INTERFACE, IFACE_ATTR_IP6ASSIGN, IFACE_ATTR_IP6HINT, + IFACE_ATTR_TABLE, IFACE_ATTR_MAX }; @@ -54,6 +55,7 @@ static const struct blobmsg_policy iface_attrs[IFACE_ATTR_MAX] = { [IFACE_ATTR_INTERFACE] = { .name = "interface", .type = BLOBMSG_TYPE_STRING }, [IFACE_ATTR_IP6ASSIGN] = { .name = "ip6assign", .type = BLOBMSG_TYPE_INT32 }, [IFACE_ATTR_IP6HINT] = { .name = "ip6hint", .type = BLOBMSG_TYPE_STRING }, + [IFACE_ATTR_TABLE] = { .name = "table", .type = BLOBMSG_TYPE_STRING }, }; static const union config_param_info iface_attr_info[IFACE_ATTR_MAX] = { @@ -497,6 +499,13 @@ interface_init(struct interface *iface, const char *name, iface->config_ip.assignment_hint = strtol(blobmsg_get_string(cur), NULL, 16) & ~((1 << (64 - iface->config_ip.assignment_length)) - 1); + if((cur = tb[IFACE_ATTR_TABLE])){ + if(!system_resolve_rt_table(blobmsg_data(cur), &iface->interface_table)) + netifd_log_message(L_WARNING, "Could not resolve table\n"); + } else + //Routing table 0 is the 'all' table, so is safe to use as not-set value + iface->interface_table = 0; + iface->config_autostart = iface->autostart; } @@ -512,6 +521,7 @@ static bool __interface_add(struct interface *iface, struct blob_attr *config, b if ((cur = tb[IFACE_ATTR_INTERFACE])) iface->parent_ifname = blobmsg_data(cur); + if (!iface->parent_ifname) return false; } else { diff --git a/interface.h b/interface.h index 0c56b36..ee69e5b 100644 --- a/interface.h +++ b/interface.h @@ -119,6 +119,10 @@ struct interface { int metric; + /* Proper multihoming support requires policy routing. Store all routes + * going through this interface in this table */ + unsigned int interface_table; + /* errors/warnings while trying to bring up the interface */ struct list_head errors; diff --git a/proto.c b/proto.c index 676852d..322a41f 100644 --- a/proto.c +++ b/proto.c @@ -249,6 +249,7 @@ parse_gateway_option(struct interface *iface, struct blob_attr *attr, bool v6) return false; } + route->iface = iface; route->mask = 0; route->flags = (v6 ? DEVADDR_INET6 : DEVADDR_INET4); diff --git a/system-linux.c b/system-linux.c index f5c900d..bd169f9 100644 --- a/system-linux.c +++ b/system-linux.c @@ -266,6 +266,7 @@ static int system_rtnl_call(struct nl_msg *msg) int ret; ret = nl_send_auto_complete(sock_rtnl, msg); + nlmsg_free(msg); if (ret < 0) @@ -972,7 +973,9 @@ static int system_rt(struct device *dev, struct device_route *route, int cmd) struct rtmsg rtm = { .rtm_family = (alen == 4) ? AF_INET : AF_INET6, .rtm_dst_len = route->mask, - .rtm_table = (table < 256) ? table : RT_TABLE_UNSPEC, + //Kernel will only use one route, so no need to set twice (RTA_TABLE). + //Also, route is automatically added to RT_TABLE_UNSPEC. + .rtm_table = table, .rtm_protocol = (route->flags & DEVADDR_KERNEL) ? RTPROT_KERNEL : RTPROT_STATIC, .rtm_scope = scope, .rtm_type = (cmd == RTM_DELROUTE) ? 0: RTN_UNICAST, @@ -980,7 +983,7 @@ static int system_rt(struct device *dev, struct device_route *route, int cmd) struct nl_msg *msg; if (cmd == RTM_NEWROUTE) { - flags |= NLM_F_CREATE | NLM_F_REPLACE; + flags |= NLM_F_CREATE | NLM_F_APPEND | NLM_F_REPLACE; if (!dev) { // Add null-route rtm.rtm_scope = RT_SCOPE_UNIVERSE; @@ -1006,10 +1009,23 @@ static int system_rt(struct device *dev, struct device_route *route, int cmd) if (dev) nla_put_u32(msg, RTA_OIF, dev->ifindex); - if (table >= 256) - nla_put_u32(msg, RTA_TABLE, table); - - return system_rtnl_call(msg); + //All routes belonging to one interface are added to both the desired table + //and the interface table. This is required for example for default routes. + //If they are only present in the interface table, applications running on + //the device and which does not bind to a specific IP, will not work as + //intended. + if(route->iface != NULL && route->iface->interface_table && + table != route->iface->interface_table){ + nlmsg_get(msg); + int retval = system_rtnl_call(msg); + struct rtmsg* rtm_ptr = (struct rtmsg*) nlmsg_data(nlmsg_hdr(msg)); + + rtm_ptr->rtm_table = route->iface->interface_table; + retval = system_rtnl_call(msg); + + return retval; + } else + return system_rtnl_call(msg); } int system_add_route(struct device *dev, struct device_route *route) -- 1.8.1.2 _______________________________________________ openwrt-devel mailing list openwrt-devel@lists.openwrt.org https://lists.openwrt.org/mailman/listinfo/openwrt-devel