interface_ip_update_complete(&iface->config_ip);
}
+static void
+config_init_globals(void)
+{
+ struct uci_section *globals = uci_lookup_section(
+ uci_ctx, uci_network, "globals");
+ if (!globals)
+ return;
+
+ const char *ula_prefix = uci_lookup_option_string(
+ uci_ctx, globals, "ula_prefix");
+ interface_ip_set_ula_prefix(ula_prefix);
+}
+
void
config_init_all(void)
{
config_init_devices();
config_init_interfaces();
config_init_routes();
+ config_init_globals();
config_init = false;
device_unlock();
PROTO_IP6ADDR=
PROTO_ROUTE=
PROTO_ROUTE6=
+ PROTO_PREFIX6=
PROTO_DNS=
PROTO_DNS_SEARCH=
json_init
append PROTO_ROUTE6 "$target/$mask/$gw"
}
+proto_add_ipv6_prefix() {
+ local prefix="$1"
+ local valid="$2"
+ local preferred="$3"
+
+ if [ -z "$valid" ]; then
+ append PROTO_PREFIX6 "$prefix"
+ else
+ [ -z "$preferred" ] && preferred="$valid"
+ append PROTO_PREFIX6 "$prefix,$valid,$preferred"
+ fi
+}
+
_proto_push_ipv4_addr() {
local str="$1"
local address mask broadcast ptp
_proto_push_array "ip6addr" "$PROTO_IP6ADDR" _proto_push_ipv6_addr
_proto_push_array "routes" "$PROTO_ROUTE" _proto_push_route
_proto_push_array "routes6" "$PROTO_ROUTE6" _proto_push_route
+ _proto_push_array "ip6prefix" "$PROTO_PREFIX6" _proto_push_string
_proto_push_array "dns" "$PROTO_DNS" _proto_push_string
_proto_push_array "dns_search" "$PROTO_DNS_SEARCH" _proto_push_string
_proto_notify "$interface"
.params = route_attr,
};
+
+struct list_head prefixes = LIST_HEAD_INIT(prefixes);
+static struct device_prefix *ula_prefix = NULL;
+
+
static void
clear_if_addr(union if_addr *a, int mask)
{
offsetof(struct device_route, flags));
}
+static int
+prefix_cmp(const void *k1, const void *k2, void *ptr)
+{
+ return memcmp(k1, k2, sizeof(struct device_prefix) -
+ offsetof(struct device_prefix, addr));
+}
+
+static int
+prefix_assignment_cmp(const void *k1, const void *k2, void *ptr)
+{
+ return strcmp((const char*)k1, (const char*)k2);
+}
+
static void
interface_handle_subnet_route(struct interface *iface, struct device_addr *addr, bool add)
{
system_add_route(dev, route_new);
}
+
+static void
+interface_set_prefix_address(struct interface *iface, bool add,
+ struct device_prefix_assignment *assignment)
+{
+ struct interface *uplink = assignment->prefix->iface;
+ if (!iface->l3_dev.dev)
+ return;
+
+ struct device *l3_downlink = iface->l3_dev.dev;
+
+ struct device_addr addr;
+ memset(&addr, 0, sizeof(addr));
+ addr.addr.in6 = assignment->addr;
+ addr.mask = assignment->length;
+ addr.flags = DEVADDR_INET6;
+ addr.preferred_until = assignment->prefix->preferred_until;
+ addr.valid_until = assignment->prefix->valid_until;
+
+ if (!add) {
+ if (assignment->enabled)
+ system_del_address(l3_downlink, &addr);
+ } else {
+ system_add_address(l3_downlink, &addr);
+
+ if (uplink && uplink->l3_dev.dev) {
+ int mtu = system_update_ipv6_mtu(
+ uplink->l3_dev.dev, 0);
+ if (mtu > 0)
+ system_update_ipv6_mtu(l3_downlink, mtu);
+ }
+ }
+ assignment->enabled = add;
+}
+
+
+static void
+interface_update_prefix_assignments(struct vlist_tree *tree,
+ struct vlist_node *node_new,
+ struct vlist_node *node_old)
+{
+ struct device_prefix_assignment *old, *new;
+ old = container_of(node_old, struct device_prefix_assignment, node);
+ new = container_of(node_new, struct device_prefix_assignment, node);
+
+ // Assignments persist across interface reloads etc.
+ // so use indirection to avoid dangling pointers
+ struct interface *iface = vlist_find(&interfaces,
+ (node_new) ? new->name : old->name, iface, node);
+
+ if (node_old && node_new) {
+ new->addr = old->addr;
+ new->length = old->length;
+ } else if (node_old) {
+ if (iface)
+ interface_set_prefix_address(iface, false, old);
+ free(old->name);
+ free(old);
+ } else if (node_new) {
+ struct device_prefix *prefix = new->prefix;
+ uint64_t want = 1ULL << (64 - new->length);
+ prefix->avail &= ~(want - 1);
+ prefix->avail -= want;
+
+ // Invert assignment
+ uint64_t assigned = ~prefix->avail;
+ assigned &= (1ULL << (64 - prefix->length)) - 1;
+ assigned &= ~(want - 1);
+
+ // Assignment
+ new->addr = prefix->addr;
+ new->addr.s6_addr32[0] |=
+ htonl(assigned >> 32);
+ new->addr.s6_addr32[1] |=
+ htonl(assigned & 0xffffffffU);
+ new->addr.s6_addr[15] += 1;
+ }
+
+ if (node_new && (iface->state == IFS_UP || iface->state == IFS_SETUP))
+ interface_set_prefix_address(iface, true, new);
+}
+
+
+void
+interface_ip_set_prefix_assignment(struct device_prefix *prefix,
+ struct interface *iface, uint8_t length)
+{
+ if (!length || length > 64) {
+ struct device_prefix_assignment *assignment = vlist_find(
+ prefix->assignments, &iface, assignment, node);
+ if (assignment)
+ interface_set_prefix_address(iface, false, assignment);
+ } else {
+ uint8_t length = iface->proto_ip.assignment_length;
+ uint64_t want = 1ULL << (64 - length);
+ if (prefix->avail < want && prefix->avail > 0) {
+ do {
+ want = 1ULL << (64 - ++length);
+ } while (want > prefix->avail);
+ }
+
+ if (prefix->avail < want)
+ return;
+
+ // Assignment
+ struct device_prefix_assignment *assignment = calloc(1, sizeof(*assignment));
+ assignment->prefix = prefix;
+ assignment->length = length;
+ assignment->name = strdup(iface->name);
+
+ vlist_add(prefix->assignments, &assignment->node, assignment->name);
+ }
+}
+
+static void
+interface_update_prefix(struct vlist_tree *tree,
+ struct vlist_node *node_new,
+ struct vlist_node *node_old)
+{
+ struct device_prefix *prefix_old, *prefix_new;
+ prefix_old = container_of(node_old, struct device_prefix, node);
+ prefix_new = container_of(node_new, struct device_prefix, node);
+
+ 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;
+
+ if (node_old && node_new) {
+ prefix_new->avail = prefix_old->avail;
+ prefix_new->assignments = prefix_old->assignments;
+ prefix_old->assignments = NULL;
+
+ // Update all assignments
+ struct device_prefix_assignment *assignment;
+ struct vlist_tree *assignments = prefix_new->assignments;
+ vlist_for_each_element(assignments, assignment, node)
+ assignments->update(assignments,
+ &assignment->node, &assignment->node);
+ } else if (node_new) {
+ prefix_new->avail = 1ULL << (64 - prefix_new->length);
+ prefix_new->assignments = calloc(1, sizeof(*prefix_new->assignments));
+ vlist_init(prefix_new->assignments, prefix_assignment_cmp,
+ interface_update_prefix_assignments);
+
+ // Create initial assignments for interfaces
+ struct interface *iface;
+ vlist_for_each_element(&interfaces, iface, node)
+ interface_ip_set_prefix_assignment(prefix_new, iface,
+ iface->proto_ip.assignment_length);
+
+ list_add(&prefix_new->head, &prefixes);
+
+ // Set null-route to avoid routing loops
+ system_add_route(NULL, &route);
+ }
+
+ if (node_old) {
+ // Remove null-route
+ system_del_route(NULL, &route);
+
+ list_del(&prefix_old->head);
+
+ if (prefix_old->assignments) {
+ vlist_flush_all(prefix_old->assignments);
+ free(prefix_old->assignments);
+ }
+ free(prefix_old);
+ }
+}
+
+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 *prefix = calloc(1, sizeof(*prefix));
+ prefix->length = length;
+ prefix->addr = *addr;
+ prefix->preferred_until = preferred_until;
+ prefix->valid_until = valid_until;
+ prefix->iface = iface;
+
+ if (iface)
+ vlist_add(&iface->proto_ip.prefix, &prefix->node, &prefix->addr);
+ else
+ interface_update_prefix(NULL, &prefix->node, NULL);
+}
+
+void
+interface_ip_set_ula_prefix(const char *prefix)
+{
+ char buf[INET6_ADDRSTRLEN + 4] = {0}, *saveptr;
+ strncpy(buf, prefix, sizeof(buf) - 1);
+ char *prefixaddr = strtok_r(buf, "/", &saveptr);
+
+ struct in6_addr addr;
+ if (!prefixaddr || inet_pton(AF_INET6, prefixaddr, &addr) < 1)
+ return;
+
+ int length;
+ char *prefixlen = strtok_r(NULL, ",", &saveptr);
+ 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;
+ }
+
+ interface_ip_add_device_prefix(NULL, &addr, length, 0, 0);
+}
+
void
interface_add_dns_server(struct interface_ip_settings *ip, const char *str)
{
}
vlist_update(&ip->route);
vlist_update(&ip->addr);
+ vlist_update(&ip->prefix);
}
void
vlist_simple_flush(&ip->dns_search);
vlist_flush(&ip->route);
vlist_flush(&ip->addr);
+ vlist_flush(&ip->prefix);
interface_write_resolv_conf();
}
vlist_simple_flush_all(&ip->dns_search);
vlist_flush_all(&ip->route);
vlist_flush_all(&ip->addr);
+ vlist_flush_all(&ip->prefix);
}
static void
vlist_simple_init(&ip->dns_servers, struct dns_server, node);
vlist_init(&ip->route, route_cmp, interface_update_proto_route);
vlist_init(&ip->addr, addr_cmp, interface_update_proto_addr);
+ vlist_init(&ip->prefix, prefix_cmp, interface_update_prefix);
}
void
struct in6_addr in6;
};
+struct device_prefix {
+ struct vlist_node node;
+ struct list_head head;
+ struct vlist_tree *assignments;
+ struct interface *iface;
+ uint64_t avail;
+ time_t valid_until;
+ time_t preferred_until;
+
+ struct in6_addr addr;
+ uint8_t length;
+};
+
+struct device_prefix_assignment {
+ struct vlist_node node;
+ struct device_prefix *prefix;
+ struct in6_addr addr;
+ bool enabled;
+ uint8_t length;
+ char *name;
+};
+
struct device_addr {
struct vlist_node node;
bool enabled;
uint32_t broadcast;
uint32_t point_to_point;
+ /* ipv6 only */
+ time_t valid_until;
+ time_t preferred_until;
+
/* must be last */
enum device_addr_flags flags;
unsigned int mask;
};
extern const struct config_param_list route_attr_list;
+extern struct list_head prefixes;
void interface_ip_init(struct interface *iface);
void interface_add_dns_server(struct interface_ip_settings *ip, const char *str);
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_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
bool enabled;
bool no_defaultroute;
bool no_dns;
+ uint8_t assignment_length;
struct vlist_tree addr;
struct vlist_tree route;
+ struct vlist_tree prefix;
struct vlist_simple_tree dns_servers;
struct vlist_simple_tree dns_search;
struct ubus_object ubus;
};
+
extern struct vlist_tree interfaces;
extern const struct config_param_list interface_attr_list;
#include <netinet/in.h>
#include "netifd.h"
+#include "system.h"
#include "interface.h"
#include "interface-ip.h"
#include "proto.h"
OPT_BROADCAST,
OPT_GATEWAY,
OPT_IP6GW,
+ OPT_IP6PREFIX,
+ OPT_IP6ASSIGN,
__OPT_MAX,
};
[OPT_BROADCAST] = { .name = "broadcast", .type = BLOBMSG_TYPE_STRING },
[OPT_GATEWAY] = { .name = "gateway", .type = BLOBMSG_TYPE_STRING },
[OPT_IP6GW] = { .name = "ip6gw", .type = BLOBMSG_TYPE_STRING },
+ [OPT_IP6PREFIX] = { .name = "ip6prefix", .type = BLOBMSG_TYPE_ARRAY },
+ [OPT_IP6ASSIGN] = { .name = "ip6assign", .type = BLOBMSG_TYPE_INT32 },
};
static const union config_param_info proto_ip_attr_info[__OPT_MAX] = {
[OPT_IPADDR] = { .type = BLOBMSG_TYPE_STRING },
[OPT_IP6ADDR] = { .type = BLOBMSG_TYPE_STRING },
+ [OPT_IP6PREFIX] = { .type = BLOBMSG_TYPE_STRING },
};
const struct config_param_list proto_ip_attr = {
return true;
}
+static bool
+parse_ip6assign_option(struct interface *iface, struct blob_attr *attr)
+{
+ uint8_t oldval = iface->proto_ip.assignment_length;
+ uint8_t newval = blobmsg_get_u32(attr);
+
+ struct device_prefix *prefix;
+ list_for_each_entry(prefix, &prefixes, head) {
+ if (oldval && oldval != newval)
+ interface_ip_set_prefix_assignment(prefix, iface, 0);
+
+ if (newval && newval <= 64)
+ interface_ip_set_prefix_assignment(prefix, iface, newval);
+ }
+
+ iface->proto_ip.assignment_length = newval;
+ return true;
+}
+
+static bool
+parse_prefix_option(struct interface *iface, const char *str, size_t len)
+{
+ char buf[128] = {0}, *saveptr;
+ if (len > sizeof(buf))
+ return false;
+
+ memcpy(buf, str, len);
+ char *addrstr = strtok_r(buf, "/", &saveptr);
+ if (!addrstr)
+ return false;
+
+ char *lengthstr = strtok_r(NULL, ",", &saveptr);
+ if (!lengthstr)
+ return false;
+
+ char *prefstr = strtok_r(NULL, ",", &saveptr);
+ char *validstr = (!prefstr) ? NULL : strtok_r(NULL, ",", &saveptr);
+
+ uint32_t pref = (!prefstr) ? 0 : strtoul(prefstr, NULL, 10);
+ uint32_t valid = (!validstr) ? 0 : strtoul(validstr, NULL, 10);
+
+ uint8_t length = strtoul(lengthstr, NULL, 10);
+ if (length < 1 || length > 64)
+ return false;
+
+ struct in6_addr addr;
+ if (inet_pton(AF_INET6, addrstr, &addr) < 1)
+ return false;
+
+ time_t now = system_get_rtime();
+ time_t preferred_until = 0;
+ if (prefstr && pref != 0xffffffffU)
+ preferred_until = pref + now;
+
+ time_t valid_until = 0;
+ if (validstr && valid != 0xffffffffU)
+ valid_until = valid + now;
+
+ interface_ip_add_device_prefix(iface, &addr, length,
+ valid_until, preferred_until);
+ return true;
+}
+
+static int
+parse_prefix_list(struct interface *iface, struct blob_attr *attr)
+{
+ struct blob_attr *cur;
+ int n_addr = 0;
+ int rem;
+
+ blobmsg_for_each_attr(cur, attr, rem) {
+ if (blobmsg_type(cur) != BLOBMSG_TYPE_STRING)
+ return -1;
+
+ n_addr++;
+ if (!parse_prefix_option(iface, blobmsg_data(cur),
+ blobmsg_data_len(cur)))
+ return -1;
+ }
+
+ return n_addr;
+}
+
int
proto_apply_static_ip_settings(struct interface *iface, struct blob_attr *attr)
{
n_v6 = parse_static_address_option(iface, cur, true,
netmask, false, 0);
+ if ((cur = tb[OPT_IP6PREFIX]))
+ if (parse_prefix_list(iface, cur) < 0)
+ goto out;
+
+/* TODO: Clarify
if (!n_v4 && !n_v6) {
error = "NO_ADDRESS";
goto error;
}
+*/
if (n_v4 < 0 || n_v6 < 0)
goto out;
goto out;
}
+ if ((cur = tb[OPT_IP6ASSIGN]))
+ if (!parse_ip6assign_option(iface, cur))
+ goto out;
+
return 0;
error:
{
struct blob_attr *tb[__OPT_MAX];
struct blob_attr *cur;
- const char *error;
+// const char *error;
int n_v4 = 0, n_v6 = 0;
blobmsg_parse(proto_ip_attributes, __OPT_MAX, tb, blob_data(attr), blob_len(attr));
if ((cur = tb[OPT_IP6ADDR]))
n_v6 = parse_address_list(iface, cur, true, ext);
+ if ((cur = tb[OPT_IP6PREFIX]))
+ if (parse_prefix_list(iface, cur) < 0)
+ goto out;
+
+/* TODO: clarify
if (!n_v4 && !n_v6) {
error = "NO_ADDRESS";
goto error;
}
+*/
if (n_v4 < 0 || n_v6 < 0)
goto out;
goto out;
}
+ if ((cur = tb[OPT_IP6ASSIGN]))
+ if (!parse_ip6assign_option(iface, cur))
+ goto out;
+
return 0;
+/* TODO: clarify
error:
interface_add_error(iface, "proto", error, NULL, 0);
+*/
out:
return -1;
}
else
gw[0] = 0;
- sprintf(devstr, " dev %s", dev->ifname);
+ if (dev)
+ sprintf(devstr, " dev %s", dev->ifname);
if (route->metric > 0)
sprintf(devstr, " metric %d", route->metric);
{
return 0;
}
+
+int system_update_ipv6_mtu(struct device *dev, int mtu)
+{
+ return 0;
+}
{
bool v4 = ((addr->flags & DEVADDR_FAMILY) == DEVADDR_INET4);
int alen = v4 ? 4 : 16;
+ unsigned int flags = 0;
struct ifaddrmsg ifa = {
.ifa_family = (alen == 4) ? AF_INET : AF_INET6,
.ifa_prefixlen = addr->mask,
};
struct nl_msg *msg;
+ if (cmd == RTM_NEWADDR)
+ flags |= NLM_F_CREATE | NLM_F_REPLACE;
- msg = nlmsg_alloc_simple(cmd, 0);
+ msg = nlmsg_alloc_simple(cmd, flags);
if (!msg)
return -1;
nla_put_u32(msg, IFA_BROADCAST, addr->broadcast);
if (addr->point_to_point)
nla_put_u32(msg, IFA_ADDRESS, addr->point_to_point);
+ } else {
+ time_t now = system_get_rtime();
+ struct ifa_cacheinfo cinfo = {0xffffffffU, 0xffffffffU, 0, 0};
+
+ if (addr->preferred_until) {
+ int preferred = addr->preferred_until - now;
+ if (preferred < 0)
+ preferred = 0;
+
+ cinfo.ifa_prefered = preferred;
+ }
+
+ if (addr->valid_until) {
+ int valid = addr->valid_until - now;
+ if (valid <= 0)
+ return -1;
+
+ cinfo.ifa_valid = valid;
+ }
+
+ nla_put(msg, IFA_CACHEINFO, sizeof(cinfo), &cinfo);
}
return system_rtnl_call(msg);
int alen = ((route->flags & DEVADDR_FAMILY) == DEVADDR_INET4) ? 4 : 16;
bool have_gw;
unsigned int flags = 0;
- int ifindex = dev->ifindex;
if (alen == 4)
have_gw = !!route->nexthop.in.s_addr;
};
struct nl_msg *msg;
- if (cmd == RTM_NEWROUTE)
+ if (cmd == RTM_NEWROUTE) {
flags |= NLM_F_CREATE | NLM_F_REPLACE;
+ if (!dev) { // Add null-route
+ rtm.rtm_scope = RT_SCOPE_UNIVERSE;
+ rtm.rtm_type = RTN_UNREACHABLE;
+ }
+ }
+
msg = nlmsg_alloc_simple(cmd, flags);
if (!msg)
return -1;
if (have_gw)
nla_put(msg, RTA_GATEWAY, alen, &route->nexthop);
- nla_put_u32(msg, RTA_OIF, ifindex);
+ if (dev)
+ nla_put_u32(msg, RTA_OIF, dev->ifindex);
return system_rtnl_call(msg);
}
return tunnel_ioctl(name, SIOCDELTUNNEL, &p);
}
+int system_update_ipv6_mtu(struct device *dev, int mtu)
+{
+ int ret = -1;
+ char buf[64];
+ snprintf(buf, sizeof(buf), "/proc/sys/net/ipv6/conf/%s/mtu",
+ dev->ifname);
+
+ int fd = open(buf, O_RDWR);
+ ssize_t len = read(fd, buf, sizeof(buf) - 1);
+ if (len < 0)
+ goto out;
+
+ buf[len] = 0;
+ ret = atoi(buf);
+
+ if (!mtu || ret <= mtu)
+ goto out;
+
+ lseek(fd, 0, SEEK_SET);
+ if (write(fd, buf, snprintf(buf, sizeof(buf), "%i", mtu)) <= 0)
+ ret = -1;
+
+out:
+ close(fd);
+ return ret;
+}
+
static int parse_ipaddr(struct blob_attr *attr, __be32 *addr)
{
if (!attr)
void system_fd_set_cloexec(int fd);
+int system_update_ipv6_mtu(struct device *device, int mtu);
+
#endif
}
}
+
+static void
+interface_ip_dump_prefix_list(struct interface_ip_settings *ip)
+{
+ struct device_prefix *prefix;
+ char *buf;
+ void *a, *c;
+ const int buflen = INET6_ADDRSTRLEN;
+
+ vlist_for_each_element(&ip->prefix, prefix, node) {
+ a = blobmsg_open_table(&b, NULL);
+
+ buf = blobmsg_alloc_string_buffer(&b, "address", buflen);
+ inet_ntop(AF_INET6, &prefix->addr, buf, buflen);
+ blobmsg_add_string_buffer(&b);
+
+ 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)
+ preferred = 0;
+ 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);
+ }
+
+ c = blobmsg_open_table(&b, "assigned");
+ struct device_prefix_assignment *assign;
+ vlist_for_each_element(prefix->assignments, assign, node) {
+ void *d = blobmsg_open_table(&b, assign->name);
+
+ 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);
+
+ blobmsg_close_table(&b, d);
+ }
+ blobmsg_close_table(&b, c);
+
+ blobmsg_close_table(&b, a);
+ }
+}
+
+
static void
interface_ip_dump_dns_server_list(struct interface_ip_settings *ip,
bool enabled)
interface_ip_dump_address_list(&iface->config_ip, true, true);
interface_ip_dump_address_list(&iface->proto_ip, true, true);
blobmsg_close_array(&b, a);
+ a = blobmsg_open_array(&b, "ipv6-prefix");
+ 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, "route");
interface_ip_dump_route_list(&iface->config_ip, true);
interface_ip_dump_route_list(&iface->proto_ip, true);