/* SPDX-License-Identifier: BSD-3-Clause */
/*
 * i1905_netlink.c - netlink interface to kernel.
 *
 * Copyright (C) 2021-2024 IOPSYS Software Solutions AB. All rights reserved.
 * Copyright (C) 2025 Genexis AB.
 *
 * Author: anjan.chanda@iopsys.eu
 */

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <errno.h>
#include <net/if.h>
#include <sys/ioctl.h>

#include <linux/netlink.h>
#include <linux/rtnetlink.h>
#include <linux/if.h>
#include <linux/if_arp.h>

#include <netlink/netlink.h>
#include <netlink/utils.h>

#include <netlink/route/rtnl.h>
#include <netlink/route/neighbour.h>
#include <netlink/route/addr.h>
#include <netlink/route/link.h>
#include <netlink/genl/ctrl.h>
#include <netlink/genl/genl.h>
#include <netlink/attr.h>

#include <json-c/json.h>
#include <libubox/blobmsg.h>
#include <libubox/blobmsg_json.h>
#include <libubox/uloop.h>
#include <libubox/ustream.h>
#include <libubox/utils.h>
#include <libubus.h>


#include "debug.h"
#include "util.h"
#include "timer.h"
#include "config.h"
#include "cmdu.h"
#include "cmdu_ackq.h"
#include "cmdufrag.h"
#include "1905_tlvs.h"
#include "i1905_dm.h"
#include "i1905.h"
#include "i1905_wifi.h"

struct i1905_nlevent {
	struct uloop_fd uloop;
	void (*error_cb)(struct i1905_nlevent *e, int error);
	void (*event_cb)(struct i1905_nlevent *e);
};

struct event_socket {
	struct i1905_nlevent ev;
	struct nl_sock *sock;
	int sock_bufsize;
};

static int i1905_nlevents_cb(struct nl_msg *msg, void *arg);

static void handle_error(struct i1905_nlevent *e, int error)
{
	struct event_socket *ev_sock = container_of(e, struct event_socket, ev);

	if (error != ENOBUFS)
		goto err;

	ev_sock->sock_bufsize *= 2;
	if (nl_socket_set_buffer_size(ev_sock->sock, ev_sock->sock_bufsize, 0))
		goto err;

	return;

err:
	e->uloop.cb = NULL;
	uloop_fd_delete(&e->uloop);
}

static void recv_nlevents(struct i1905_nlevent *e)
{
	struct event_socket *ev_sock = container_of(e, struct event_socket, ev);

	nl_recvmsgs_default(ev_sock->sock);
}

static struct event_socket rtnl_event = {
	.ev = {
		.uloop = {.fd = - 1, },
		.error_cb = handle_error,
		.event_cb = recv_nlevents,
	},
	.sock = NULL,
	.sock_bufsize = 0x20000,
};

struct br_fdb_entry {
	uint8_t macaddr[6];
	uint16_t port;
	uint32_t ageing_timer_value;
};

/* defined in linux/if_bridge.h */
struct __fdb_entry {
        __u8 mac_addr[6];
        __u8 port_no;
        __u8 is_local;
        __u32 ageing_timer_value;
        __u8 port_hi;
        __u8 pad0;
        __u16 unused;
};

