/*
 * ubus.c - ubus interface for status and management of hostmngr.
 *
 * Copyright (C) 2023 IOPSYS Software Solutions AB. All rights reserved.
 *
 * See LICENSE file for license related information.
 *
 */

#include <stdio.h>
#include <stdlib.h>
#include <stdint.h>
#include <string.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <sys/ioctl.h>
#include <net/if_arp.h>
#include <net/if.h>
#include <arpa/inet.h>
#include <netlink/route/neighbour.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 <easy/easy.h>

#include "wifi_api.h"
#include "util.h"
#include "debug.h"
#include "timer.h"
#include "neigh.h"
#include "hostmngr.h"


static const char *l4proto_type2str(uint8_t l4proto)
{
#define C2S(x)	case IPPROTO_ ## x: return #x;

	switch (l4proto) {
	C2S(TCP)
	C2S(UDP)
	C2S(ICMP)
	C2S(ICMPV6)
	C2S(SCTP)
	C2S(DCCP)
	C2S(GRE)
	C2S(UDPLITE)
	default:
		break;
	}

	return "Unknown";
#undef C2S
}

char *interface_type2str(enum neigh_type t)
{
	switch (t) {
	case NEIGH_TYPE_ETH:
		return "Ethernet";
	case NEIGH_TYPE_WIFI:
		return "Wi-Fi";
	case NEIGH_TYPE_GHN:
		return "G.hn";
	case NEIGH_TYPE_HPNA:
		return "HPNA";
	case NEIGH_TYPE_HOMEPLUG:
		return "HomePlug";
	case NEIGH_TYPE_MOCA:
		return "MoCA";
	case NEIGH_TYPE_UPA:
		return "UPA";
	case NEIGH_TYPE_OTHER:
		return "Other";
	default:
		dbg("%s: neigh interface-type = %u\n", __func__, t);
		break;
	}

	return "Ethernet";
}

uint32_t lookup_object(void *bus, const char *objname)
{
	uint32_t id;
	int status;

	status = ubus_lookup_id(bus, objname, &id);
	if (status != UBUS_STATUS_OK) {
		dbg("object '%s' not present!\n", objname);
		return OBJECT_INVALID;
	}

	return id;
}

static void hostmngr_1905topology_cb(struct ubus_request *req, int type,
				     struct blob_attr *msg)
{
	const struct blobmsg_policy attrs[2] = {
		[0] = { .name = "num_nodes", .type = BLOBMSG_TYPE_INT32 },
		[1] = { .name = "nodes", .type = BLOBMSG_TYPE_ARRAY },
	};
	enum {
		ATTR_ALID,
		ATTR_NUM_IF,
		ATTR_NUM_NBR_1905,
		ATTR_NUM_NBR_NON1905,
		ATTR_NUM_IPV4,
		ATTR_NUM_IPV6,
		ATTR_IFLIST,
		ATTR_NBRLIST,
		ATTR_NON1905_NBRLIST,
		ATTR_IP4LIST,
		ATTR_IP6LIST,
		_ATTR_MAX,
	};

	struct hostmngr_private *priv;
	struct blob_attr *tb[2];
	struct blob_attr *cur;
	int num_nodes, num_tb1;
	int rem;
	//int ret;

	if (!msg || !req->priv) {
		fprintf(stderr, "%s: %d: --- error ---\n", __func__, __LINE__);
		return;
	}

	blobmsg_parse(attrs, 2, tb, blob_data(msg), blob_len(msg));
	if (!tb[0] || !tb[1]) {
		fprintf(stderr, "%s: %d: --- error ---\n", __func__, __LINE__);
		return;
	}

	priv = req->priv;
	num_nodes = blobmsg_get_u32(tb[0]);
	num_tb1 = blobmsg_check_array(tb[1], BLOBMSG_TYPE_TABLE);
	if (num_nodes != num_tb1) {
		fprintf(stderr, "%s: %d: --- error ---\n", __func__, __LINE__);
		return;
	}

