Improved IPv6 featureset
authorSteven Barth <steven@midlink.org>
Fri, 1 Feb 2013 12:17:41 +0000 (13:17 +0100)
committerSteven Barth <steven@midlink.org>
Fri, 1 Feb 2013 12:17:41 +0000 (13:17 +0100)
* Fix reloading of ula-prefixes
* Added support for temporary addresses and routes
* Added support for offlink addresses
* Improved status-output for assigned prefixes

dummy/netifd-proto.sh
interface-ip.c
interface-ip.h
proto.c
ubus.c
utils.h

index 17efd59..ae5a346 100755 (executable)
@@ -122,8 +122,11 @@ proto_add_ipv4_address() {
 proto_add_ipv6_address() {
        local address="$1"
        local mask="$2"
 proto_add_ipv6_address() {
        local address="$1"
        local mask="$2"
+       local preferred="$3"
+       local valid="$4"
+       local offlink="$5"
 
 
-       append PROTO_IP6ADDR "$address/$mask"
+       append PROTO_IP6ADDR "$address/$mask/$preferred/$valid/$offlink"
 }
 
 proto_add_ipv4_route() {
 }
 
 proto_add_ipv4_route() {
@@ -131,15 +134,17 @@ proto_add_ipv4_route() {
        local mask="$2"
        local gw="$3"
 
        local mask="$2"
        local gw="$3"
 
-       append PROTO_ROUTE "$target/$mask/$gw"
+       append PROTO_ROUTE "$target/$mask/$gw//"
 }
 
 proto_add_ipv6_route() {
        local target="$1"
        local mask="$2"
        local gw="$3"
 }
 
 proto_add_ipv6_route() {
        local target="$1"
        local mask="$2"
        local gw="$3"
+       local metric="$4"
+       local valid="$5"
 
 
-       append PROTO_ROUTE6 "$target/$mask/$gw"
+       append PROTO_ROUTE6 "$target/$mask/$gw/$metric/$valid"
 }
 
 proto_add_ipv6_prefix() {
 }
 
 proto_add_ipv6_prefix() {
@@ -177,15 +182,24 @@ _proto_push_ipv4_addr() {
 
 _proto_push_ipv6_addr() {
        local str="$1"
 
 _proto_push_ipv6_addr() {
        local str="$1"
-       local address mask
+       local address mask preferred valid offlink
 
        address="${str%%/*}"
        str="${str#*/}"
 
        address="${str%%/*}"
        str="${str#*/}"
-       mask="$str"
+       mask="${str%%/*}"
+       str="${str#*/}"
+       preferred="${str%%/*}"
+       str="${str#*/}"
+       valid="${str%%/*}"
+       str="${str#*/}"
+       offlink="${str%%/*}"
 
        json_add_object ""
        json_add_string ipaddr "$address"
        [ -n "$mask" ] && json_add_string mask "$mask"
 
        json_add_object ""
        json_add_string ipaddr "$address"
        [ -n "$mask" ] && json_add_string mask "$mask"
+       [ -n "$preferred" ] && json_add_int preferred "$preferred"
+       [ -n "$valid" ] && json_add_int valid "$valid"
+       [ -n "$offlink" ] && json_add_boolean offlink "$offlink"
        json_close_object
 }
 
        json_close_object
 }
 
@@ -198,12 +212,19 @@ _proto_push_route() {
        local target="${str%%/*}"
        str="${str#*/}"
        local mask="${str%%/*}"
        local target="${str%%/*}"
        str="${str#*/}"
        local mask="${str%%/*}"
-       local gw="${str#*/}"
+       str="${str#*/}"
+       local gw="${str%%/*}"
+       str="${str#*/}"
+       local metric="${str%%/*}"
+       str="${str#*/}"
+       local valid="${str%%/*}"
 
        json_add_object ""
        json_add_string target "$target"
        json_add_string netmask "$mask"
        [ -n "$gw" ] && json_add_string gateway "$gw"
 
        json_add_object ""
        json_add_string target "$target"
        json_add_string netmask "$mask"
        [ -n "$gw" ] && json_add_string gateway "$gw"
+       [ -n "$metric" ] && json_add_int metric "$metric"
+       [ -n "$valid" ] && json_add_int valid "$valid"
        json_close_object
 }
 
        json_close_object
 }
 
index 6906d62..df2904f 100644 (file)
@@ -33,6 +33,7 @@ enum {
        ROUTE_GATEWAY,
        ROUTE_METRIC,
        ROUTE_MTU,
        ROUTE_GATEWAY,
        ROUTE_METRIC,
        ROUTE_MTU,
+       ROUTE_VALID,
        __ROUTE_MAX
 };
 
        __ROUTE_MAX
 };
 
@@ -43,6 +44,7 @@ static const struct blobmsg_policy route_attr[__ROUTE_MAX] = {
        [ROUTE_GATEWAY] = { .name = "gateway", .type = BLOBMSG_TYPE_STRING },
        [ROUTE_METRIC] = { .name = "metric", .type = BLOBMSG_TYPE_INT32 },
        [ROUTE_MTU] = { .name = "mtu", .type = BLOBMSG_TYPE_INT32 },
        [ROUTE_GATEWAY] = { .name = "gateway", .type = BLOBMSG_TYPE_STRING },
        [ROUTE_METRIC] = { .name = "metric", .type = BLOBMSG_TYPE_INT32 },
        [ROUTE_MTU] = { .name = "mtu", .type = BLOBMSG_TYPE_INT32 },
+       [ROUTE_VALID] = { .name = "valid", .type = BLOBMSG_TYPE_INT32 },
 };
 
 const struct config_param_list route_attr_list = {
 };
 
 const struct config_param_list route_attr_list = {
@@ -53,6 +55,7 @@ const struct config_param_list route_attr_list = {
 
 struct list_head prefixes = LIST_HEAD_INIT(prefixes);
 static struct device_prefix *ula_prefix = NULL;
 
 struct list_head prefixes = LIST_HEAD_INIT(prefixes);
 static struct device_prefix *ula_prefix = NULL;
+static struct uloop_timeout valid_until_timeout;
 
 
 static void
 
 
 static void
@@ -248,6 +251,9 @@ interface_ip_add_route(struct interface *iface, struct blob_attr *attr, bool v6)
                route->flags |= DEVROUTE_MTU;
        }
 
                route->flags |= DEVROUTE_MTU;
        }
 
+       if ((cur = tb[ROUTE_VALID]) != NULL)
+               route->valid_until = system_get_rtime() + blobmsg_get_u32(cur);
+
        vlist_add(&ip->route, &route->node, &route->flags);
        return;
 
        vlist_add(&ip->route, &route->node, &route->flags);
        return;
 
@@ -293,11 +299,14 @@ interface_handle_subnet_route(struct interface *iface, struct device_addr *addr,
                route.flags |= DEVADDR_KERNEL;
                system_del_route(dev, &route);
 
                route.flags |= DEVADDR_KERNEL;
                system_del_route(dev, &route);
 
-               route.flags &= ~DEVADDR_KERNEL;
-               route.metric = iface->metric;
-               system_add_route(dev, &route);
+               if (!(addr->flags & DEVADDR_OFFLINK)) {
+                       route.flags &= ~DEVADDR_KERNEL;
+                       route.metric = iface->metric;
+                       system_add_route(dev, &route);
+               }
        } else {
        } else {
-               system_del_route(dev, &route);
+               if (!(addr->flags & DEVADDR_OFFLINK))
+                       system_del_route(dev, &route);
        }
 }
 
        }
 }
 