int i1905_update_neigh_brport(struct i1905_private *priv, char *brname)
{
	struct br_fdb_entry fdbs[512];
	struct __fdb_entry fdb[256];
	struct timeval now = {0};
	bool skiplocal = true;
	char path[512] = {0};
	long offset = 0;
	int i, n;
	int num = 0;
	FILE *f;


	getcurrtime(&now);

	snprintf(path, 512, "/sys/class/net/%s/brforward", brname);
	f = fopen(path, "r"); /* cppcheck-suppress cert-MSC24-C */
	if (!f)
		return -1;

	do {
		memset(fdb, 0, sizeof(fdb));
		fseek(f, offset * sizeof(struct __fdb_entry), SEEK_SET);
		n = fread(fdb, sizeof(struct __fdb_entry), 256, f);
		if (n <= 0)
			break;

		//TODO: extend when more than 256 entries
		if (num > 255)
			break;

		for (i = 0; i < n; i++) {
			if (skiplocal && fdb[i].is_local == 1)
				continue;

			memcpy(fdbs[num].macaddr, fdb[i].mac_addr, 6);
			fdbs[num].port = fdb[i].port_no;
			fdbs[num].ageing_timer_value = fdb[i].ageing_timer_value * 10;	/* in ms */
			num++;
		}
		offset += n;
	} while (n > 0);

	fclose(f);

	for (i = 0; i < num; i++) {
		struct neigh_entry *t;
		char *ifname = NULL;

		i1905_dbg(LOG_NEIGH, "FDB[%d] : " MACFMT "  port = %hu  time-elapsed = %us\n", i,
			  MAC2STR(fdbs[i].macaddr), fdbs[i].port, fdbs[i].ageing_timer_value / 1000);

		t = neigh_lookup(&priv->neigh_q, fdbs[i].macaddr);
		ifname = i1905_brport_to_ifname(priv, fdbs[i].port);
		if (t) {
			/* update br-portnum where this neigh was last seen */
			t->brport = fdbs[i].port;
			timersub_msecs(&now, fdbs[i].ageing_timer_value, &t->last_seen);
			t->unreachable = fdbs[i].ageing_timer_value > NEIGH_AGEOUT_DEFAULT ? 1 : 0;

			if (ifname) {
				memset(t->ifname, 0, 16);
				strncpy(t->ifname, ifname, 16);
			}
		} else {
			/* create a new neigh just learnt from br-fdb */
			struct neigh_entry *new = NULL;
			enum if_mediatype mtype;

			if (ifname) {
				if_getmediatype(ifname, &mtype);
				if (mtype == IF_MEDIA_ETH) {
					new = neigh_enqueue(&priv->neigh_q,
							    fdbs[i].macaddr,
							    NEIGH_STATE_UNKNOWN,
							    ifname,
							    NEIGH_TYPE_ETH,
							    NULL,
							    NEIGH_AGEOUT_DEFAULT,
							    NULL);
					if (new) {
						timersub_msecs(&now, fdbs[i].ageing_timer_value, &new->last_seen);

						new->unreachable =
							fdbs[i].ageing_timer_value > NEIGH_AGEOUT_DEFAULT ?
							true : false;

						i1905_send_topology_notification(priv, ifname);

						i1905_extmodules_notify(priv,
									IEEE1905_TOPOLOGY_CHANGED,
									ifname,
									1,
									0,
									fdbs[i].macaddr);
					}
				}
			}
		}
	}

	return 0;
}

static int i1905_neigh_is_wifi_type(struct i1905_private *priv, char *ifname,
				    uint8_t *macaddr)
{
	struct i1905_interface *iface;
	struct ieee80211_info *wifi;
	enum if_mediatype mtype;
	uint8_t stas[768] = {0};
	int num = 128;
	int ret;
	int i;


	if (if_isbridge(ifname)) {
		i1905_dbg(LOG_NEIGH, "%s: '%s' is not allowed here\n",
			  __func__, ifname);
		return 0;
	}

	if_getmediatype(ifname, &mtype);
	if (mtype != IF_MEDIA_WIFI)
		return 0;

	iface = i1905_ifname_to_interface(priv, ifname);
	if (!iface || iface->mediainfo == NULL)
		return 0;

	wifi = (struct ieee80211_info *)iface->mediainfo;
	if (wifi->role != IEEE80211_ROLE_AP)
		return 0;

	ret = platform_wifi_get_assoclist(ifname, stas, &num);
	if (ret)
		return 0;

	for (i = 0; i < num; i++) {
		if (!memcmp(&stas[i*6], macaddr, 6)) {
			return 1;
		}
	}

	return 0;
}