	/* Iterate through nodes */
	blobmsg_for_each_attr(cur, tb[1], rem) {
		const struct blobmsg_policy node_attrs[] = {
			[ATTR_ALID] = { .name = "ieee1905id", .type = BLOBMSG_TYPE_STRING },
			[ATTR_NUM_IF] = { .name = "num_interface", .type = BLOBMSG_TYPE_INT32 },
			[ATTR_NUM_NBR_1905] = { .name = "num_neighbor_1905", .type = BLOBMSG_TYPE_INT32 },
			[ATTR_NUM_NBR_NON1905] = { .name = "num_neighbor_non1905", .type = BLOBMSG_TYPE_INT32 },
			[ATTR_NUM_IPV4] = { .name = "num_ipv4", .type = BLOBMSG_TYPE_INT32 },
			[ATTR_NUM_IPV6] = { .name = "num_ipv6", .type = BLOBMSG_TYPE_INT32 },
			[ATTR_IFLIST] = { .name = "interface", .type = BLOBMSG_TYPE_ARRAY },
			[ATTR_NBRLIST] = { .name = "ieee1905_neighbors", .type = BLOBMSG_TYPE_ARRAY },
			[ATTR_NON1905_NBRLIST] = { .name = "non1905_neighbors", .type = BLOBMSG_TYPE_ARRAY },
			[ATTR_IP4LIST] = { .name = "ipv4_address", .type = BLOBMSG_TYPE_ARRAY },
			[ATTR_IP6LIST] = { .name = "ipv6_address", .type = BLOBMSG_TYPE_ARRAY },
		};
		struct blob_attr *tbn[ARRAY_SIZE(node_attrs)];
		struct blob_attr *n_attr, *nn_attr, *if_attr, *ip4_attr;
		uint8_t node_aladdr[6] = {0};
		char node_macstr[18] = {0};
		int num_interface;
		int num_neighbor_1905;
		int num_neighbor_non1905;
		int rem2, rem3, rem4, rem5;
		int num_ipv4;
		//int num_ipv6;
		struct neigh_entry *n;
		int iter = 0;

		blobmsg_parse(node_attrs, ARRAY_SIZE(node_attrs), tbn,
			      blobmsg_data(cur), blobmsg_len(cur));

		if (!tbn[ATTR_ALID] ||
		    !tbn[ATTR_NUM_IF] ||
		    !tbn[ATTR_NUM_NBR_1905] ||
		    !tbn[ATTR_NUM_NBR_NON1905] ||
		    !tbn[ATTR_IFLIST] ||
		    !tbn[ATTR_NBRLIST] ||
		    !tbn[ATTR_NON1905_NBRLIST] ||
		    !tbn[ATTR_IP4LIST] ||
		    !tbn[ATTR_IP6LIST]) {
			fprintf(stderr, "%s: %d: --- error ---\n", __func__, __LINE__);
			return;
		}

		if (blobmsg_type(tbn[ATTR_ALID]) != BLOBMSG_TYPE_STRING) {
			fprintf(stderr, "%s: %d: --- error ---\n", __func__, __LINE__);
			return;
		}

		strncpy(node_macstr, blobmsg_data(tbn[ATTR_ALID]), sizeof(node_macstr) - 1);
		if (!hwaddr_aton(node_macstr, node_aladdr)) {
			fprintf(stderr, "%s: %d: --- error ---\n", __func__, __LINE__);
			return;
		}

		neigh_set_1905(&priv->neigh_q, node_aladdr, 1);
		neigh_history_entry_set_1905(&priv->neigh_q, node_aladdr, 1);

		dbg("%s: Node " MACFMT "\n", __func__, MAC2STR(node_aladdr));

		num_interface = blobmsg_check_array(tbn[ATTR_IFLIST], BLOBMSG_TYPE_TABLE);
		num_neighbor_1905 = blobmsg_check_array(tbn[ATTR_NBRLIST], BLOBMSG_TYPE_TABLE);
		num_neighbor_non1905 = blobmsg_check_array(tbn[ATTR_NON1905_NBRLIST], BLOBMSG_TYPE_TABLE);
		num_ipv4 = blobmsg_check_array(tbn[ATTR_IP4LIST], BLOBMSG_TYPE_TABLE);
		//num_ipv6 = blobmsg_check_array(tbn[ATTR_IP6LIST], BLOBMSG_TYPE_TABLE);

		iter = 0;
		/* Iterate through interfaces of node */
		blobmsg_for_each_attr(if_attr, tbn[ATTR_IFLIST], rem4) {
			const struct blobmsg_policy if_attrs[] = {
				[0] = { .name = "macaddress", .type = BLOBMSG_TYPE_STRING },
				[1] = { .name = "media", .type = BLOBMSG_TYPE_STRING },
			};
			struct blob_attr *tbif[ARRAY_SIZE(if_attrs)];
			uint8_t if_macaddr[6] = {0};
			char if_macstr[18] = {0};
			char mediastr[64] = {0};

			blobmsg_parse(if_attrs, ARRAY_SIZE(if_attrs), tbif,
				      blobmsg_data(if_attr), blobmsg_data_len(if_attr));

			if (!tbif[0])
				continue;

			if (blobmsg_type(tbif[0]) != BLOBMSG_TYPE_STRING)
				continue;

			if (blobmsg_type(tbif[1]) != BLOBMSG_TYPE_STRING)
				continue;

			strncpy(if_macstr, blobmsg_data(tbif[0]), sizeof(if_macstr) - 1);
			if (!hwaddr_aton(if_macstr, if_macaddr)) {
				fprintf(stderr, "%s: %d: --- error ---\n", __func__, __LINE__);
				return;
			}

			strncpy(mediastr, blobmsg_data(tbif[1]), sizeof(mediastr) - 1);

			n = neigh_lookup_by_aladdr(priv, node_aladdr);
			if (n) {
				struct node_interface *new;

				new = niegh_add_interface(n, if_macaddr);
				if (new)
					new->mediatype = ieee80211_str_to_mediatype(mediastr);
			}

			neigh_set_1905_slave(&priv->neigh_q, if_macaddr, node_aladdr, 1);
			neigh_history_entry_set_1905_slave(&priv->neigh_q, if_macaddr, node_aladdr, 1);
			dbg("%s: Interface " MACFMT "\n", __func__, MAC2STR(if_macaddr));
			iter++;
		}

		if (num_interface != iter) {
			fprintf(stderr, "%s: %d: --- error ---\n", __func__, __LINE__);
			return;
		}


		iter = 0;
		/* Iterate through ipv4 addresses of node */
		blobmsg_for_each_attr(ip4_attr, tbn[ATTR_IP4LIST], rem5) {
			const struct blobmsg_policy ip4_attrs[] = {
				[0] = { .name = "macaddress", .type = BLOBMSG_TYPE_STRING },
			};
			struct blob_attr *tbip4[ARRAY_SIZE(ip4_attrs)];
			uint8_t if_macaddr[6] = {0};
			char if_macstr[18] = {0};

			blobmsg_parse(ip4_attrs, ARRAY_SIZE(ip4_attrs), tbip4,
				      blobmsg_data(ip4_attr), blobmsg_data_len(ip4_attr));

			if (!tbip4[0])
				continue;

			if (blobmsg_type(tbip4[0]) != BLOBMSG_TYPE_STRING)
				continue;

			strncpy(if_macstr, blobmsg_data(tbip4[0]), sizeof(if_macstr) - 1);
			if (!hwaddr_aton(if_macstr, if_macaddr)) {
				fprintf(stderr, "%s: %d: --- error ---\n", __func__, __LINE__);
				return;
			}

			neigh_set_1905_link(&priv->neigh_q, if_macaddr, node_aladdr, 1);
			neigh_history_entry_set_1905(&priv->neigh_q, if_macaddr, 1);
			neigh_history_entry_set_1905_link(&priv->neigh_q, if_macaddr, node_aladdr, 1);
			dbg("%s: Interface " MACFMT "\n", __func__, MAC2STR(if_macaddr));
			iter++;
		}

		if (num_ipv4 != iter) {
			fprintf(stderr, "%s: %d: --- error ---\n", __func__, __LINE__);
			return;
		}

		iter = 0;
		/* Iterate through neighbors of node */
		blobmsg_for_each_attr(n_attr, tbn[ATTR_NBRLIST], rem2) {
			const struct blobmsg_policy nbr_attrs[2] = {
				[0] = { .name = "macaddress", .type = BLOBMSG_TYPE_STRING },
				[1] = { .name = "via", .type = BLOBMSG_TYPE_STRING },
			};
			struct blob_attr *tbnbr[2];
			uint8_t nbr_macaddr[6] = {0};
			uint8_t nbr_viaaddr[6] = {0};
			char nbr_macstr[18] = {0};
			char nbr_viastr[18] = {0};


			blobmsg_parse(nbr_attrs, 2, tbnbr, blobmsg_data(n_attr), blobmsg_len(n_attr));
			if (!tbnbr[0] || !tbnbr[1])
				continue;

			if (blobmsg_type(tbnbr[0]) != BLOBMSG_TYPE_STRING)
				continue;

			if (blobmsg_type(tbnbr[0]) != BLOBMSG_TYPE_STRING)
				continue;

			strncpy(nbr_macstr, blobmsg_data(tbnbr[0]), sizeof(nbr_macstr) - 1);
			if (!hwaddr_aton(nbr_macstr, nbr_macaddr))
				return;

			strncpy(nbr_viastr, blobmsg_data(tbnbr[1]), sizeof(nbr_viastr) - 1);
			if (!hwaddr_aton(nbr_viastr, nbr_viaaddr)) {
				fprintf(stderr, "%s: %d: --- error ---\n", __func__, __LINE__);
				return;
			}

			dbg("%s: Nbr " MACFMT "\n", __func__, MAC2STR(nbr_macaddr));
			dbg("%s: Nif " MACFMT "\n", __func__, MAC2STR(nbr_viaaddr));

			if (!memcmp(nbr_macaddr, priv->ieee1905id, 6)) {
				neigh_set_1905_linkaddr(&priv->neigh_q, node_aladdr, nbr_viaaddr);
				neigh_set_1905_remote(&priv->neigh_q, node_aladdr, 0);
			} else {
				neigh_set_1905_remote(&priv->neigh_q, node_aladdr, 1);
			}

			iter++;
		}

		if (num_neighbor_1905 != iter) {
			fprintf(stderr, "%s: %d: --- error ---\n", __func__, __LINE__);
			return;
		}

		iter = 0;
		/* Iterate through non1905-neighbors of node */
		blobmsg_for_each_attr(nn_attr, tbn[ATTR_NON1905_NBRLIST], rem3) {
			const struct blobmsg_policy xbr_attrs[2] = {
				[0] = { .name = "macaddress", .type = BLOBMSG_TYPE_STRING },
				[1] = { .name = "via", .type = BLOBMSG_TYPE_STRING },
			};
			struct blob_attr *tbxbr[2];
			uint8_t xbr_macaddr[6] = {0};
			uint8_t xbr_viaaddr[6] = {0};
			char xbr_macstr[18] = {0};
			char xbr_viastr[18] = {0};
			struct neigh_entry *rn = NULL;

			blobmsg_parse(xbr_attrs, 2, tbxbr, blobmsg_data(nn_attr), blobmsg_data_len(nn_attr));
			if (!tbxbr[0] || !tbxbr[1])
				continue;

			if (blobmsg_type(tbxbr[0]) != BLOBMSG_TYPE_STRING)
				continue;

			if (blobmsg_type(tbxbr[0]) != BLOBMSG_TYPE_STRING)
				continue;

			strncpy(xbr_macstr, blobmsg_data(tbxbr[0]), sizeof(xbr_macstr) - 1);
			if (!hwaddr_aton(xbr_macstr, xbr_macaddr)) {
				fprintf(stderr, "%s: %d: --- error ---\n", __func__, __LINE__);
				return;
			}

			strncpy(xbr_viastr, blobmsg_data(tbxbr[1]), sizeof(xbr_viastr) - 1);
			if (!hwaddr_aton(xbr_viastr, xbr_viaaddr)) {
				fprintf(stderr, "%s: %d: --- error ---\n", __func__, __LINE__);
				return;
			}

			/* appropriately set neigh-type of non-local hosts */
			rn = neigh_lookup(&priv->neigh_q, xbr_macaddr);
			if (memcmp(node_aladdr, priv->ieee1905id, 6)) {
				enum neigh_type rn_type = NEIGH_TYPE_ETH;

				n = neigh_lookup_by_aladdr(priv, node_aladdr);
				if (n) {
					struct node_interface *nif = NULL;

					list_for_each_entry(nif, &n->iflist, list) {
						if (!memcmp(nif->macaddr, xbr_viaaddr, 6) &&
						    nif->mediatype == IF_MEDIA_WIFI)
							rn_type = NEIGH_TYPE_WIFI;
					}
				}

				if (rn) {
					rn->isremote = 1;
					rn->type = rn_type;
				} else {
					/* create remote non-1905 host entry */
					dbg("%s: neigh_enqueue " MACFMT " (priv = %p) >>>\n",
					    __func__, MAC2STR(xbr_macaddr), priv);
					rn = neigh_enqueue(&priv->neigh_q, xbr_macaddr,
						      NEIGH_STATE_REACHABLE,
						      "br-lan", /* FIXME */
						      rn_type,
						      NULL,
						      NEIGH_AGEOUT_DEFAULT,
						      NULL);
					if (rn) {
						rn->isremote = 1;
						hostmngr_get_neigh_hostname(&priv->neigh_q, xbr_macaddr);
						neigh_history_enqueue(priv, rn,
								      priv->cfg.history_ageout);

						if (!ipaddr_is_zero(&rn->ipv4) && (rn->ipv4_type_static || strlen(rn->hostname)))
							hostmngr_host_event(priv, HOST_EVENT_CONNECT, rn);
						else
							rn->event_pending = 1;
					}
				}
			} else {
				if (rn)
					rn->isremote = 0;
			}

			dbg("%s: Xbr " MACFMT "\n", __func__, MAC2STR(xbr_macaddr));
			dbg("%s: Xif " MACFMT "\n", __func__, MAC2STR(xbr_viaaddr));
			iter++;
		}

		if (num_neighbor_non1905 != iter) {
			fprintf(stderr, "%s: %d: --- error ---\n", __func__, __LINE__);
			return;
		}

	}

}

