From 08989e46b9030671ce57b8872538d40e2ddcbbe0 Mon Sep 17 00:00:00 2001 From: meurisa Date: Fri, 12 Apr 2019 09:56:28 +0200 Subject: [PATCH] interface: add neighbor config support The neighbor or neighbor6 network section makes neighbours configurable via UCI or proto shell handlers. It allows to install neighbor proxy entries or static neighbor entries The neighbor or neighbor6 section has the following types: interface : declares the logical OpenWrt interface ipaddr : the ip address of the neighbor mac : the mac address of the neighbor proxy : specifies whether the neighbor ia a proxy entry (can be 1 or 0) router : specifies whether the neighbor is a router (can be 1 or 0) Signed-off-by: Alexander Meuris Signed-off-by: Hans Dedecker --- config.c | 19 ++++- interface-ip.c | 149 +++++++++++++++++++++++++++++++++++++++- interface-ip.h | 20 +++++- interface.h | 2 + proto-shell.c | 27 ++++++++ scripts/netifd-proto.sh | 58 ++++++++++++++++ system-dummy.c | 20 ++++++ system-linux.c | 50 +++++++++++++- system.h | 3 + ubus.c | 45 ++++++++++++ 10 files changed, 388 insertions(+), 5 deletions(-) diff --git a/config.c b/config.c index be10379..843c53f 100644 --- a/config.c +++ b/config.c @@ -143,6 +143,17 @@ config_parse_route(struct uci_section *s, bool v6) interface_ip_add_route(NULL, blob_data(b.head), v6); } +static void +config_parse_neighbor(struct uci_section *s, bool v6) +{ + void *neighbor; + blob_buf_init(&b,0); + neighbor = blobmsg_open_array(&b, "neighbor"); + uci_to_blob(&b,s, &neighbor_attr_list); + blobmsg_close_array(&b, neighbor); + interface_ip_add_neighbor(NULL, blob_data(b.head), v6); +} + static void config_parse_rule(struct uci_section *s, bool v6) { @@ -251,7 +262,7 @@ config_init_interfaces(void) } static void -config_init_routes(void) +config_init_ip(void) { struct interface *iface; struct uci_element *e; @@ -266,6 +277,10 @@ config_init_routes(void) config_parse_route(s, false); else if (!strcmp(s->type, "route6")) config_parse_route(s, true); + if (!strcmp(s->type, "neighbor")) + config_parse_neighbor(s, false); + else if (!strcmp(s->type, "neighbor6")) + config_parse_neighbor(s, true); } vlist_for_each_element(&interfaces, iface, node) @@ -417,7 +432,7 @@ config_init_all(void) device_reset_config(); config_init_devices(); config_init_interfaces(); - config_init_routes(); + config_init_ip(); config_init_rules(); config_init_globals(); config_init_wireless(); diff --git a/interface-ip.c b/interface-ip.c index 83f1dfa..22c21d7 100644 --- a/interface-ip.c +++ b/interface-ip.c @@ -20,6 +20,10 @@ #include #include +#ifdef linux +#include +#endif + #include "netifd.h" #include "device.h" #include "interface.h" @@ -64,6 +68,28 @@ const struct uci_blob_param_list route_attr_list = { .params = route_attr, }; +enum { + NEIGHBOR_INTERFACE, + NEIGHBOR_ADDRESS, + NEIGHBOR_MAC, + NEIGHBOR_PROXY, + NEIGHBOR_ROUTER, + __NEIGHBOR_MAX +}; + +static const struct blobmsg_policy neighbor_attr[__NEIGHBOR_MAX]={ + [NEIGHBOR_INTERFACE]= { .name = "interface", .type = BLOBMSG_TYPE_STRING}, + [NEIGHBOR_ADDRESS]= { .name = "ipaddr", .type = BLOBMSG_TYPE_STRING}, + [NEIGHBOR_MAC]= { .name = "mac", .type = BLOBMSG_TYPE_STRING}, + [NEIGHBOR_PROXY]= { .name = "proxy", .type = BLOBMSG_TYPE_BOOL}, + [NEIGHBOR_ROUTER]= {.name = "router", .type = BLOBMSG_TYPE_BOOL}, +}; + +const struct uci_blob_param_list neighbor_attr_list = { + .n_params = __NEIGHBOR_MAX, + .params = neighbor_attr, +}; + struct list_head prefixes = LIST_HEAD_INIT(prefixes); static struct device_prefix *ula_prefix = NULL; @@ -298,6 +324,64 @@ interface_set_route_info(struct interface *iface, struct device_route *route) } } +void +interface_ip_add_neighbor(struct interface *iface, struct blob_attr *attr, bool v6) +{ + struct interface_ip_settings *ip; + struct blob_attr *tb[__NEIGHBOR_MAX], *cur; + struct device_neighbor *neighbor; + int af = v6 ? AF_INET6: AF_INET; + struct ether_addr *ea; + + blobmsg_parse(neighbor_attr, __NEIGHBOR_MAX, tb, blobmsg_data(attr), blobmsg_data_len(attr)); + + if (!iface) { + if ((cur = tb[NEIGHBOR_INTERFACE]) == NULL) + return; + + iface = vlist_find(&interfaces, blobmsg_data(cur), iface, node); + + if (!iface) + return; + + ip = &iface->config_ip; + } else + ip = &iface->proto_ip; + + neighbor = calloc(1,sizeof(*neighbor)); + neighbor->flags = v6 ? DEVADDR_INET6 : DEVADDR_INET4; + + if (!neighbor) + return; + + if ((cur = tb[NEIGHBOR_ADDRESS]) != NULL){ + if (!inet_pton(af, blobmsg_data(cur), &neighbor->addr)) + goto error; + } else + goto error; + + if ((cur = tb[NEIGHBOR_MAC]) != NULL) { + neighbor->flags |= DEVNEIGH_MAC; + ea = ether_aton(blobmsg_data(cur)); + if (!ea) + goto error; + + memcpy(neighbor->macaddr, ea, 6); + } + + if ((cur = tb[NEIGHBOR_PROXY]) != NULL) + neighbor->proxy = blobmsg_get_bool(cur); + + if ((cur = tb[NEIGHBOR_ROUTER]) != NULL) + neighbor->router = blobmsg_get_bool(cur); + + vlist_add(&ip->neighbor, &neighbor->node, neighbor); + return; + +error: + free(neighbor); +} + void interface_ip_add_route(struct interface *iface, struct blob_attr *attr, bool v6) { @@ -428,6 +512,14 @@ addr_cmp(const void *k1, const void *k2, void *ptr) offsetof(struct device_addr, flags)); } +static int +neighbor_cmp(const void *k1, const void *k2, void *ptr) +{ + const struct device_neighbor *n1 = k1, *n2 = k2; + + return memcmp(&n1->addr, &n2->addr, sizeof(n2->addr)); +} + static int route_cmp(const void *k1, const void *k2, void *ptr) { @@ -624,6 +716,44 @@ enable_route(struct interface_ip_settings *ip, struct device_route *route) return ip->enabled; } +static void +interface_update_proto_neighbor(struct vlist_tree *tree, + struct vlist_node * node_new, + struct vlist_node *node_old) +{ + struct device *dev; + struct device_neighbor *neighbor_old, *neighbor_new; + struct interface_ip_settings *ip; + bool keep = false; + + ip = container_of(tree, struct interface_ip_settings, neighbor); + dev = ip->iface->l3_dev.dev; + + neighbor_old = container_of(node_old, struct device_neighbor, node); + neighbor_new = container_of(node_new, struct device_neighbor, node); + + if (node_old && node_new) { + keep = (!memcmp(neighbor_old->macaddr, neighbor_new->macaddr, sizeof(neighbor_old->macaddr)) && + (neighbor_old->proxy == neighbor_new->proxy) && + (neighbor_old->router == neighbor_new->router)); + } + + if (node_old) { + if (!keep && neighbor_old->enabled) + system_del_neighbor(dev, neighbor_old); + + free(neighbor_old); + } + + if (node_new) { + if (!keep && ip->enabled) + if (system_add_neighbor(dev, neighbor_new)) + neighbor_new->failed = true; + + neighbor_new->enabled = ip->enabled; + } +} + static void interface_update_proto_route(struct vlist_tree *tree, struct vlist_node *node_new, @@ -1394,6 +1524,7 @@ void interface_ip_set_enabled(struct interface_ip_settings *ip, bool enabled) { struct device_addr *addr; struct device_route *route; + struct device_neighbor *neighbor; struct device *dev; struct interface *iface; @@ -1439,7 +1570,6 @@ void interface_ip_set_enabled(struct interface_ip_settings *ip, bool enabled) if (!enable_route(ip, route)) _enabled = false; - if (route->enabled == _enabled) continue; @@ -1453,6 +1583,19 @@ void interface_ip_set_enabled(struct interface_ip_settings *ip, bool enabled) route->enabled = _enabled; } + vlist_for_each_element(&ip->neighbor, neighbor, node) { + if (neighbor->enabled == enabled) + continue; + + if (enabled) { + if(system_add_neighbor(dev, neighbor)) + neighbor->failed = true; + } else + system_del_neighbor(dev, neighbor); + + neighbor->enabled = enabled; + } + struct device_prefix *c; struct device_prefix_assignment *a; list_for_each_entry(c, &prefixes, head) @@ -1481,6 +1624,7 @@ interface_ip_update_start(struct interface_ip_settings *ip) vlist_update(&ip->route); vlist_update(&ip->addr); vlist_update(&ip->prefix); + vlist_update(&ip->neighbor); } void @@ -1491,6 +1635,7 @@ interface_ip_update_complete(struct interface_ip_settings *ip) vlist_flush(&ip->route); vlist_flush(&ip->addr); vlist_flush(&ip->prefix); + vlist_flush(&ip->neighbor); interface_write_resolv_conf(); } @@ -1503,6 +1648,7 @@ interface_ip_flush(struct interface_ip_settings *ip) vlist_simple_flush_all(&ip->dns_search); vlist_flush_all(&ip->route); vlist_flush_all(&ip->addr); + vlist_flush_all(&ip->neighbor); vlist_flush_all(&ip->prefix); } @@ -1514,6 +1660,7 @@ __interface_ip_init(struct interface_ip_settings *ip, struct interface *iface) vlist_simple_init(&ip->dns_search, struct dns_search_domain, node); vlist_simple_init(&ip->dns_servers, struct dns_server, node); vlist_init(&ip->route, route_cmp, interface_update_proto_route); + vlist_init(&ip->neighbor, neighbor_cmp, interface_update_proto_neighbor); vlist_init(&ip->addr, addr_cmp, interface_update_proto_addr); vlist_init(&ip->prefix, prefix_cmp, interface_update_prefix); } diff --git a/interface-ip.h b/interface-ip.h index 21c6e9b..3f99eb9 100644 --- a/interface-ip.h +++ b/interface-ip.h @@ -48,6 +48,9 @@ enum device_addr_flags { /* route overrides the default route type */ DEVROUTE_TYPE = (1 << 10), + + /* neighbor mac address */ + DEVNEIGH_MAC = (1 << 11), }; union if_addr { @@ -106,6 +109,20 @@ struct device_route { union if_addr source; }; +struct device_neighbor { + struct vlist_node node; + + bool failed; + bool proxy; + bool keep; + bool enabled; + bool router; + + uint8_t macaddr[6]; + enum device_addr_flags flags; + union if_addr addr; +}; + struct device_addr { struct vlist_node node; bool enabled; @@ -150,6 +167,7 @@ struct dns_search_domain { }; extern const struct uci_blob_param_list route_attr_list; +extern const struct uci_blob_param_list neighbor_attr_list; extern struct list_head prefixes; void interface_ip_init(struct interface *iface); @@ -158,7 +176,7 @@ void interface_add_dns_search_list(struct interface_ip_settings *ip, struct blob void interface_write_resolv_conf(void); void interface_ip_add_route(struct interface *iface, struct blob_attr *attr, bool v6); - +void interface_ip_add_neighbor(struct interface *iface, struct blob_attr *attr, bool v6); void interface_ip_update_start(struct interface_ip_settings *ip); void interface_ip_update_complete(struct interface_ip_settings *ip); void interface_ip_flush(struct interface_ip_settings *ip); diff --git a/interface.h b/interface.h index 098e0fc..22738a9 100644 --- a/interface.h +++ b/interface.h @@ -82,6 +82,7 @@ struct interface_ip_settings { struct vlist_tree addr; struct vlist_tree route; struct vlist_tree prefix; + struct vlist_tree neighbor; struct vlist_simple_tree dns_servers; struct vlist_simple_tree dns_search; @@ -146,6 +147,7 @@ struct interface { struct interface_ip_settings proto_ip; struct interface_ip_settings config_ip; struct vlist_tree host_routes; + struct vlist_tree host_neighbors; int metric; int dns_metric; diff --git a/proto-shell.c b/proto-shell.c index 47a9568..07ec21a 100644 --- a/proto-shell.c +++ b/proto-shell.c @@ -413,6 +413,23 @@ proto_shell_parse_route_list(struct interface *iface, struct blob_attr *attr, } } +static void +proto_shell_parse_neighbor_list(struct interface *iface, struct blob_attr *attr, + bool v6) +{ + struct blob_attr *cur; + int rem; + + blobmsg_for_each_attr(cur, attr, rem) { + if (blobmsg_type(cur) != BLOBMSG_TYPE_TABLE) { + DPRINTF("Ignore wrong neighbor type: %d\n", blobmsg_type(cur)); + continue; + } + + interface_ip_add_neighbor(iface, cur, v6); + } +} + static void proto_shell_parse_data(struct interface *iface, struct blob_attr *attr) { @@ -456,6 +473,8 @@ enum { NOTIFY_HOST, NOTIFY_DNS, NOTIFY_DNS_SEARCH, + NOTIFY_NEIGHBORS, + NOTIFY_NEIGHBORS6, __NOTIFY_LAST }; @@ -477,6 +496,8 @@ static const struct blobmsg_policy notify_attr[__NOTIFY_LAST] = { [NOTIFY_HOST] = { .name = "host", .type = BLOBMSG_TYPE_STRING }, [NOTIFY_DNS] = { .name = "dns", .type = BLOBMSG_TYPE_ARRAY }, [NOTIFY_DNS_SEARCH] = { .name = "dns_search", .type = BLOBMSG_TYPE_ARRAY }, + [NOTIFY_NEIGHBORS]= {.name = "neighbor", .type = BLOBMSG_TYPE_ARRAY}, + [NOTIFY_NEIGHBORS6]= {.name = "neighbor6", .type = BLOBMSG_TYPE_ARRAY}, }; static int @@ -546,6 +567,12 @@ proto_shell_update_link(struct proto_shell_state *state, struct blob_attr *data, if ((cur = tb[NOTIFY_ROUTES6]) != NULL) proto_shell_parse_route_list(state->proto.iface, cur, true); + if ((cur = tb[NOTIFY_NEIGHBORS]) != NULL) + proto_shell_parse_neighbor_list(state->proto.iface, cur, false); + + if ((cur = tb[NOTIFY_NEIGHBORS6]) != NULL) + proto_shell_parse_neighbor_list(state->proto.iface, cur, true); + if ((cur = tb[NOTIFY_DNS])) interface_add_dns_server_list(&iface->proto_ip, cur); diff --git a/scripts/netifd-proto.sh b/scripts/netifd-proto.sh index 31df91f..87d337d 100644 --- a/scripts/netifd-proto.sh +++ b/scripts/netifd-proto.sh @@ -64,6 +64,8 @@ proto_init_update() { PROTO_PREFIX6= PROTO_DNS= PROTO_DNS_SEARCH= + PROTO_NEIGHBOR= + PROTO_NEIGHBOR6= json_init json_add_int action 0 [ -n "$ifname" -a "*" != "$ifname" ] && json_add_string "ifname" "$ifname" @@ -133,6 +135,23 @@ proto_add_ipv6_address() { append PROTO_IP6ADDR "$address/$mask/$preferred/$valid/$offlink/$class" } +proto_add_ipv4_neighbor(){ + local address="$1" + local mac="$2" + local proxy="$3" + + append PROTO_NEIGHBOR "$address/$mac/$proxy" +} + +proto_add_ipv6_neighbor(){ + local address="$1" + local mac="$2" + local proxy="$3" + local router="$4" + + append PROTO_NEIGHBOR6 "$address/$mac/$proxy/$router" +} + proto_add_ipv4_route() { local target="$1" local mask="$2" @@ -218,6 +237,43 @@ _proto_push_string() { json_add_string "" "$1" } +_proto_push_ipv4_neighbor(){ + local str="$1" + local address mac proxy + + address="${str%%/*}" + str="${str#*/}" + mac="${str%%/*}" + str="${str#*/}" + proxy="${str%%/*}" + + json_add_object "" + json_add_string ipaddr "$address" + [ -n "$mac" ] && json_add_string mac "$mac" + [ -n "$proxy" ] && json_add_boolean proxy "$proxy" + json_close_object +} + +_proto_push_ipv6_neighbor(){ + local str="$1" + local address mac proxy router + + address="${str%%/*}" + str="${str#*/}" + mac="${str%%/*}" + str="${str#*/}" + proxy="${str%%/*}" + str="${str#*/}" + router="${str%%/*}" + + json_add_object "" + json_add_string ipaddr "$address" + [ -n "$mac" ] && json_add_string mac "$mac" + [ -n "$proxy" ] && json_add_boolean proxy "$proxy" + [ -n "$router" ] && json_add_boolean router "$router" + json_close_object +} + _proto_push_route() { local str="$1"; local target="${str%%/*}" @@ -277,6 +333,8 @@ proto_send_update() { _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_push_array "neighbor" "$PROTO_NEIGHBOR" _proto_push_ipv4_neighbor + _proto_push_array "neighbor6" "$PROTO_NEIGHBOR6" _proto_push_ipv6_neighbor _proto_notify "$interface" } diff --git a/system-dummy.c b/system-dummy.c index 11c8ccc..58fd2d0 100644 --- a/system-dummy.c +++ b/system-dummy.c @@ -181,6 +181,26 @@ static int system_route_msg(struct device *dev, struct device_route *route, cons return 0; } +static int system_neighbor_msg(struct device *dev, struct device_neighbor *neighbor, const char *type) +{ + char addr[64]; + int af = system_get_addr_family(neighbor->flags); + inet_ntop(af, &neighbor->addr.in , addr, sizeof(addr)); + + D(SYSTEM, "neigh %s %s%s%s %s\n", type, addr, neighbor->proxy ? "proxy " : "", + (neighbor->flags & DEVNEIGH_MAC) ? format_macaddr(neighbor->macaddr) : "", + neighbor->router ? "router": ""); +} +int system_add_neighbor(struct device *dev, struct device_neighbor *neighbor) +{ + return system_neighbor_msg(dev, neighbor, "add"); +} + +int system_del_neighbor(struct device *dev, struct device_neighbor *neighbor) +{ + return system_neighbor_msg(dev, neighbor, "del"); +} + int system_add_route(struct device *dev, struct device_route *route) { return system_route_msg(dev, route, "add"); diff --git a/system-linux.c b/system-linux.c index 82e9928..6a0105e 100644 --- a/system-linux.c +++ b/system-linux.c @@ -31,6 +31,7 @@ #include #include +#include #include #include #include @@ -1023,8 +1024,8 @@ void system_if_clear_state(struct device *dev) { static char buf[256]; char *bridge; - device_set_ifindex(dev, system_if_resolve(dev)); + if (dev->external || !dev->ifindex) return; @@ -1046,6 +1047,8 @@ void system_if_clear_state(struct device *dev) system_if_clear_entries(dev, RTM_GETADDR, AF_INET); system_if_clear_entries(dev, RTM_GETROUTE, AF_INET6); system_if_clear_entries(dev, RTM_GETADDR, AF_INET6); + system_if_clear_entries(dev, RTM_GETNEIGH, AF_INET); + system_if_clear_entries(dev, RTM_GETNEIGH, AF_INET6); system_set_disable_ipv6(dev, "0"); } @@ -1930,6 +1933,51 @@ int system_del_address(struct device *dev, struct device_addr *addr) return system_addr(dev, addr, RTM_DELADDR); } +static int system_neigh(struct device *dev, struct device_neighbor *neighbor, int cmd) +{ + int alen = ((neighbor->flags & DEVADDR_FAMILY) == DEVADDR_INET4) ? 4 : 16; + unsigned int flags = 0; + struct ndmsg ndm = { + .ndm_family = (alen == 4) ? AF_INET : AF_INET6, + .ndm_ifindex = dev->ifindex, + .ndm_state = NUD_PERMANENT, + .ndm_flags = (neighbor->proxy ? NTF_PROXY : 0) | (neighbor->router ? NTF_ROUTER : 0), + }; + struct nl_msg *msg; + + if (!dev) + return 1; + + if (cmd == RTM_NEWNEIGH) + flags |= NLM_F_CREATE | NLM_F_REPLACE; + + msg = nlmsg_alloc_simple(cmd, flags); + + if (!msg) + return -1; + + nlmsg_append(msg, &ndm, sizeof(ndm), 0); + + nla_put(msg, NDA_DST, alen, &neighbor->addr); + if (neighbor->flags & DEVNEIGH_MAC) + nla_put(msg, NDA_LLADDR, sizeof(neighbor->macaddr), &neighbor->macaddr); + + + return system_rtnl_call(msg); +} + +int system_add_neighbor(struct device *dev, struct device_neighbor *neighbor) +{ + return system_neigh(dev, neighbor, RTM_NEWNEIGH); +} + +int system_del_neighbor(struct device *dev, struct device_neighbor *neighbor) +{ + int rval = system_neigh(dev, neighbor, RTM_DELNEIGH); + netifd_log_message(L_NOTICE,"return delete %d", rval); + return rval; +} + static int system_rt(struct device *dev, struct device_route *route, int cmd) { int alen = ((route->flags & DEVADDR_FAMILY) == DEVADDR_INET4) ? 4 : 16; diff --git a/system.h b/system.h index 4d4cf6e..9fefcae 100644 --- a/system.h +++ b/system.h @@ -213,6 +213,9 @@ int system_add_route(struct device *dev, struct device_route *route); int system_del_route(struct device *dev, struct device_route *route); int system_flush_routes(void); +int system_add_neighbor(struct device *dev, struct device_neighbor * neighbor); +int system_del_neighbor(struct device *dev, struct device_neighbor * neighbor); + bool system_resolve_rt_type(const char *type, unsigned int *id); bool system_resolve_rt_proto(const char *type, unsigned int *id); bool system_resolve_rt_table(const char *name, unsigned int *id); diff --git a/ubus.c b/ubus.c index 14688c2..e7cbfb9 100644 --- a/ubus.c +++ b/ubus.c @@ -458,6 +458,43 @@ interface_ip_dump_address_list(struct interface_ip_settings *ip, bool v6, bool e } } +static void +interface_ip_dump_neighbor_list(struct interface_ip_settings *ip, bool enabled) +{ + struct device_neighbor *neighbor; + int buflen = 128; + char *buf; + void *r; + int af; + + vlist_for_each_element(&ip->neighbor, neighbor, node) { + if (neighbor->enabled != enabled) + continue; + + if ((neighbor->flags & DEVADDR_FAMILY) == DEVADDR_INET4) + af = AF_INET; + else + af = AF_INET6; + + r = blobmsg_open_table(&b, NULL); + + if (neighbor->flags & DEVNEIGH_MAC) + blobmsg_add_string(&b, "mac", format_macaddr(neighbor->macaddr)); + + buf = blobmsg_alloc_string_buffer(&b , "address", buflen); + inet_ntop(af, &neighbor->addr, buf, buflen); + blobmsg_add_string_buffer(&b); + + if (neighbor->proxy) + blobmsg_add_u32(&b, "proxy", neighbor->proxy); + + if (neighbor->router) + blobmsg_add_u32(&b, "router", neighbor->router); + + blobmsg_close_table(&b, r); + } +} + static void interface_ip_dump_route_list(struct interface_ip_settings *ip, bool enabled) { @@ -737,6 +774,10 @@ netifd_dump_status(struct interface *iface) interface_ip_dump_dns_search_list(&iface->config_ip, true); interface_ip_dump_dns_search_list(&iface->proto_ip, true); blobmsg_close_array(&b, a); + a = blobmsg_open_array(&b, "neighbors"); + interface_ip_dump_neighbor_list(&iface->config_ip, true); + interface_ip_dump_neighbor_list(&iface->proto_ip, true); + blobmsg_close_array(&b, a); inactive = blobmsg_open_table(&b, "inactive"); a = blobmsg_open_array(&b, "ipv4-address"); @@ -759,6 +800,10 @@ netifd_dump_status(struct interface *iface) interface_ip_dump_dns_search_list(&iface->config_ip, false); interface_ip_dump_dns_search_list(&iface->proto_ip, false); blobmsg_close_array(&b, a); + a = blobmsg_open_array(&b, "neighbors"); + interface_ip_dump_neighbor_list(&iface->config_ip, false); + interface_ip_dump_neighbor_list(&iface->proto_ip, false); + blobmsg_close_array(&b, a); blobmsg_close_table(&b, inactive); } -- 2.25.1