static int i1905_handle_neigh_tbl_change(struct i1905_private *priv, bool add,
					 char *ifname, uint8_t *macaddr,
					 struct ip_address *ip,
					 uint16_t state)
{
	struct neigh_entry *new = NULL;
	enum neigh_type type = NEIGH_TYPE_UNKNOWN;


	if (hwaddr_is_zero(macaddr))
		return 0;

	if (!add) {
		i1905_dbg(LOG_NEIGH, "%s: NOP\n", __func__);
		return 0;
	}

	if (if_isbridge(ifname)) {
		struct i1905_interface *iface;
		struct i1905_selfdevice *self;
		uint16_t brport;

		/* update bridge portnum on which the hosts are last seen */
		i1905_update_neigh_brport(priv, ifname);

		brport = neigh_get_brport(&priv->neigh_q, macaddr);
		if (brport != 0xffff) {
			ifname = i1905_brport_to_ifname(priv, brport);
			if (!ifname)
				return 0;
		}

		/* do not enqueue wifi neigh types from here */
		self = &priv->dm.self;
		list_for_each_entry(iface, &self->iflist, list) {
			if (i1905_neigh_is_wifi_type(priv, iface->ifname, macaddr)) {
				type = NEIGH_TYPE_WIFI;
				break;
			}
		}
	} else {
		if (i1905_neigh_is_wifi_type(priv, ifname, macaddr))
			type = NEIGH_TYPE_WIFI;
	}

	if (type == NEIGH_TYPE_WIFI && !neigh_lookup(&priv->neigh_q, macaddr)) {
		i1905_dbg(LOG_NEIGH, "Do not create WiFi neigh " MACFMT " from RTM_NEWNEIGH event\n",
			  MAC2STR(macaddr));
		return 0;
	}

	new = neigh_enqueue(&priv->neigh_q, macaddr, state, ifname,
			    NEIGH_TYPE_UNKNOWN, ip, NEIGH_AGEOUT_DEFAULT, NULL);
	if (new) {
		i1905_send_topology_notification(priv, ifname);

		i1905_extmodules_notify(priv, IEEE1905_TOPOLOGY_CHANGED,
					ifname, 1, 0, macaddr);
	}

#if 0	//def NEIGH_DEBUG
	neigh_queue_print(&priv->neigh_q);
#endif
	return 0;
}

static int i1905_handle_nlevents_neigh(struct i1905_private *priv,
				       struct nlmsghdr *hdr, bool add)
{
	struct ndmsg *ndm = nlmsg_data(hdr);
	struct nlattr *nla[__NDA_MAX];
	uint8_t macaddr[6] = {0};
	struct ip_address ip;
	char ipbuf[256] = {0};
	char ifname[16] = {0};
	char state[128] = {0};


	i1905_trace(LOG_NEIGH, "%s: ------------->\n", __func__);
	if (!nlmsg_valid_hdr(hdr, sizeof(*ndm)))
		return NL_SKIP;

	nlmsg_parse(hdr, sizeof(*ndm), nla, __NDA_MAX - 1, NULL);
	if (!nla[NDA_DST])
		return NL_SKIP;


	nla_memcpy(&ip.addr, nla[NDA_DST], sizeof(ip.addr));
	nla_memcpy(macaddr, nla[NDA_LLADDR], sizeof(macaddr));
	if (hwaddr_is_zero(macaddr))
		return NL_SKIP;

	ip.family = ndm->ndm_family;
	if (IN6_IS_ADDR_LINKLOCAL(&ip.addr) || IN6_IS_ADDR_MULTICAST(&ip.addr))
		return NL_SKIP;

	if (ndm->ndm_family == AF_INET || ndm->ndm_family == AF_INET6)
		inet_ntop(ip.family, &ip.addr, ipbuf, sizeof(ipbuf));

	if_indextoname(ndm->ndm_ifindex, ifname);

	i1905_dbg(LOG_NEIGH, "%s: [ %s ] Neigh " MACFMT " ip = %s on %s, state = %s\n",
		  __func__, add ? "NEW" : "DEL", MAC2STR(macaddr), ipbuf, ifname,
		  rtnl_neigh_state2str(ndm->ndm_state, state, sizeof(state)));

	i1905_handle_neigh_tbl_change(priv, add, ifname, macaddr, &ip,
				      ndm->ndm_state);

	return NL_OK;
}