int hostmngr_get_1905_topology(struct hostmngr_private *p)
{
	const char *objname = "ieee1905.topology";
	struct blob_buf bb = {0};
	uint32_t id;
	int ret;

	id = lookup_object(p->bus, objname);
	if (id == OBJECT_INVALID)
		return UBUS_STATUS_OK;

	blob_buf_init(&bb, 0);
	ret = ubus_invoke(p->bus, id, "dump", bb.head,
			  hostmngr_1905topology_cb, p, 2000);
	if (ret) {
		dbg("Failed 'ieee1905.topology dump' (ret = %s)\n",
		    ubus_strerror(ret));
	}

	blob_buf_free(&bb);
	return ret;
}

static void hostmngr_1905aladdr_cb(struct ubus_request *req, int type,
			       struct blob_attr *msg)
{
	const struct blobmsg_policy attrs[1] = {
		[0] = { .name = "ieee1905id", .type = BLOBMSG_TYPE_STRING },
	};
	struct hostmngr_private *priv;
	struct blob_attr *tb[1];
	uint8_t aladdr[6] = {0};
	char alstr[18] = {0};

	if (!msg || !req->priv)
		return;

	blobmsg_parse(attrs, 1, tb, blob_data(msg), blob_len(msg));
	if (!tb[0])
		return;

	priv = req->priv;
	strncpy(alstr, blobmsg_data(tb[0]), sizeof(alstr) - 1);
	if (!hwaddr_aton(alstr, aladdr)) {
		fprintf(stderr, "%s: %d: error\n", __func__, __LINE__);
		return;
	}
	memcpy(priv->ieee1905id, aladdr, 6);
}

int hostmngr_get_1905_aladdr(struct hostmngr_private *p)
{
	const char *objname = "ieee1905";
	struct blob_buf bb = {0};
	uint32_t id;
	int ret;

	id = lookup_object(p->bus, objname);
	if (id == OBJECT_INVALID)
		return UBUS_STATUS_OK;

	blob_buf_init(&bb, 0);
	ret = ubus_invoke(p->bus, id, "id", bb.head,
			  hostmngr_1905aladdr_cb, p, 2000);
	if (ret) {
		dbg("Failed 'ieee1905 id' (ret = %s)\n",
		    ubus_strerror(ret));
	}

	blob_buf_free(&bb);
	return ret;
}

static void hostmngr_network_interface_dump_cb(struct ubus_request *req,
						int mtype,
						struct blob_attr *msg)
{
	const struct blobmsg_policy attrs[1] = {
		[0] = { .name = "interface", .type = BLOBMSG_TYPE_ARRAY },
	};
	struct hostmngr_private *priv;
	struct blob_attr *tb[1];
	struct blob_attr *cur;
	int rem;

	if (!msg || !req->priv)
		return;

	blobmsg_parse(attrs, 1, tb, blob_data(msg), blob_len(msg));
	if (!tb[0])
		return;

	priv = req->priv;

