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

Reply via email to