@@ -336,7 +345,9 @@ interface_update_proto_addr(struct vlist_tree *tree,
        if (a_new && a_old) {
                keep = true;
 
        if (a_new && a_old) {
                keep = true;
 
-               if (a_old->flags != a_new->flags)
+               if (a_old->flags != a_new->flags ||
+                               a_old->valid_until != a_new->valid_until ||
+                               a_old->preferred_until != a_new->preferred_until)
                        keep = false;
 
                if ((a_new->flags & DEVADDR_FAMILY) == DEVADDR_INET4 &&
                        keep = false;
 
                if ((a_new->flags & DEVADDR_FAMILY) == DEVADDR_INET4 &&
@@ -356,7 +367,7 @@ interface_update_proto_addr(struct vlist_tree *tree,
                a_new->enabled = true;
                if (!(a_new->flags & DEVADDR_EXTERNAL) && !keep) {
                        system_add_address(dev, a_new);
                a_new->enabled = true;
                if (!(a_new->flags & DEVADDR_EXTERNAL) && !keep) {
                        system_add_address(dev, a_new);
-                       if (iface->metric)
+                       if ((a_new->flags & DEVADDR_OFFLINK) || iface->metric)
                                interface_handle_subnet_route(iface, a_new, true);
                }
        }
                                interface_handle_subnet_route(iface, a_new, true);
                }
        }
@@ -618,7 +629,7 @@ interface_update_prefix(struct vlist_tree *tree,
                list_add(&prefix_new->head, &prefixes);
 }
 
                list_add(&prefix_new->head, &prefixes);
 }
 
-void
+struct device_prefix*
 interface_ip_add_device_prefix(struct interface *iface, struct in6_addr *addr,
                uint8_t length, time_t valid_until, time_t preferred_until)
 {
 interface_ip_add_device_prefix(struct interface *iface, struct in6_addr *addr,
                uint8_t length, time_t valid_until, time_t preferred_until)
 {
@@ -633,6 +644,8 @@ interface_ip_add_device_prefix(struct interface *iface, struct in6_addr *addr,
                vlist_add(&iface->proto_ip.prefix, &prefix->node, &prefix->addr);
        else
                interface_update_prefix(NULL, &prefix->node, NULL);
                vlist_add(&iface->proto_ip.prefix, &prefix->node, &prefix->addr);
        else
                interface_update_prefix(NULL, &prefix->node, NULL);
+
+       return prefix;
 }
 
 void
 }
 
 void
@@ -657,13 +670,13 @@ interface_ip_set_ula_prefix(const char *prefix)
        if (!prefixlen || (length = atoi(prefixlen)) < 1 || length > 64)
                return;
 
        if (!prefixlen || (length = atoi(prefixlen)) < 1 || length > 64)
                return;
 
-       if (ula_prefix && (!IN6_ARE_ADDR_EQUAL(&addr, &ula_prefix->addr) ||
-                       ula_prefix->length != length)) {
-               interface_update_prefix(NULL, NULL, &ula_prefix->node);
-               ula_prefix = NULL;
-       }
+       if (!ula_prefix || !IN6_ARE_ADDR_EQUAL(&addr, &ula_prefix->addr) ||
+                       ula_prefix->length != length) {
+               if (ula_prefix)
+                       interface_update_prefix(NULL, NULL, &ula_prefix->node);
 
 
-       interface_ip_add_device_prefix(NULL, &addr, length, 0, 0);
+               ula_prefix = interface_ip_add_device_prefix(NULL, &addr, length, 0, 0);
+       }
 }
 
 void
 }
 
 void
@@ -894,4 +907,42 @@ interface_ip_init(struct interface *iface)
        __interface_ip_init(&iface->proto_ip, iface);
        __interface_ip_init(&iface->config_ip, iface);
        vlist_init(&iface->host_routes, route_cmp, interface_update_host_route);
        __interface_ip_init(&iface->proto_ip, iface);
        __interface_ip_init(&iface->config_ip, iface);
        vlist_init(&iface->host_routes, route_cmp, interface_update_host_route);
+
+}
+
+static void
+interface_ip_valid_until_handler(struct uloop_timeout *t)
+{
+       time_t now = system_get_rtime();
+       struct interface *iface;
+       vlist_for_each_element(&interfaces, iface, node) {
+               if (iface->state != IFS_UP)
+                       continue;
+
+               struct device_addr *addr, *addrp;
+               struct device_route *route, *routep;
+               struct device_prefix *pref, *prefp;
+
+               vlist_for_each_element_safe(&iface->proto_ip.addr, addr, node, addrp)
+                       if (addr->valid_until && addr->valid_until < now)
+                               vlist_delete(&iface->proto_ip.addr, &addr->node);
+
+               vlist_for_each_element_safe(&iface->proto_ip.route, route, node, routep)
+                       if (route->valid_until && route->valid_until < now)
+                               vlist_delete(&iface->proto_ip.route, &route->node);
+
+               vlist_for_each_element_safe(&iface->proto_ip.prefix, pref, node, prefp)
+                       if (pref->valid_until && pref->valid_until < now)
+                               vlist_delete(&iface->proto_ip.prefix, &pref->node);
+
+       }
+
+       uloop_timeout_set(t, 1000);
+}
+
+static void __init
+interface_ip_init_worker(void)
+{
+       valid_until_timeout.cb = interface_ip_valid_until_handler;
+       uloop_timeout_set(&valid_until_timeout, 1000);
 }
 }