	/* Iterate through interfaces */
	blobmsg_for_each_attr(cur, tb[0], rem) {
		const struct blobmsg_policy netif_attrs[2] = {
			[0] = { .name = "interface", .type = BLOBMSG_TYPE_STRING },
			[1] = { .name = "device", .type = BLOBMSG_TYPE_STRING },
		};
		struct local_interface *iface = NULL;
		struct blob_attr *tbn[2];
		char interface[32] = {0};
		char device[32] = {0};

		blobmsg_parse(netif_attrs, 2, tbn, blobmsg_data(cur), blobmsg_len(cur));
		if (!tbn[0] || !tbn[1])
			return;

		if (blobmsg_type(tbn[0]) != BLOBMSG_TYPE_STRING ||
		    blobmsg_type(tbn[1]) != BLOBMSG_TYPE_STRING)
			return;

		strncpy(interface, blobmsg_data(tbn[0]), sizeof(interface) - 1);
		strncpy(device, blobmsg_data(tbn[1]), sizeof(device) - 1);
		dbg("%s: interface = %s, device = %s\n", __func__, interface, device);

		iface = hostmngr_ifname_to_interface(priv, device);
		if (iface) {
			memset(iface->network, 0, sizeof(iface->network));
			strncpy(iface->network, interface, strlen(interface));
		}
	}
}

int hostmngr_get_interface_network(struct hostmngr_private *priv)
{
	const char *net_objname = "network.interface";
	struct blob_buf bb;
	uint32_t id;
	int ret;

	id = lookup_object(priv->bus, net_objname);
	if (id == OBJECT_INVALID)
		return -1;

	/* ubus call network.interface dump */
	memset(&bb, 0, sizeof(struct blob_buf));
	blob_buf_init(&bb, 0);
	ret = ubus_invoke(priv->bus, id, "dump", bb.head,
			  hostmngr_network_interface_dump_cb, priv, 2000);

	blob_buf_free(&bb);
	return ret;
}

void hostmngr_topology_dump_cb(struct ubus_request *req, int type, struct blob_attr *msg)
{
	struct blob_buf *bb = (struct blob_buf *)req->priv;
	struct json_object *jmsg;
	char *jstr;

	blob_buf_init(bb, 0);
	jstr = blobmsg_format_json(msg, true);
	if (!jstr)
		return;

	jmsg = json_tokener_parse(jstr);
	if (!jmsg)
		goto out_str;

	if (!json_object_is_type(jmsg, json_type_object))
		goto out_json;

	if (!blobmsg_add_json_from_string(bb, jstr))
		dbg("%s: blobmsg from json-string error\n", __func__);

out_json:
	json_object_put(jmsg);
out_str:
	free(jstr);
}

int hostmngr_ubus_dump_topology(struct ubus_context *ctx, struct ubus_object *obj,
			struct ubus_request_data *req, const char *method,
			struct blob_attr *msg)
{
	struct hostmngr_private *p = container_of(obj, struct hostmngr_private, obj);
	const char *objname = "ieee1905.topology";
	struct blob_buf bi = {0};
	struct blob_buf bo = {0};
	uint32_t id;
	int ret;

	id = lookup_object(p->bus, objname);
	if (id == OBJECT_INVALID)
		return UBUS_STATUS_OK;

	/* ubus call topology dump */
	blob_buf_init(&bi, 0);
	ret = ubus_invoke(p->bus, id, "dump", bi.head,
			  hostmngr_topology_dump_cb, &bo, 2000);
	if (ret == UBUS_STATUS_OK)
		ubus_send_reply(ctx, req, bo.head);

	blob_buf_free(&bi);
	blob_buf_free(&bo);

	return UBUS_STATUS_OK;
}

int hostmngr_ubus_show_arptable(struct ubus_context *ctx, struct ubus_object *obj,
			     struct ubus_request_data *req, const char *method,
			     struct blob_attr *msg)
{
	struct hostmngr_private *p = container_of(obj, struct hostmngr_private, obj);
	struct neigh_queue *q = &p->neigh_q;
	struct blob_buf bb;
	void *a, *aa;
	int i;

	memset(&bb, 0, sizeof(bb));
	blob_buf_init(&bb, 0);

	a = blobmsg_open_array(&bb, "arptable");
	for (i = 0; i < NEIGH_ENTRIES_MAX; i++) {
		struct neigh_entry *e = NULL;
		char macstr[18] = {0};

		if (hlist_empty(&q->table[i]))
			continue;

		hlist_for_each_entry(e, &q->table[i], hlist) {
			aa = blobmsg_open_table(&bb, "");
			uint16_t brport;
			char *ifname = NULL;

			hwaddr_ntoa(e->macaddr, macstr);
			blobmsg_add_string(&bb, "macaddr", macstr);
			blobmsg_add_u8(&bb, "is1905", e->is1905 ? true : false);
			blobmsg_add_u8(&bb, "is1905_interface", e->is1905_slave ? true : false);
			blobmsg_add_u8(&bb, "is1905_link", e->is1905_link ? true : false);
			blobmsg_add_u32(&bb, "ndm_state", e->state);

			brport = neigh_get_brport(q, e->macaddr);
			if (brport != 0xffff) {
				ifname = hostmngr_brport_to_ifname(p, brport);
				if (ifname)
					blobmsg_add_string(&bb, "ifname", ifname);
			}

			blobmsg_close_table(&bb, aa);
		}
	}
	blobmsg_close_array(&bb, a);

	ubus_send_reply(ctx, req, bb.head);
	blob_buf_free(&bb);

	return 0;
}

int hostmngr_ubus_show_hosts(struct ubus_context *ctx, struct ubus_object *obj,
			     struct ubus_request_data *req, const char *method,
			     struct blob_attr *msg)
{
	struct hostmngr_private *p = container_of(obj, struct hostmngr_private, obj);
	struct neigh_queue *q = &p->neigh_q;
	struct host_macaddr_entry *h;
	struct blob_buf bb;
	void *a, *aa, *aaa;

	memset(&bb, 0, sizeof(bb));
	blob_buf_init(&bb, 0);