static int i1905_is_bridge_ours(struct i1905_private *priv, const char *ifname)
{
	struct i1905_iface_config *f = NULL;

	list_for_each_entry(f, &priv->cfg.iflist, list) {
		if ((f->is_regex && regex_match(f->ifname, &ifname[1])) ||
		    (strlen(f->ifname) == strlen(ifname) && !strncmp(f->ifname, ifname, strlen(f->ifname)))) {
			i1905_dbg(LOG_MAIN, "%s is managed by us\n", ifname);
			return 1;
		}
	}

	return 0;
}

static int i1905_is_interface_ours(struct i1905_private *priv, const char *ifname)
{
	struct i1905_iface_config *f = NULL;
	int br_ifindex = 0;

	if (if_isbridge(ifname))
		return (i1905_is_bridge_ours(priv, ifname));

	//TODO: check non-bridged standalone interfaces

	br_ifindex = if_isbridge_interface(ifname);
	if (br_ifindex <= 0)
		return 0;

	list_for_each_entry(f, &priv->cfg.iflist, list) {
		if (if_isbridge(f->ifname) &&
		    if_nametoindex(f->ifname) == br_ifindex) {
			i1905_dbg(LOG_MAIN, "%s belongs to %s managed by us\n",
				  ifname, f->ifname);
			return 1;
		}
	}

	return 0;
}

static int i1905_handle_nlevents_link(struct i1905_private *priv,
				      struct nlmsghdr *hdr, bool add)
{
	struct ifinfomsg *ifi = nlmsg_data(hdr);
	struct nlattr *nla[__IFLA_MAX];
	struct i1905_interface *iface;
	uint8_t macaddr[6] = {0};
	char flagstr[256] = {0};
	char ifname[16] = {0};
	int br_ifindex = 0;
	int operstate_xchg = 0;	/* 0 = no change, 1 = down->up, -1 = up->down */
	int carrier_xchg = 0; /* 0 = no change, 1 = off->on, -1 = on->off */
	int operstate = -1;
	bool addif = false;
	bool delif = false;
	int carrier = -1;
	int master = -1;


	i1905_trace(LOG_MAIN, "%s: ------------->\n", __func__);

	if (!nlmsg_valid_hdr(hdr, sizeof(*ifi)))
		return NL_SKIP;

	nlmsg_parse(hdr, sizeof(*ifi), nla, __IFLA_MAX - 1, NULL);
	if (!nla[IFLA_IFNAME])
		return NL_SKIP;

	nla_memcpy(ifname, nla[IFLA_IFNAME], 15);
	i1905_dbg(LOG_MAIN, "%s: %s ifname = %s\n", __func__,
		  add ? "NEWLINK" : "DELLINK", ifname);

	if (!strncmp(ifname, "lo", 2))
		return NL_SKIP;

	iface = i1905_ifname_to_interface(priv, ifname);
	if (!iface) {
		if (!i1905_is_interface_ours(priv, ifname)) {
			i1905_dbg(LOG_MAIN, "%s: %s is not ours\n", __func__, ifname);
			return NL_SKIP;	/* not interested */
		}

		if (if_isbridge(ifname) && add) {
			i1905_add_master_interface(priv, ifname);
			return NL_OK;
		}

		if (add)
			i1905_handle_iflink_change(priv, ifname);

		return NL_OK;
	}

	nla_memcpy(macaddr, nla[IFLA_ADDRESS], sizeof(macaddr));
	iface->ifflags = ifi->ifi_flags;
	iface->ifstatus = ifi->ifi_flags;
	rtnl_link_flags2str(ifi->ifi_flags, flagstr, sizeof(flagstr));

	if (nla[IFLA_OPERSTATE])
		operstate = nla_get_u8(nla[IFLA_OPERSTATE]);

	if (nla[IFLA_CARRIER])
		carrier = nla_get_u8(nla[IFLA_CARRIER]);

	if (nla[IFLA_MASTER])
		master = nla_get_u32(nla[IFLA_MASTER]);

	if (iface->is_brif) {
		if (!add && master > 0 && master == iface->br_ifindex) {
			iface->nomaster = 1;
			delif = true;
		} else if (add && master == iface->br_ifindex && iface->nomaster) {
			iface->nomaster = 0;
			addif = true;
		}
	}