index 054ed40..9b4f0a1 100644 (file)
@@ -33,6 +33,9 @@ enum device_addr_flags {
 
        /* route automatically added by kernel */
        DEVADDR_KERNEL          = (1 << 5),
 
        /* route automatically added by kernel */
        DEVADDR_KERNEL          = (1 << 5),
+
+       /* address is off-link (no subnet-route) */
+       DEVADDR_OFFLINK         = (1 << 6),
 };
 
 union if_addr {
 };
 
 union if_addr {
@@ -88,11 +91,12 @@ struct device_route {
        bool keep;
 
        union if_addr nexthop;
        bool keep;
 
        union if_addr nexthop;
-       int metric;
        int mtu;
        int mtu;
+       time_t valid_until;
 
        /* must be last */
        enum device_addr_flags flags;
 
        /* must be last */
        enum device_addr_flags flags;
+       int metric; // there can be multiple routes to the same target
        unsigned int mask;
        union if_addr addr;
 };
        unsigned int mask;
        union if_addr addr;
 };
@@ -129,8 +133,8 @@ struct interface *interface_ip_add_target_route(union if_addr *addr, bool v6);
 
 void interface_ip_set_prefix_assignment(struct device_prefix *prefix,
                struct interface *iface, uint8_t length);
 
 void interface_ip_set_prefix_assignment(struct device_prefix *prefix,
                struct interface *iface, uint8_t length);