	a = blobmsg_open_array(&bb, "hosts");
	list_for_each_entry(h, &p->hostlist, list) {
		struct neigh_history_entry *he = NULL;
		struct neigh_entry *e = NULL;
		char macstr[18] = {0};

		e = neigh_lookup(q, h->macaddr);
		if (e) {
			struct ip_address_entry *ipv4, *ipv6;
			char statestr[128] = {0};
			char ipstr[128] = {0};
			char *ifname = NULL;
			char tbuf[64] = {0};
			long leasetime_rem;
			char *net = NULL;
			uint16_t brport;
			struct tm *t;
			void *wt;
			struct node_interface *ni;

			//if (e->unreachable)
			//	continue;

			if (e->is1905 || (e->is1905_slave && !e->is1905_link) ||
			    e->is_affiliated_sta) {
				dbg("%s: skipping entry " MACFMT"\n", __func__, MAC2STR(e->macaddr));
				continue;
			}

			neigh_update_ip_entry_stats(p, &e->ipv4, e);
			hostmngr_get_neigh_hostname(q, e->macaddr);

			aa = blobmsg_open_table(&bb, "");

			hwaddr_ntoa(e->macaddr, macstr);
			blobmsg_add_string(&bb, "macaddr", macstr);
			blobmsg_add_string(&bb, "hostname", e->hostname);
			blobmsg_add_u8(&bb, "active", e->unreachable ? false : true);
			blobmsg_add_u32(&bb, "reconnect_count", h->reconnect_cnt);
			blobmsg_add_u8(&bb, "local", e->isremote ? false : true);

			t = gmtime(&e->lastchange);
			strftime(tbuf, sizeof(tbuf), "%Y-%m-%dT%H:%M:%SZ", t);
			blobmsg_add_string(&bb, "active_last_change", tbuf);

			inet_ntop(e->ipv4.family, &e->ipv4.addr, ipstr, sizeof(ipstr));
			blobmsg_add_string(&bb, "ipaddr", ipstr);
			leasetime_rem = e->leasetime ? e->leasetime - (unsigned long)time(NULL) : 0;
			blobmsg_add_string(&bb, "address_source", e->ipv4_type_dhcp ? "DHCP": "Static");
			blobmsg_add_u32(&bb, "lease_time_remaining", (leasetime_rem < 0) ? 0 : leasetime_rem);

			ni = neigh_interface_lookup(e, e->linkaddr[0]);
			if (ni)
				blobmsg_add_string(&bb, "interface_type", (ni->mediatype == IF_MEDIA_WIFI ? "Wi-Fi" : "Ethernet"));
			else
				blobmsg_add_string(&bb, "interface_type", interface_type2str(e->type));

			if (e->is1905_link) {
				struct neigh_entry *stamld;
				uint8_t (*linkaddr)[6];
				int num_links;
				int i;

				blobmsg_add_u8(&bb, "is1905", true);
				blobmsg_add_macaddr(&bb, "ieee1905id", e->aladdr);

				/* TODO: below can be improved when/if ieee1905.topology
				 * dump supports MLD */
				stamld = neigh_lookup_stamld_by_aladdr(p, e->aladdr);
				if (stamld) {
					blobmsg_add_u8(&bb, "is_mld", true);
					blobmsg_add_macaddr(&bb, "mld_macaddr", stamld->macaddr);
					linkaddr  = stamld->linkaddr;
					num_links = stamld->num_links;
				} else {
					linkaddr  = e->linkaddr;
					num_links = e->num_links;
				}
				aaa = blobmsg_open_array(&bb, "link_macaddr");

				for (i = 0; i < num_links; i++)
					blobmsg_add_macaddr(&bb, "macaddr", linkaddr[i]);

				blobmsg_close_array(&bb, aaa);
			} else if (e->is_stamld) {
				int i;

				blobmsg_add_u8(&bb, "is_mld", true);
				aaa = blobmsg_open_array(&bb, "link_macaddr");
				for (i = 0; i < e->num_links; i++)
					blobmsg_add_macaddr(&bb, "macaddr", e->linkaddr[i]);
				blobmsg_close_array(&bb, aaa);
			}

			rtnl_neigh_state2str(e->state, statestr, sizeof(statestr));
			blobmsg_add_string(&bb, "ndm_state", statestr);
			blobmsg_add_u32(&bb, "num_tcp", e->num_tcp);
			blobmsg_add_u32(&bb, "num_udp", e->num_udp);
			blobmsg_add_u64(&bb, "active_connections", e->num_tcp + e->num_udp);

			brport = neigh_get_brport(q, e->macaddr);
			if (brport != 0xffff) {
				ifname = hostmngr_brport_to_ifname(p, brport);
				if (ifname) {
					blobmsg_add_string(&bb, "device", ifname);
				}
			} else {
				blobmsg_add_string(&bb, "device", e->ifname);
				ifname = e->ifname;
			}

			if (is_wifi_interface(ifname) == 1) {
				char pifname[16] = {0};

				interface_get_4addr_parent(ifname, pifname);
				blobmsg_add_string(&bb, "parent_device", pifname);
			}

			net = hostmngr_ifname_to_network(p, ifname);
			blobmsg_add_string(&bb, "network", net ? net : "");

			aaa = blobmsg_open_array(&bb, "ipv4addr");
			list_for_each_entry(ipv4, &e->iplist, list) {

				if (ipv4->ip.family == AF_INET) {
					char ipbuf[256] = {0};

					inet_ntop(ipv4->ip.family, &ipv4->ip.addr, ipbuf, sizeof(ipbuf));
					blobmsg_add_string(&bb, "ip", ipbuf);
				}
			}
			blobmsg_close_array(&bb, aaa);

			aaa = blobmsg_open_array(&bb, "ipv6addr");
			list_for_each_entry(ipv6, &e->iplist, list) {

				if (ipv6->ip.family == AF_INET6) {
					char ipbuf[256] = {0};

					inet_ntop(ipv6->ip.family, &ipv6->ip.addr, ipbuf, sizeof(ipbuf));
					blobmsg_add_string(&bb, "ip", ipbuf);
				}
			}
			blobmsg_close_array(&bb, aaa);

			/* wan stats from/to this host */
			wt = blobmsg_open_table(&bb, "stats");
			blobmsg_add_u64(&bb, "tx_packets", e->ws.ul_packets);
			blobmsg_add_u64(&bb, "tx_bytes", e->ws.ul_bytes);
			blobmsg_add_u64(&bb, "rx_packets", e->ws.dl_packets);
			blobmsg_add_u64(&bb, "rx_bytes", e->ws.dl_bytes);
			blobmsg_close_table(&bb, wt);

			/* total stats = current session + past sessions from history */
			he = neigh_history_lookup(q, e->macaddr);
			wt = blobmsg_open_table(&bb, "total_stats");
			blobmsg_add_u64(&bb, "tx_packets",
					(e->ws.ul_packets - e->ws_synced.ul_packets) +
					(he ? he->ws.ul_packets : 0));
			blobmsg_add_u64(&bb, "tx_bytes",
					(e->ws.ul_bytes - e->ws_synced.ul_bytes) +
					(he ? he->ws.ul_bytes : 0));
			blobmsg_add_u64(&bb, "rx_packets",
					(e->ws.dl_packets - e->ws_synced.dl_packets) +
					(he ? he->ws.dl_packets : 0));
			blobmsg_add_u64(&bb, "rx_bytes",
					(e->ws.dl_bytes - e->ws_synced.dl_bytes) +
					(he ? he->ws.dl_bytes : 0));
			blobmsg_close_table(&bb, wt);

			blobmsg_close_table(&bb, aa);
		} else {
			he = neigh_history_lookup(q, h->macaddr);
			if (he) {
				struct ip_address_entry *ipv4, *ipv6;
				char ipstr[128] = {0};
				char tbuf[64] = {0};
				char *net = NULL;
				struct tm *t;
				void *wt;


				if (he->alive) {
					dbg("%s: skipping alive history entry " MACFMT"\n",
					    __func__, MAC2STR(he->macaddr));
					continue;
				}

				if ((he->is1905_slave && !he->is1905_link) || he->is_affiliated_sta) {
					dbg("%s: skipping history entry " MACFMT"\n",
					    __func__, MAC2STR(he->macaddr));
					continue;
				}

				aa = blobmsg_open_table(&bb, "");
				hwaddr_ntoa(he->macaddr, macstr);
				blobmsg_add_string(&bb, "macaddr", macstr);
				blobmsg_add_string(&bb, "hostname", he->hostname);
				blobmsg_add_u8(&bb, "active", false);
				blobmsg_add_u32(&bb, "reconnect_count", h->reconnect_cnt);


				t = gmtime(&he->lastchange);
				strftime(tbuf, sizeof(tbuf), "%Y-%m-%dT%H:%M:%SZ", t);
				blobmsg_add_string(&bb, "active_last_change", tbuf);

				inet_ntop(he->ip.family, &he->ip.addr, ipstr, sizeof(ipstr));
				blobmsg_add_string(&bb, "ipaddr", ipstr);

				blobmsg_add_string(&bb, "address_source", he->ipv4_type_dhcp ? "DHCP": "Static");
				blobmsg_add_u32(&bb, "lease_time_remaining", 0);
				blobmsg_add_string(&bb, "interface_type", interface_type2str(he->type));
				if (he->is1905_link) {
					char alstr[18] = {0};
					char linkstr[18] = {0};

					blobmsg_add_u8(&bb, "is1905", true);
					hwaddr_ntoa(he->aladdr, alstr);
					//hwaddr_ntoa(he->linkaddr, linkstr);
					blobmsg_add_string(&bb, "ieee1905id", alstr);
					blobmsg_add_string(&bb, "link_macaddr", linkstr);
				} else {
					blobmsg_add_u8(&bb, "is1905", false);
				}
				blobmsg_add_string(&bb, "ndm_state", "Unknown");
				blobmsg_add_u32(&bb, "num_tcp", 0);
				blobmsg_add_u32(&bb, "num_udp", 0);
				blobmsg_add_u64(&bb, "active_connections", 0);
				blobmsg_add_string(&bb, "device", he->ifname);
				net = hostmngr_ifname_to_network(p, he->ifname);
				blobmsg_add_string(&bb, "network", net ? net : "");

				aaa = blobmsg_open_array(&bb, "ipv4addr");
				list_for_each_entry(ipv4, &he->iplist, list) {
					if (ipv4->ip.family == AF_INET) {
						char ipbuf[256] = {0};

						inet_ntop(ipv4->ip.family, &ipv4->ip.addr, ipbuf, sizeof(ipbuf));
						blobmsg_add_string(&bb, "ip", ipbuf);
					}
				}
				blobmsg_close_array(&bb, aaa);

				aaa = blobmsg_open_array(&bb, "ipv6addr");
				list_for_each_entry(ipv6, &he->iplist, list) {
					if (ipv6->ip.family == AF_INET6) {
						char ipbuf[256] = {0};

						inet_ntop(ipv6->ip.family, &ipv6->ip.addr, ipbuf, sizeof(ipbuf));
						blobmsg_add_string(&bb, "ip", ipbuf);
					}
				}
				blobmsg_close_array(&bb, aaa);

				wt = blobmsg_open_table(&bb, "stats");
				blobmsg_add_u64(&bb, "tx_packets", 0);
				blobmsg_add_u64(&bb, "tx_bytes", 0);
				blobmsg_add_u64(&bb, "rx_packets", 0);
				blobmsg_add_u64(&bb, "rx_bytes", 0);
				blobmsg_close_table(&bb, wt);

				/* total stats = history stats when host is inactive */
				wt = blobmsg_open_table(&bb, "total_stats");
				blobmsg_add_u64(&bb, "tx_packets", he->ws.ul_packets);
				blobmsg_add_u64(&bb, "tx_bytes", he->ws.ul_bytes);
				blobmsg_add_u64(&bb, "rx_packets", he->ws.dl_packets);
				blobmsg_add_u64(&bb, "rx_bytes", he->ws.dl_bytes);
				blobmsg_close_table(&bb, wt);

				blobmsg_close_table(&bb, aa);
			}
		}
	}
	blobmsg_close_array(&bb, a);