	if (operstate != -1) {
		if (iface->operstate == IF_OPER_DOWN && operstate == IF_OPER_UP)
			operstate_xchg = 1;
		else if (iface->operstate == IF_OPER_UP && operstate == IF_OPER_DOWN)
			operstate_xchg = -1;

		iface->operstate = operstate;
	}

	if (carrier != -1) {
		if (iface->carrier == 0 && carrier == 1)
			carrier_xchg = 1;
		else if (iface->carrier == 1 && carrier == 0)
			carrier_xchg = -1;

		iface->carrier = carrier;
	}

	i1905_dbg(LOG_MAIN, "%s: %s (%d) family = %d, flags = 0x%x %s, opstate = %d, carrier = %d, master (%d) --->\n",
		  add ? "NEWLINK" : "DELLINK", ifname, ifi->ifi_index,
		  ifi->ifi_family, ifi->ifi_flags, flagstr, operstate,
		  carrier, master);

	if (if_isbridge(ifname)) {
		//TODO
		return NL_SKIP;
	}

	br_ifindex = if_isbridge_interface(ifname);
	if (br_ifindex > 0) {
		iface->is_brif = true;
		iface->brport = if_brportnum(iface->ifname);
		iface->br_ifindex = br_ifindex;
	}

	/* unplug: operstate = IF_OPER_DOWN && carrier = 0
	 * plug: operstate = IF_OPER_UP && carrier = 1
	 *
	 * delif: operstate = IF_OPER_UP && carrier = 1 && nomaster
	 * addif: operstate = IF_OPER_UP && carrier = 1 && master
	 *
	 * ifdown: operstate = IF_OPER_DOWN
	 * ifup: operstate = IF_OPER_UP
	 */
	if (operstate_xchg == -1 || carrier_xchg == -1 || delif) {
		i1905_dbg(LOG_MAIN, "%s: %s either went down, link-lost or removed from bridge.\n",
			  __func__, ifname);

		if (delif) {
			i1905_remove_interface_object(priv, ifname);
			i1905_teardown_interface(priv, ifname);
		}

		neigh_set_unreachable(&priv->neigh_q, ifname);

	} else if (operstate_xchg == 1 || carrier_xchg == 1 || addif) {
		i1905_dbg(LOG_MAIN, "%s: %s is up or added to bridge\n", __func__, ifname);
		/* addif case is handled above */

		neigh_probe_unreachable(&priv->neigh_q, ifname);
	}

	return NL_OK;
}

static int i1905_nlevents_cb(struct nl_msg *msg, void *arg)
{
	struct nlmsghdr *hdr = nlmsg_hdr(msg);
	struct i1905_private *priv = arg;
	int ret = NL_SKIP;
	bool add = false;


	switch (hdr->nlmsg_type) {
	case RTM_NEWLINK:
		add = true;
	case RTM_DELLINK:
		ret = i1905_handle_nlevents_link(priv, hdr, add);
		break;
#if 0	//TODO: when needed
	case RTM_NEWADDR:
		add = true;
	case RTM_DELADDR:
		ret = i1905_handle_nlevents_addr(priv, hdr, add);
		break;
#endif
	case RTM_NEWNEIGH:
		add = true;
	case RTM_DELNEIGH:
		ret = i1905_handle_nlevents_neigh(priv, hdr, add);
		break;

	default:
		break;
	}

	return ret;
}


static void i1905_receive_nlevents(struct uloop_fd *u, unsigned int events)
{
	struct i1905_nlevent *e = container_of(u, struct i1905_nlevent, uloop);

	if (u->error) {
		int ret = -1;
		socklen_t ret_len = sizeof(ret);

		u->error = false;
		if (e->error_cb &&
		    getsockopt(u->fd, SOL_SOCKET, SO_ERROR, &ret, &ret_len) == 0) {
			e->error_cb(e, ret);
		}
	}

	if (e->event_cb) {
		e->event_cb(e);
		return;
	}
}