-void interface_ip_add_device_prefix(struct interface *iface, struct in6_addr *addr,
-               uint8_t length, time_t valid_until, time_t preferred_until);
+struct device_prefix* interface_ip_add_device_prefix(struct interface *iface,
+               struct in6_addr *addr, uint8_t length, time_t valid_until, time_t preferred_until);
 void interface_ip_set_ula_prefix(const char *prefix);
 
 #endif
 void interface_ip_set_ula_prefix(const char *prefix);
 
 #endif
diff --git a/proto.c b/proto.c
index b45a2d5..d040be8 100644 (file)
--- a/proto.c
+++ b/proto.c
@@ -66,6 +66,9 @@ enum {
        ADDR_MASK,
        ADDR_BROADCAST,
        ADDR_PTP,
        ADDR_MASK,
        ADDR_BROADCAST,
        ADDR_PTP,
+       ADDR_PREFERRED,
+       ADDR_VALID,
+       ADDR_OFFLINK,
        __ADDR_MAX
 };
 
        __ADDR_MAX
 };
 
@@ -74,6 +77,9 @@ static const struct blobmsg_policy proto_ip_addr[__ADDR_MAX] = {
        [ADDR_MASK] = { .name = "mask", .type = BLOBMSG_TYPE_STRING },
        [ADDR_BROADCAST] = { .name = "broadcast", .type = BLOBMSG_TYPE_STRING },
        [ADDR_PTP] = { .name = "ptp", .type = BLOBMSG_TYPE_STRING },
        [ADDR_MASK] = { .name = "mask", .type = BLOBMSG_TYPE_STRING },
        [ADDR_BROADCAST] = { .name = "broadcast", .type = BLOBMSG_TYPE_STRING },
        [ADDR_PTP] = { .name = "ptp", .type = BLOBMSG_TYPE_STRING },
+       [ADDR_PREFERRED] = { .name = "preferred", .type = BLOBMSG_TYPE_INT32 },
+       [ADDR_VALID] = { .name = "valid", .type = BLOBMSG_TYPE_INT32 },
+       [ADDR_OFFLINK] = { .name = "offlink", .type = BLOBMSG_TYPE_BOOL },
 };
 
 static struct device_addr *
 };
 
 static struct device_addr *
@@ -169,6 +175,9 @@ parse_address_item(struct blob_attr *attr, bool v6, bool ext)
        if (!inet_pton(v6 ? AF_INET6 : AF_INET, blobmsg_data(cur), &addr->addr))
                goto error;
 
        if (!inet_pton(v6 ? AF_INET6 : AF_INET, blobmsg_data(cur), &addr->addr))
                goto error;
 