	ubus_send_reply(ctx, req, bb.head);
	blob_buf_free(&bb);

	return 0;
}

int hostmngr_ubus_show_history(struct ubus_context *ctx, struct ubus_object *obj,
				struct ubus_request_data *req, const char *method,
				struct blob_attr *msg)
{
	struct hostmngr_private *p = container_of(obj, struct hostmngr_private, obj);
	struct neigh_queue *q = &p->neigh_q;
	time_t now = time(NULL);
	struct blob_buf bb;
	void *a, *aa;
	int i;

	memset(&bb, 0, sizeof(bb));
	blob_buf_init(&bb, 0);

	a = blobmsg_open_array(&bb, "history");
	for (i = 0; i < NEIGH_ENTRIES_MAX; i++) {
		struct neigh_history_entry *e = NULL;
		char macstr[18] = {0};

		if (hlist_empty(&q->history[i]))
			continue;

		hlist_for_each_entry(e, &q->history[i], hlist) {
			char ipstr[128] = {0};
			char tbuf3[64] = {0};
			char tbuf2[64] = {0};
			char tbuf[64] = {0};
			struct tm *t;
			void *wt;

			aa = blobmsg_open_table(&bb, "");

			blobmsg_add_u32(&bb, "idx", i);
			hwaddr_ntoa(e->macaddr, macstr);
			blobmsg_add_string(&bb, "macaddr", macstr);
			blobmsg_add_string(&bb, "hostname", e->hostname);

			t = localtime(&e->lastchange);
			strftime(tbuf3, sizeof(tbuf3), "%Y-%m-%dT%H:%M:%S", t);
			blobmsg_add_string(&bb, "active_last_change", tbuf3);

			inet_ntop(e->ip.family, &e->ip.addr, ipstr, sizeof(ipstr));
			blobmsg_add_string(&bb, "ipaddr", ipstr);

			t = localtime(&e->createtime);
			strftime(tbuf, sizeof(tbuf), "%Y-%m-%dT%H:%M:%S", t);
			blobmsg_add_string(&bb, "first_seen", tbuf);

			t = e->alive ? localtime(&now) : localtime(&e->lastseen);
			strftime(tbuf2, sizeof(tbuf2), "%Y-%m-%dT%H:%M:%S", t);
			blobmsg_add_string(&bb, "last_seen", tbuf2);

			blobmsg_add_u8(&bb, "alive", e->alive ? true : false);
			blobmsg_add_string(&bb, "interface_type", interface_type2str(e->type));
			blobmsg_add_u8(&bb, "is1905", e->is1905 ? true : false);
			blobmsg_add_u8(&bb, "is1905_interface", e->is1905_slave ? true : false);
			blobmsg_add_u8(&bb, "is1905_link", e->is1905_link ? true : false);
			if (e->is1905_link) {
				char alstr[18] = {0};

				hwaddr_ntoa(e->aladdr, alstr);
				blobmsg_add_string(&bb, "ieee1905id", alstr);
			}

			wt = blobmsg_open_table(&bb, "stats");
			blobmsg_add_u64(&bb, "tx_packets", e->ws.ul_packets);
			blobmsg_add_u64(&bb, "tx_bytes", e->ws.ul_bytes);
			blobmsg_add_u64(&bb, "rx_packets", e->ws.dl_packets);
			blobmsg_add_u64(&bb, "rx_bytes", e->ws.dl_bytes);
			blobmsg_close_table(&bb, wt);

			blobmsg_close_table(&bb, aa);
		}
	}
	blobmsg_close_array(&bb, a);

