Browse Source

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 <meurisalexander@gmail.com>
Signed-off-by: Hans Dedecker <dedeckeh@gmail.com>
meurisa 5 years ago
parent
commit
08989e46b9
10 changed files with 388 additions and 5 deletions
  1. 17 2
      config.c
  2. 148 1
      interface-ip.c
  3. 19 1
      interface-ip.h
  4. 2 0
      interface.h
  5. 27 0
      proto-shell.c
  6. 58 0
      scripts/netifd-proto.sh
  7. 20 0
      system-dummy.c
  8. 49 1
      system-linux.c
  9. 3 0
      system.h
  10. 45 0
      ubus.c

+ 17 - 2
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();

+ 148 - 1
interface-ip.c

@@ -20,6 +20,10 @@
 #include <arpa/inet.h>
 #include <netinet/in.h>
 
+#ifdef linux
+#include <netinet/ether.h>
+#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);
 }

+ 19 - 1
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);

+ 2 - 0
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;

+ 27 - 0
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);
 

+ 58 - 0
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"
 }
 

+ 20 - 0
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");

+ 49 - 1
system-linux.c

@@ -31,6 +31,7 @@
 #include <netinet/in.h>
 
 #include <linux/rtnetlink.h>
+#include <linux/neighbour.h>
 #include <linux/sockios.h>
 #include <linux/ip.h>
 #include <linux/if_addr.h>
@@ -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;

+ 3 - 0
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);

+ 45 - 0
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);
 	}