+       if ((cur = tb[ADDR_OFFLINK]) && blobmsg_get_bool(cur))
+               addr->flags |= DEVADDR_OFFLINK;
+
        if (!v6) {
                if ((cur = tb[ADDR_BROADCAST]) &&
                    !inet_pton(AF_INET, blobmsg_data(cur), &addr->broadcast))
        if (!v6) {
                if ((cur = tb[ADDR_BROADCAST]) &&
                    !inet_pton(AF_INET, blobmsg_data(cur), &addr->broadcast))
@@ -176,6 +185,27 @@ parse_address_item(struct blob_attr *attr, bool v6, bool ext)
                if ((cur = tb[ADDR_PTP]) &&
                    !inet_pton(AF_INET, blobmsg_data(cur), &addr->point_to_point))
                        goto error;
                if ((cur = tb[ADDR_PTP]) &&
                    !inet_pton(AF_INET, blobmsg_data(cur), &addr->point_to_point))
                        goto error;
+       } else {
+               time_t now = system_get_rtime();
+               if ((cur = tb[ADDR_PREFERRED])) {
+                       uint32_t preferred = blobmsg_get_u32(cur);
+                       if (preferred < UINT32_MAX)
+                               addr->preferred_until = now + preferred;
+               }
+
+               if ((cur = tb[ADDR_VALID])) {
+                       uint32_t valid = blobmsg_get_u32(cur);
+                       if (valid < UINT32_MAX)
+                               addr->valid_until = now + valid;
+
+               }
+
+               if (addr->valid_until) {
+                       if (!addr->preferred_until)
+                               addr->preferred_until = addr->valid_until;
+                       else if (addr->preferred_until > addr->valid_until)
+                               goto error;
+               }
        }
 
        return addr;
        }
 
        return addr;
diff --git a/ubus.c b/ubus.c
index 2fd9e92..f2421cc 100644 (file)
--- a/ubus.c
+++ b/ubus.c
@@ -347,6 +347,7 @@ interface_ip_dump_address_list(struct interface_ip_settings *ip, bool v6,
        int buflen = 128;
        int af;
 
        int buflen = 128;
        int af;
 
+       time_t now = system_get_rtime();
        vlist_for_each_element(&ip->addr, addr, node) {
                if (addr->enabled != enabled)
                        continue;
        vlist_for_each_element(&ip->addr, addr, node) {
                if (addr->enabled != enabled)
                        continue;
@@ -367,6 +368,16 @@ interface_ip_dump_address_list(struct interface_ip_settings *ip, bool v6,
 
                blobmsg_add_u32(&b, "mask", addr->mask);
 
 
                blobmsg_add_u32(&b, "mask", addr->mask);
 
+               if (addr->preferred_until) {
+                       int preferred = addr->preferred_until - now;
+                       if (preferred < 0)
+                               preferred = 0;
+                       blobmsg_add_u32(&b, "preferred", preferred);
+               }
+
+               if (addr->valid_until)
+                       blobmsg_add_u32(&b, "valid", addr->valid_until - now);
+
                blobmsg_close_table(&b, a);
        }
 }
                blobmsg_close_table(&b, a);
        }
 }
@@ -380,6 +391,7 @@ interface_ip_dump_route_list(struct interface_ip_settings *ip, bool enabled)
        void *r;
        int af;
 
        void *r;
        int af;
 
+       time_t now = system_get_rtime();
        vlist_for_each_element(&ip->route, route, node) {
                if (route->enabled != enabled)
                        continue;
        vlist_for_each_element(&ip->route, route, node) {
                if (route->enabled != enabled)
                        continue;
@@ -407,6 +419,9 @@ interface_ip_dump_route_list(struct interface_ip_settings *ip, bool enabled)
                if (route->flags & DEVROUTE_METRIC)
                        blobmsg_add_u32(&b, "metric", route->metric);
 
                if (route->flags & DEVROUTE_METRIC)
                        blobmsg_add_u32(&b, "metric", route->metric);
 
+               if (route->valid_until)
+                       blobmsg_add_u32(&b, "valid", route->valid_until - now);
+
                blobmsg_close_table(&b, r);
        }
 }
                blobmsg_close_table(&b, r);
        }
 }
@@ -420,6 +435,7 @@ interface_ip_dump_prefix_list(struct interface_ip_settings *ip)
        void *a, *c;
        const int buflen = INET6_ADDRSTRLEN;
 
        void *a, *c;
        const int buflen = INET6_ADDRSTRLEN;
 