int i1905_register_nlevents(struct i1905_private *priv)
{
	struct nl_sock *sk;


	sk = nl_socket_alloc();
	if (!sk) {
		err("Unable to open nl event socket: %m");
		return -1;
	}

	if (nl_connect(sk, NETLINK_ROUTE) < 0) {
		nl_socket_free(sk);
		return -1;
	}

	rtnl_event.sock = sk;

	if (nl_socket_set_buffer_size(rtnl_event.sock, rtnl_event.sock_bufsize, 0)) {
		err("%s: %d\n", __func__, __LINE__);
		goto out_err;
	}

	nl_socket_disable_seq_check(rtnl_event.sock);

	nl_socket_modify_cb(rtnl_event.sock, NL_CB_VALID, NL_CB_CUSTOM,
			    i1905_nlevents_cb, priv);

	if (nl_socket_add_memberships(rtnl_event.sock,
				      RTNLGRP_NEIGH, RTNLGRP_LINK, 0))
		goto out_err;

	rtnl_event.ev.uloop.fd = nl_socket_get_fd(rtnl_event.sock);
	rtnl_event.ev.uloop.cb = i1905_receive_nlevents;
	uloop_fd_add(&rtnl_event.ev.uloop, ULOOP_READ |
		     ((rtnl_event.ev.error_cb) ? ULOOP_ERROR_CB : 0));

	return 0;

out_err:
	if (rtnl_event.sock) {
		nl_socket_free(rtnl_event.sock);
		rtnl_event.sock = NULL;
		rtnl_event.ev.uloop.fd = -1;
	}

	return -1;
}

void i1905_unregister_nlevents(struct i1905_private *priv)
{
	UNUSED(priv);

	if (rtnl_event.sock) {
		uloop_fd_delete(&rtnl_event.ev.uloop);
		rtnl_event.ev.uloop.fd = -1;
		nl_socket_free(rtnl_event.sock);
		rtnl_event.sock = NULL;
	}
}

int i1905_get_known_neighbors(struct i1905_private *priv, char *ifname)
{
	struct rtnl_neigh *neigh;
	struct nl_object *nobj;
	struct nl_cache *cache;
	uint32_t ifindex = 0;
	struct nl_sock *sk;
	int i, num;
	int ret;



	ifindex = if_nametoindex(ifname);
	if (!ifindex)
		return -1;

	sk = nl_socket_alloc();
	if (!sk) {
		err("Unable to open nl event socket\n");
		return -1;
	}

	if (nl_connect(sk, NETLINK_ROUTE) < 0) {
		nl_socket_free(sk);
		return -1;
	}

	ret = rtnl_neigh_alloc_cache(sk, &cache);
	if (ret) {
		nl_socket_free(sk);
		return -1;
	}

	num = nl_cache_nitems(cache);
	nobj = nl_cache_get_first(cache);
	neigh = (struct rtnl_neigh *)nobj;

	for (i = 0; i < num; i++) {
		if (rtnl_neigh_get_ifindex(neigh) == ifindex) {
			struct nl_addr *lladdr = NULL;
			struct nl_addr *ipaddr = NULL;
			struct ip_address ip = {0};
			uint8_t hwaddr[6] = {0};
			uint16_t state;
			enum neigh_type type = NEIGH_TYPE_UNKNOWN;

			nl_object_get((struct nl_object *)neigh);

			state = rtnl_neigh_get_state(neigh);
			lladdr = rtnl_neigh_get_lladdr(neigh);
			if (lladdr && nl_addr_get_len(lladdr) == 6)
				memcpy(hwaddr, nl_addr_get_binary_addr(lladdr),
					nl_addr_get_len(lladdr));

			if (hwaddr_is_zero(hwaddr) || hwaddr_is_mcast(hwaddr)) {
				nl_object_put((struct nl_object *) neigh);
				nobj = nl_cache_get_next(nobj);
				neigh = (struct rtnl_neigh *)nobj;
				continue;
			}

			ipaddr = rtnl_neigh_get_dst(neigh);
			if (ipaddr) {
				ip.family = nl_addr_get_family(ipaddr);
				if (ip.family == AF_INET6 || ip.family == AF_INET) {
					memcpy(&ip.addr, nl_addr_get_binary_addr(ipaddr),
					       nl_addr_get_len(ipaddr));
				}

				/* ignore states for ipv6 entries */
				if (ip.family == AF_INET6) {
					nl_object_put((struct nl_object *)neigh);
					nobj = nl_cache_get_next(nobj);
					neigh = (struct rtnl_neigh *)nobj;
					continue;
				}
			}

			if (i1905_neigh_is_wifi_type(priv, ifname, hwaddr))
				type = NEIGH_TYPE_WIFI;

			neigh_enqueue(&priv->neigh_q, hwaddr, state, ifname,
				      type, &ip, NEIGH_AGEOUT_DEFAULT, NULL);

			nl_object_put((struct nl_object *)neigh);
		}

		nobj = nl_cache_get_next(nobj);
		neigh = (struct rtnl_neigh *)nobj;
	}

	nl_cache_free(cache);
	nl_socket_free(sk);

	if (if_isbridge(ifname)) {
		/* bridge port_nos on which the hosts are last seen */
		i1905_update_neigh_brport(priv, ifname);
	}

#if 0
	i1905_dbg(LOG_NEIGH, "-------------------------------\n");
	neigh_queue_print(&priv->neigh_q);
	i1905_dbg(LOG_NEIGH, "-------------------------------\n");
#endif

	return 0;
}