	ubus_send_reply(ctx, req, bb.head);
	blob_buf_free(&bb);
	return 0;
}

int hostmngr_ubus_debug_output(struct ubus_context *ctx, struct ubus_object *obj,
			     struct ubus_request_data *req, const char *method,
			     struct blob_attr *msg)
{
	struct hostmngr_private *p = container_of(obj, struct hostmngr_private, obj);
	struct neigh_queue *q = &p->neigh_q;
	struct neigh_flow_table *ft = &p->nftable;
	struct blob_buf bb;
	void *a, *aa, *aaa;
	int i;

	memset(&bb, 0, sizeof(bb));
	blob_buf_init(&bb, 0);

	aaa = blobmsg_open_table(&bb, "");
	a = blobmsg_open_array(&bb, "neigh_table");
	for (i = 0; i < NEIGH_ENTRIES_MAX; i++) {
		struct neigh_entry *e = NULL;
		char macstr[18] = {0};

		if (hlist_empty(&q->table[i]))
			continue;

		hlist_for_each_entry(e, &q->table[i], hlist) {
			aa = blobmsg_open_table(&bb, "");
			char ip4buf[32] = {0};
			char nstr[32] = {0};

			blobmsg_add_u32(&bb, "idx", i);
			snprintf(nstr, sizeof(nstr), "%p", e);
			blobmsg_add_string(&bb, "@", nstr);
			hwaddr_ntoa(e->macaddr, macstr);
			blobmsg_add_string(&bb, "macaddress", macstr);
			blobmsg_add_string(&bb, "hostname", e->hostname);
			inet_ntop(e->ipv4.family, &e->ipv4.addr, ip4buf, sizeof(ip4buf));
			blobmsg_add_string(&bb, "ipv4addr", ip4buf);
			blobmsg_close_table(&bb, aa);
		}
	}
	blobmsg_close_array(&bb, a);

	a = blobmsg_open_array(&bb, "ipaddr_table");
	for (i = 0; i < NEIGH_IP_ENTRIES_MAX; i++) {
		struct neigh_ip_entry *e = NULL;
		char macstr[18] = {0};

		if (hlist_empty(&ft->iptable[i]))
			continue;

		hlist_for_each_entry(e, &ft->iptable[i], iphlist) {
			aa = blobmsg_open_table(&bb, "");
			char ipbuf[32] = {0};
			char nstr[32] = {0};
			void *wt;

			blobmsg_add_u32(&bb, "idx", i);
			hwaddr_ntoa(e->macaddr, macstr);
			blobmsg_add_string(&bb, "macaddress", macstr);
			inet_ntop(e->ip.family, &e->ip.addr, ipbuf, sizeof(ipbuf));
			blobmsg_add_string(&bb, "ipv4addr", ipbuf);
			snprintf(nstr, sizeof(nstr), "0x%p", e->neigh ? e->neigh : 0);
			blobmsg_add_string(&bb, "@neigh", nstr);

			wt = blobmsg_open_table(&bb, "wan_stats");
			blobmsg_add_u32(&bb, "num_tcp", e->num_tcp);
			blobmsg_add_u32(&bb, "num_udp", e->num_udp);
			blobmsg_add_u64(&bb, "tx_packets", e->ws.ul_packets);
			blobmsg_add_u64(&bb, "tx_bytes", e->ws.ul_bytes);
			blobmsg_add_u64(&bb, "rx_packets", e->ws.dl_packets);
			blobmsg_add_u64(&bb, "rx_bytes", e->ws.dl_bytes);
			blobmsg_close_table(&bb, wt);

			blobmsg_close_table(&bb, aa);
		}
	}
	blobmsg_close_array(&bb, a);

	blobmsg_add_u32(&bb, "num_flow_entries", ft->num);
	a = blobmsg_open_array(&bb, "flow_table");
	for (i = 0; i < NEIGH_FLOW_ENTRIES_MAX; i++) {
		struct neigh_flow_entry *e = NULL;
		char macstr[18] = {0};

		if (hlist_empty(&ft->table[i]))
			continue;

		hlist_for_each_entry(e, &ft->table[i], hlist) {
			aa = blobmsg_open_table(&bb, "");
			char sipbuf[64] = {0};
			char dipbuf[64] = {0};

			blobmsg_add_u32(&bb, "idx", i);
			hwaddr_ntoa(e->macaddr, macstr);
			blobmsg_add_string(&bb, "macaddress", macstr);
			blobmsg_add_string(&bb, "protocol", l4proto_type2str(e->l4proto));
			inet_ntop(e->sip.family, &e->sip.addr, sipbuf, sizeof(sipbuf));
			blobmsg_add_string(&bb, "sip", sipbuf);
			inet_ntop(e->dip.family, &e->dip.addr, dipbuf, sizeof(dipbuf));
			blobmsg_add_string(&bb, "dip", dipbuf);
			if (e->l4proto == IPPROTO_TCP || e->l4proto == IPPROTO_UDP) {
				blobmsg_add_u16(&bb, "sport", ntohs(e->sport));
				blobmsg_add_u16(&bb, "dport", ntohs(e->dport));
			}
			blobmsg_close_table(&bb, aa);
		}
	}
	blobmsg_close_array(&bb, a);

	blobmsg_close_table(&bb, aaa);

	ubus_send_reply(ctx, req, bb.head);
	blob_buf_free(&bb);

	return 0;
}

int hostmngr_publish_object(struct hostmngr_private *p, const char *objname)
{
	struct ubus_object_type *obj_type;
	struct ubus_method *obj_methods;
	struct ubus_object *obj;
	struct ubus_method m[5] = {
		UBUS_METHOD_NOARG("show", hostmngr_ubus_show_hosts),
		UBUS_METHOD_NOARG("dump", hostmngr_ubus_dump_topology),
		UBUS_METHOD_NOARG("history", hostmngr_ubus_show_history),
		UBUS_METHOD_NOARG("arptable", hostmngr_ubus_show_arptable),
		UBUS_METHOD_NOARG("debug", hostmngr_ubus_debug_output),
	};
	int num_methods = ARRAY_SIZE(m);
	int ret;


	if (!p->bus) {
		dbg("%s: connect to ubus..\n", __func__);
		p->bus = ubus_connect(NULL);
		if (!p->bus) {
			dbg("Failed to connect to ubus\n");
			return -1;
		}
		ubus_add_uloop(p->bus);
	}

	obj = &p->obj;
	memset(obj, 0, sizeof(*obj));

	obj_type = calloc(1, sizeof(struct ubus_object_type));
	if (!obj_type)
		return -1;

	obj_methods = calloc(num_methods, sizeof(struct ubus_method));
	if (!obj_methods) {
		free(obj_type);
		return -1;
	}

	obj->name = strdup(objname);
	memcpy(obj_methods, m, num_methods * sizeof(struct ubus_method));


	obj->methods = obj_methods;
	obj->n_methods = num_methods;

	obj_type->name = obj->name;
	obj_type->n_methods = obj->n_methods;
	obj_type->methods = obj->methods;
	obj->type = obj_type;

	ret = ubus_add_object(p->bus, obj);
	if (ret) {
		dbg("Failed to add '%s' err = %s\n", objname, ubus_strerror(ret));
		free((void *)obj->name);
		free(obj_methods);
		free(obj_type);

		return ret;
	}

	dbg("Added object '%s'\n", objname);
	return 0;
}