+       time_t now = system_get_rtime();
        vlist_for_each_element(&ip->prefix, prefix, node) {
                a = blobmsg_open_table(&b, NULL);
 
        vlist_for_each_element(&ip->prefix, prefix, node) {
                a = blobmsg_open_table(&b, NULL);
 
@@ -429,7 +445,6 @@ interface_ip_dump_prefix_list(struct interface_ip_settings *ip)
 
                blobmsg_add_u32(&b, "mask", prefix->length);
 
 
                blobmsg_add_u32(&b, "mask", prefix->length);
 
-               time_t now = system_get_rtime();
                if (prefix->preferred_until) {
                        int preferred = prefix->preferred_until - now;
                        if (preferred < 0)
                if (prefix->preferred_until) {
                        int preferred = prefix->preferred_until - now;
                        if (preferred < 0)
@@ -437,12 +452,8 @@ interface_ip_dump_prefix_list(struct interface_ip_settings *ip)
                        blobmsg_add_u32(&b, "preferred", preferred);
                }
 
                        blobmsg_add_u32(&b, "preferred", preferred);
                }
 
-               if (prefix->valid_until) {
-                       int valid = prefix->valid_until - now;
-                       if (valid < 0)
-                               valid = 0;
-                       blobmsg_add_u32(&b, "valid", valid);
-               }
+               if (prefix->valid_until)
+                       blobmsg_add_u32(&b, "valid", prefix->valid_until - now);
 
                c = blobmsg_open_table(&b, "assigned");
                struct device_prefix_assignment *assign;
 
                c = blobmsg_open_table(&b, "assigned");
                struct device_prefix_assignment *assign;
@@ -465,6 +476,45 @@ interface_ip_dump_prefix_list(struct interface_ip_settings *ip)
 
 
 static void
 
 
 static void
+interface_ip_dump_prefix_assignment_list(struct interface *iface)
+{
+       void *a;
+       char *buf;
+       const int buflen = INET6_ADDRSTRLEN;
+       time_t now = system_get_rtime();
+
+       struct device_prefix *prefix;
+       list_for_each_entry(prefix, &prefixes, head) {
+               struct device_prefix_assignment *assign;
+               vlist_for_each_element(prefix->assignments, assign, node) {
+                       if (strcmp(assign->name, iface->name))
+                               continue;
+
+                       a = blobmsg_open_table(&b, NULL);
+
+                       buf = blobmsg_alloc_string_buffer(&b, "address", buflen);
+                       inet_ntop(AF_INET6, &assign->addr, buf, buflen);
+                       blobmsg_add_string_buffer(&b);
+
+                       blobmsg_add_u32(&b, "mask", assign->length);
+
+                       if (prefix->preferred_until) {
+                               int preferred = prefix->preferred_until - now;
+                               if (preferred < 0)
+                                       preferred = 0;
+                               blobmsg_add_u32(&b, "preferred", preferred);
+                       }
+
+                       if (prefix->valid_until)
+                               blobmsg_add_u32(&b, "valid", prefix->valid_until - now);
+
+                       blobmsg_close_table(&b, a);
+               }
+       }
+}
+
+
+static void
 interface_ip_dump_dns_server_list(struct interface_ip_settings *ip,
                                   bool enabled)
 {
 interface_ip_dump_dns_server_list(struct interface_ip_settings *ip,
                                   bool enabled)
 {
@@ -542,6 +592,9 @@ netifd_handle_status(struct ubus_context *ctx, struct ubus_object *obj,
                interface_ip_dump_prefix_list(&iface->config_ip);
                interface_ip_dump_prefix_list(&iface->proto_ip);
                blobmsg_close_array(&b, a);
                interface_ip_dump_prefix_list(&iface->config_ip);
                interface_ip_dump_prefix_list(&iface->proto_ip);
                blobmsg_close_array(&b, a);
+               a = blobmsg_open_array(&b, "ipv6-prefix-assignment");
+               interface_ip_dump_prefix_assignment_list(iface);
+               blobmsg_close_array(&b, a);
                a = blobmsg_open_array(&b, "route");
                interface_ip_dump_route_list(&iface->config_ip, true);
                interface_ip_dump_route_list(&iface->proto_ip, true);
                a = blobmsg_open_array(&b, "route");
                interface_ip_dump_route_list(&iface->config_ip, true);
                interface_ip_dump_route_list(&iface->proto_ip, true);
diff --git a/utils.h b/utils.h
index e159b42..d675a5c 100644 (file)
--- a/utils.h
+++ b/utils.h
@@ -42,6 +42,9 @@ struct vlist_simple_node {
        int version;
 };
 
        int version;
 };
 
+#define vlist_for_each_element_safe(tree, element, node_member, ptr) \
+        avl_for_each_element_safe(&(tree)->avl, element, node_member.avl, ptr)
+
 #define vlist_simple_init(tree, node, member) \
        __vlist_simple_init(tree, offsetof(node, member))
 
 #define vlist_simple_init(tree, node, member) \
        __vlist_simple_init(tree, offsetof(node, member))