static void enum_interfaces_cb(struct nl_object *obj, void *arg)
{
	struct i1905_selfdevice_interface *iface;
	struct enum_interfaces_arg {
		struct list_head *iflist;
		int *n;
	} *argp = arg;
	struct rtnl_link *link;
	struct nl_addr *a;


	nl_object_get(obj);
	link = (struct rtnl_link *)obj;

	if (rtnl_link_get_arptype(link) == ARPHRD_VOID)
		goto out;

	iface = calloc(1, sizeof(*iface));
	if (!iface) {
		*argp->n = 0;
		return;
	}

	strncpy(iface->ifname, rtnl_link_get_name(link), 16);
	iface->exclude = 1;	/* include when config al-iface:ifname matches */

	a = rtnl_link_get_addr(link);
	if (!a || nl_addr_get_len(a) != 6) {
		free(iface);
		goto out;
	}

	iface->ifstatus = rtnl_link_get_flags(link);
	memcpy(iface->macaddr, nl_addr_get_binary_addr(a), nl_addr_get_len(a));
	if (hwaddr_is_zero(iface->macaddr) && !(iface->ifstatus & IFF_LOOPBACK)) {
		free(iface);
		goto out;
	}

	if (strncmp(iface->ifname, "lo", 2)) {
		iface->is_bridge = if_isbridge(iface->ifname);

		if (is_wifi_interface(iface->ifname))
			iface->mediatype = IF_MEDIA_WIFI;
		else
			iface->mediatype = IF_MEDIA_ETH;
	}

	list_add_tail(&iface->list, argp->iflist);
	*argp->n += 1;

out:
	nl_object_put((struct nl_object *)link);
}

static int i1905_enum_interfaces(struct list_head *head, int *num)
{
	struct nl_cache *cache;
	struct nl_sock *sk;
	int ret = 0;
	struct enum_interfaces_arg {
		struct list_head *head; /* cppcheck-suppress unusedStructMember */
		int *n; /* cppcheck-suppress unusedStructMember */
	} arg = {
		.head = head,
		.n = num,
	};

	sk = nl_socket_alloc();
	if (sk == NULL) {
		ret = -errno;
		return ret;
	}

	nl_connect(sk, NETLINK_ROUTE);

	ret = rtnl_link_alloc_cache(sk, AF_UNSPEC, &cache);
	if (ret) {
		nl_socket_free(sk);
		ret = -errno;
		return ret;
	}

	nl_cache_foreach(cache, enum_interfaces_cb, &arg);
	nl_cache_put(cache);
	nl_socket_free(sk);
	return 0;
}

int i1905_enumerate_local_interfaces(struct i1905_private *priv)
{
	struct i1905_selfdevice *self = &priv->dm.self;

	return i1905_enum_interfaces(&self->local_iflist, &self->num_local_interface);
}