int hostmngr_remove_object(struct hostmngr_private *p)
{
	if (p->bus) {
		if (p->obj.id != OBJECT_INVALID) {
			ubus_remove_object(p->bus, &p->obj);
			free(p->obj.type);
			free((void *)p->obj.methods);
			free((void *)p->obj.name);
		}
	}

	return 0;
}

static void hostmngr_wifi_sta_event_handler(struct hostmngr_private *p,
					 struct blob_attr *msg)
{
	char ifname[16] = {0}, event[16] = {0};
	struct blob_attr *tb[3];
	static const struct blobmsg_policy ev_attr[3] = {
		[0] = { .name = "ifname", .type = BLOBMSG_TYPE_STRING },
		[1] = { .name = "event", .type = BLOBMSG_TYPE_STRING },
		[2] = { .name = "data", .type = BLOBMSG_TYPE_TABLE },
	};
	bool add = false, del = false;


	blobmsg_parse(ev_attr, 3, tb, blob_data(msg), blob_len(msg));

	if (!tb[0] || !tb[1] || !tb[2])
		return;

	strncpy(ifname,	blobmsg_data(tb[0]), sizeof(ifname) - 1);
	strncpy(event, blobmsg_data(tb[1]), sizeof(event) - 1);

	add = !strcmp(event, "connected");
	del = !strcmp(event, "disconnected");

	if (add || del) {
		struct blob_attr *data[2];
		static const struct blobmsg_policy data_attr[2] = {
			[0] = { .name = "macaddr", .type = BLOBMSG_TYPE_STRING },
			[1] = { .name = "mlo_macaddr", .type = BLOBMSG_TYPE_STRING },
		};
		uint8_t mlo_macaddr[6] = {0};
		uint8_t macaddr[6] = {0};
		bool is_mld = false;
		uint8_t *m;

		blobmsg_parse(data_attr, 2, data, blobmsg_data(tb[2]),
			      blobmsg_data_len(tb[2]));

		if (!data[0])
			return;

		if (!hwaddr_aton(blobmsg_data(data[0]), macaddr))
			return;

		m = macaddr;

		if (data[1]) {
			if (!hwaddr_aton(blobmsg_data(data[1]), mlo_macaddr))
				return;

			m = mlo_macaddr;
			is_mld = true;
		}

		if (add) {
			struct neigh_entry *new = NULL;

			dbg("%s: neigh_enqueue " MACFMT " (priv = %p) >>>\n",
			    __func__, MAC2STR(m), p);
			new = neigh_enqueue(&p->neigh_q, m,
					    NEIGH_STATE_REACHABLE,
					    ifname,
					    NEIGH_TYPE_WIFI,
					    NULL,
					    NEIGH_AGEOUT_DEFAULT,
					    NULL);
			if (new) {
				/* new wifi neigh added */
				hostmngr_get_neigh_hostname(&p->neigh_q, m);
				/* add/update history cache for this neigh */
				neigh_history_enqueue(p, new,
						      p->cfg.history_ageout);

				if (!ipaddr_is_zero(&new->ipv4) && (new->ipv4_type_static || strlen(new->hostname)))
					hostmngr_host_event(p, HOST_EVENT_CONNECT, new);
				else
					new->event_pending = 1;

			} else
				new = neigh_lookup(&p->neigh_q, m);

			if (new && is_mld) {
				struct neigh_entry *nn = NULL;

				new->is_stamld = true;

				/* handle affiliated STA */
				nn = neigh_enqueue(&p->neigh_q, macaddr,
					NEIGH_STATE_REACHABLE,
					ifname,
					NEIGH_TYPE_WIFI,
					NULL,
					NEIGH_AGEOUT_DEFAULT,
					NULL);
				if (!nn)
					nn = neigh_lookup(&p->neigh_q, macaddr);
				if (nn) {
					nn->is_affiliated_sta = true;
					memcpy(new->linkaddr[new->num_links++], macaddr, 6);
				}
			}
		} else if (del) {
			dbg("%s: neigh_dequeue " MACFMT " (priv = %p) >>>\n",
			    __func__, MAC2STR(macaddr), p);
			neigh_dequeue(&p->neigh_q, macaddr, NULL);

			if (is_mld) {
				dbg("%s: neigh_dequeue " MACFMT " (priv = %p) >>>\n",
			    	    __func__, MAC2STR(mlo_macaddr), p);
				neigh_dequeue(&p->neigh_q, mlo_macaddr, NULL);
			}
		}
	}
}

static void hostmngr_local_event_handler(struct ubus_context *ctx,
				     struct ubus_event_handler *e,
				     const char *type, struct blob_attr *msg)
{
	struct hostmngr_private *p = container_of(e, struct hostmngr_private, evh);
	struct wifi_ev_handler {
		const char *type;
		void (*handler)(struct hostmngr_private *, struct blob_attr *);
	} evs[] = {
		{ "wifi.sta", hostmngr_wifi_sta_event_handler },
	};
	char *str;
	int i;


	str = blobmsg_format_json(msg, true);
	if (!str)
		return;

	//info("Received event: [%s] event = '%s'\n", type, str);
	free(str);

	for (i = 0; i < ARRAY_SIZE(evs); i++) {
		if (!strcmp(type, evs[i].type)) {
			evs[i].handler(p, msg);
			break;
		}
	}
}

int hostmngr_register_local_events(struct hostmngr_private *p)
{
	int ret;

	if (!p || !p->bus)
		return -1;

	p->evh.cb = hostmngr_local_event_handler;
	ret = ubus_register_event_handler(p->bus, &p->evh, "ethport");
	ret |= ubus_register_event_handler(p->bus, &p->evh, "wifi.*");

	return ret;
}

int hostmngr_unregister_local_events(struct hostmngr_private *p)
{
	if (p)
		ubus_unregister_event_handler(p->bus, &p->evh);

	return 0;
}

int hostmngr_host_event(struct hostmngr_private *priv, const char *event,
			struct neigh_entry *e)
{
	struct blob_buf b = {0};
	char macstr[18] = {0};
	char ipstr[128] = {0};
	char *net = NULL;

	blob_buf_init(&b, 0);
	if (!strcmp(event, HOST_EVENT_CONNECT))
		blobmsg_add_u8(&b, "active", true);
	else if (!strcmp(event, HOST_EVENT_DISCONNECT))
		blobmsg_add_u8(&b, "active", false);

	hwaddr_ntoa(e->macaddr, macstr);
	blobmsg_add_string(&b, "macaddr", macstr);

	inet_ntop(e->ipv4.family, &e->ipv4.addr, ipstr, sizeof(ipstr));
	blobmsg_add_string(&b, "ipaddr", ipstr);

	net = hostmngr_ifname_to_network(priv, e->ifname);
	blobmsg_add_string(&b, "network", net ? net : "");

	blobmsg_add_string(&b, "hostname", e->hostname);

	ubus_send_event(priv->bus, HOST_EVENT, b.head);
	blob_buf_free(&b);

	return 0;
}
