/*
 * hostmngr.c - implementation file for network hosts management.
 *
 * 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 <errno.h>
#include <unistd.h>
#include <fcntl.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 <linux/if_packet.h>
#include <net/ethernet.h>

#include <netlink/route/link.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 "useropts.h"
#include "debug.h"
#include "timer.h"
#include "neigh.h"
#include "hostmngr.h"


static int signal_pending;

static void hostmngr_sighandler(int sig)
{
	signal_pending = sig;
}


static void hostmngr_update_interface_bridgeinfo(struct local_interface *iface)
{
	iface->is_bridge = if_isbridge(iface->ifname);
	if (!iface->is_bridge) {
		int br_ifindex;

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

static void hostmngr_update_interface_ipaddrs(struct local_interface *iface)
{
	struct ip_address ips[32] = {0};
	int num_ipaddrs = 32;
	int ret;

	ret = if_getaddrs(iface->ifname, ips, &num_ipaddrs);
	if (!ret && num_ipaddrs > 0) {
		iface->ipaddrs = calloc(num_ipaddrs, sizeof(struct ip_address));
		if (iface->ipaddrs) {
			iface->num_ipaddrs = num_ipaddrs;
			memcpy(iface->ipaddrs, ips, num_ipaddrs * sizeof(struct ip_address));
		}
	}
}

static void hostmngr_update_interface_mediatype(struct local_interface *iface)
{
	if (strncmp(iface->ifname, "lo", 2)) {
		if (is_wifi_interface(iface->ifname)) {
			iface->mediatype = IF_MEDIA_WIFI;
			if (is_ap_interface(iface->ifname)) {
				iface->is_ap = true;
				iface->is_affiliated_ap = is_affiliated_ap(iface->ifname);
				if (iface->is_affiliated_ap)
					ap_get_mld_ifname(iface->ifname, iface->mld_ifname);
			}
		} else {
			iface->mediatype = IF_MEDIA_ETH;
		}
	}
}

struct local_interface *hostmngr_alloc_interface(const char *ifname)
{
	struct local_interface *iface = NULL;

	iface = calloc(1, sizeof(*iface));
	if (!iface) {
		return NULL;
	}

	snprintf(iface->ifname, 16, "%s", ifname);
	iface->ifindex = if_nametoindex(ifname);
	dbg("%s: %s (%d)\n", __func__, iface->ifname, iface->ifindex);
	if_gethwaddr(ifname, iface->macaddr);
	if_getflags(ifname, &iface->ifflags);
	if_getoperstate(ifname, &iface->operstate);
	if_getcarrier(ifname, &iface->carrier);

	hostmngr_update_interface_bridgeinfo(iface);
	hostmngr_update_interface_ipaddrs(iface);
	hostmngr_update_interface_mediatype(iface);

	return iface;
}

void enum_interfaces_cb(struct nl_object *obj, void *arg)
{
	struct enum_interfaces_arg {
		struct list_head *iflist;
		int *n;
	} *argp = arg;


	struct local_interface *iface = NULL;
	struct rtnl_link *lnk = NULL;
	struct nl_addr *a = NULL;


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

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

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

	strncpy(iface->ifname, rtnl_link_get_name(lnk), 16);
	iface->ifindex = rtnl_link_get_ifindex(lnk);
	iface->exclude = 1;
	iface->priv = NULL;

	a = rtnl_link_get_addr(lnk);
	if (a && nl_addr_get_len(a) == 6)
		memcpy(iface->macaddr, nl_addr_get_binary_addr(a), nl_addr_get_len(a));

	iface->ifflags = rtnl_link_get_flags(lnk);
	iface->operstate = rtnl_link_get_operstate(lnk);

	hostmngr_update_interface_bridgeinfo(iface);
	hostmngr_update_interface_ipaddrs(iface);
	hostmngr_update_interface_mediatype(iface);

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

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

int enum_interfaces(struct list_head *head, int *num)
{
	struct nl_cache *cache;
	struct nl_sock *sk = NULL;
	int ret = 0;
	struct enum_interfaces_arg {
		struct list_head *iflist; /* cppcheck-suppress unusedStructMember */
		int *n;                   /* cppcheck-suppress unusedStructMember */
	} arg = {
		.iflist = 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 hostmngr_enumerate_interfaces(struct hostmngr_private *priv)
{
	return enum_interfaces(&priv->iflist, &priv->num_local_interfaces);
}

static void hostmngr_init_interface_private(struct hostmngr_private *priv)
{
	struct local_interface *e = NULL;

	if (priv->num_local_interfaces == 0 || list_empty(&priv->iflist))
		return;

	list_for_each_entry(e, &priv->iflist, list) {
		e->priv = priv;

		if (e->exclude)
			continue;

		if (e->is_bridge)
			hostmngr_register_arp_sock(e->ifname, &e->arpsk, e);
	}
}

void hostmngr_print_interfaces(struct hostmngr_private *priv)
{
	struct local_interface *e;
	int i = 0;

	if (priv->num_local_interfaces == 0 || list_empty(&priv->iflist))
		return;

	list_for_each_entry(e, &priv->iflist, list) {
		dbg("%d: ifname = %16s, mac = " MACFMT ", ifindex = %3d, flags = 0x%08x, opstate = 0x%02x, is-bridge = %d, brport = %d\n",
			i++, e->ifname, MAC2STR(e->macaddr), e->ifindex,
			e->ifflags, e->operstate, e->is_bridge, e->brport);
	}
}

void hostmngr_free_interfaces(struct hostmngr_private *priv)
{
	struct local_interface *e = NULL, *tmp;

	if (priv->num_local_interfaces == 0 || list_empty(&priv->iflist))
		return;

	list_for_each_entry_safe(e, tmp, &priv->iflist, list) {
		if (e->mediainfo)
			free(e->mediainfo);
		if (e->num_ipaddrs && e->ipaddrs)
			free(e->ipaddrs);
		list_del(&e->list);
		free(e);
	}
}

#if 0
int hostmngr_enumerate_interfaces_in_zone(struct hostmngr_private *priv,
					  const char *zone, char *iflist,
					  int *num)
{

}
#endif


int hostmngr_include_interface(struct hostmngr_private *priv, const char *ifname)
{
	struct local_interface *e;

	if (if_isbridge(ifname)) {
		char ifnames[32][16] = {0};
		int n = 32;
		int ret;
		int i;

		ret = br_get_iflist(ifname, &n, ifnames);
		if (ret)
			return -1;

		list_for_each_entry(e, &priv->iflist, list) {
			for (i = 0; i < n; i++) {
				if (!strncmp(e->ifname, ifnames[i], strlen(e->ifname))) {
					e->exclude = 0;
					dbg("Include interface '%s'\n", e->ifname);
					break;
				}
			}
		}
	}

	list_for_each_entry(e, &priv->iflist, list) {
		if (!strncmp(e->ifname, ifname, strlen(e->ifname))) {
			e->exclude = 0;
			dbg("Include interface '%s'\n", e->ifname);
			break;
		}
	}

	return 0;
}

int hostmngr_exclude_interfaces(struct hostmngr_private *priv, void *useropts)
{
	struct hostmngr_useropts *uopts = (struct hostmngr_useropts *)useropts;
	struct local_interface *e;
	int i;

	/* exclude interface(s) passed in cmdline */
	if (!uopts || uopts->num_exclude == 0)
		return 0;

	list_for_each_entry(e, &priv->iflist, list) {
		for (i = 0; i < uopts->num_exclude; i++) {
			if (!strncmp(e->ifname, uopts->exclude_ifnames[i],
				     strlen(e->ifname))) {

				e->exclude = 1;
				break;
			}
		}
	}

	return 0;
}

int hostmngr_get_interface_neighbors(struct hostmngr_private *priv)
{
	struct local_interface *e = NULL;

	if (priv->num_local_interfaces == 0 || list_empty(&priv->iflist))
		return 0;

	list_for_each_entry(e, &priv->iflist, list) {
		if (e->exclude || hwaddr_is_zero(e->macaddr))
			continue;

		if (e->is_ap)
			hostmngr_get_wifi_stations(priv, e);
	}

	list_for_each_entry(e, &priv->iflist, list) {
		if (e->exclude || hwaddr_is_zero(e->macaddr))
			continue;

		hostmngr_get_known_neighbors(priv, e->ifname);
	}

	return 0;
}

void remove_newline(char *buf)
{
	int len = strlen(buf) - 1;

	if (buf[len] == '\n')
		buf[len] = 0;
}

int read_dhcpv4_lease_table(void **clients, int *num_clients)
{
	struct dhcp_clients {
		uint8_t macaddr[6];
		char ipv4[24];
		char hostname[256];
		unsigned long leasetime;
	} *hosts = NULL, *tmp = NULL, *ptr;
	FILE *f = NULL;
	char line[256];
	int cnt = 0;
	int ret = 0;

	f = fopen("/var/dhcp.leases", "r");  /* cppcheck-suppress cert-MSC24-C */
	if (!f)
		return -1;

	while (fgets(line, sizeof(line), f) != NULL) {
		unsigned long leasetime = 0;
		char macaddr[18] = {0};
		char ipaddr[24] = {0};
		char hostname[256] = {0};
		char clid[256] = {0};

		remove_newline(line);
		if (sscanf(line, "%lu %17s %23s %255s %255s", &leasetime,
			   macaddr, ipaddr, hostname, clid) == 5) {

			ptr = realloc(hosts, (cnt + 1) * sizeof(struct dhcp_clients));
			if (!ptr) {
				if (hosts)
					free(hosts);

				*num_clients = 0;
				ret = -1;
				goto out;
			}

			hosts = ptr;
			tmp = hosts + cnt;
			memset(tmp, 0, sizeof(struct dhcp_clients));
			hwaddr_aton(macaddr, tmp->macaddr);
			strncpy(tmp->ipv4, ipaddr, sizeof(ipaddr)-1);
			strncpy(tmp->hostname, hostname, sizeof(hostname)-1);
			tmp->leasetime = leasetime;
			cnt += 1;
		}
	}

	*clients = hosts;
	*num_clients = cnt;
	dbg("%s: num-dhcphosts = %d\n", __func__, *num_clients);
out:
	fclose(f);
	return ret;
}

void hostmngr_update_hostname_of_neighbors(struct hostmngr_private *priv,
					   void *dhclients, int num)
{
	struct dhcp_clients {
		uint8_t macaddr[6];
		char ipv4[24];
		char hostname[256];
		unsigned long leasetime;
	} *h, *hosts = (struct dhcp_clients *)dhclients;
	struct neigh_entry *e = NULL;
	struct neigh_ip_entry *ipn;
	int i;


	if (num == 0 || !hosts)
		return;

	for (i = 0; i < num; i++) {
		h = hosts + i;
		e = neigh_lookup(&priv->neigh_q, h->macaddr);
		if (e) {
			strncpy(e->hostname, h->hostname, sizeof(e->hostname) - 1);
			inet_aton(h->ipv4, &e->ipv4.addr.ip4);
			e->ipv4.family = AF_INET;
			e->leasetime = h->leasetime;
			e->ipv4_type_dhcp = 1;

			if (!neigh_is_ip_known(e, &e->ipv4)) {
				struct ip_address_entry *new;
				char newip[64] = {0};

				inet_ntop(e->ipv4.family, &e->ipv4.addr, newip, sizeof(newip));
				dbg("%s: Adding new ipaddress %s to entry " MACFMT"\n",
					__func__, newip, MAC2STR(e->macaddr));

				new = calloc(1, sizeof(*new));
				if (new) {
					memcpy(&new->ip, &e->ipv4, sizeof(e->ipv4));
					list_add_tail(&new->list, &e->iplist);
				}
			}

			ipn = neigh_ip_entry_lookup2(priv, &e->ipv4);
			if (ipn) {
				ipn->neigh = e;
				memcpy(ipn->macaddr, e->macaddr, 6);
				dbg("Host " MACFMT " with ip = %s (0x%x) added to iptable\n",
				    MAC2STR(e->macaddr), h->ipv4, e->ipv4.addr.ip4.s_addr);
			}
		}
	}
}

int hostmngr_get_neigh_hostname(struct neigh_queue *q, uint8_t *macaddr)
{
	struct hostmngr_private *priv = container_of(q, struct hostmngr_private, neigh_q);	//TODO:
	struct dhcp_clients {
		uint8_t macaddr[6];
		char ipv4[24];
		char hostname[256];
		unsigned long leasetime;
	} *h, *hosts;
	struct neigh_entry *e = NULL;
	struct neigh_ip_entry *ipn;
	int i;

	void *entries = NULL;
	int num = 0;
	int ret;

	e = neigh_lookup(q, macaddr);
	if (!e)
		return -1;

	ret = read_dhcpv4_lease_table(&entries, &num);
	if (ret || num <= 0) {
		ret = -1;
		goto out;
	}

	dbg("%s: num-dhcphosts = %d\n", __func__, num);
	hosts = (struct dhcp_clients *)entries;
	for (i = 0; i < num; i++) {
		h = hosts + i;
		if (hwaddr_equal(h->macaddr, macaddr)) {
			strncpy(e->hostname, h->hostname, sizeof(e->hostname) - 1);
			inet_aton(h->ipv4, &e->ipv4.addr.ip4);
			e->ipv4.family = AF_INET;
			e->leasetime = h->leasetime;
			e->ipv4_type_dhcp = 1;
			e->ipv4_type_static = 0;

			if (!neigh_is_ip_known(e, &e->ipv4)) {
				struct ip_address_entry *new;
				char newip[64] = {0};

				inet_ntop(e->ipv4.family, &e->ipv4.addr, newip, sizeof(newip));
				dbg("%s: Adding new ipaddress %s to entry " MACFMT"\n",
					__func__, newip, MAC2STR(e->macaddr));

				new = calloc(1, sizeof(*new));
				if (new) {
					memcpy(&new->ip, &e->ipv4, sizeof(e->ipv4));
					list_add_tail(&new->list, &e->iplist);
				}
			}


			ipn = neigh_ip_entry_lookup2(priv, &e->ipv4);
			if (ipn) {
				ipn->neigh = e;
				memcpy(ipn->macaddr, e->macaddr, 6);
				dbg("Host " MACFMT " with ip = %s (0x%x) added to iptable\n",
				    MAC2STR(e->macaddr), h->ipv4, e->ipv4.addr.ip4.s_addr);
			}

			ret = 0;
			break;
		}
	}


out:
	/* if lease table has been consulted before with no results
	 * assume static IP address
	 */
	if (e->event_pending && !strlen(e->hostname)) {
		e->ipv4_type_static = 1;
		e->ipv4_type_dhcp = 0;
	}

	if (entries)
		free(entries);
	return ret;
}

int hostmngr_get_neighbors_hostname(struct hostmngr_private *priv)
{
	void *entries = NULL;
	int num = 0;
	int ret;

	ret = read_dhcpv4_lease_table(&entries, &num);
	if (!ret && num > 0) {
		hostmngr_update_hostname_of_neighbors(priv, entries, num);
		free(entries);
	}

	return 0;
}

int hostmngr_register_notifier_file(struct hostmngr_private *priv, const char *filename)
{
#if 0	//TODO
	int wd;

	priv->fd_dhcp4 = inotify_init1(IN_NONBLOCK);
	if (priv->fd_dhcp4 == -1) {
		dbg("Error: %s\n", strerror(errno));
		return -1;
	}

	wd = inotify_add_watch(fd, filename, IN_MODIFY);
	if (wd == -1) {
		close(priv->fd_dhcp4);
		return -1;
	}

	//TODO; uloop_fd_add() etc.
#endif
	return 0;
}

static void recv_arp_cb(struct uloop_fd *u, unsigned int events)
{
	struct arp_sock *sk = container_of(u, struct arp_sock, uloop);
	struct local_interface *iface = (struct local_interface *)sk->priv;

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

		dbg("%s: - error -\n", __func__);
		u->error = false;
		/*
		if (e->error_cb &&
		    getsockopt(u->fd, SOL_SOCKET, SO_ERROR, &ret, &ret_len) == 0) {
		       e->error_cb(e, ret);
		}
		*/
	}

	for (;;) {
		char buf[512] = {0};
		ssize_t len;
		struct ethhdr *eh;

		len = read(u->fd, buf, sizeof(buf));
		if (len == -1) {
			//dbg("%s: len = %d (%s)\n", __func__, len, strerror(errno));
			return;
		}

		if (len <= sizeof(struct ethhdr)) {
			return;
		}

		eh = (struct ethhdr *)buf;
		if (ntohs(eh->h_proto) == ETH_P_ARP) {
			struct hostmngr_private *priv = (struct hostmngr_private *)iface->priv;

			err("ARP received from " MACFMT" on %s\n", MAC2STR(eh->h_source), iface->ifname);
			if (!iface->is_brif || !iface->nomaster)
				neigh_mark_reachable(&priv->neigh_q, eh->h_source, iface->ifname);
		}
	}
}

int hostmngr_register_arp_sock(const char *ifname, struct arp_sock *arpsk, void *priv)
{
	struct sockaddr_ll sa;
	int ifindex;
	int flags;
	int s;

	if (!ifname || !arpsk)
		return -1;

	s = socket(AF_PACKET, SOCK_RAW, htons(ETH_P_ARP));
	if (s < 0)
		return -1;

	flags = fcntl(s, F_GETFL, 0);
	if (flags != -1)
		fcntl(s, F_SETFL, flags | O_NONBLOCK);

	ifindex = if_nametoindex(ifname);
	if (!ifindex) {
		close(s);
		return -1;
	}

	memset(&sa, 0, sizeof(struct sockaddr_ll));
	sa.sll_family = AF_PACKET;
	sa.sll_protocol = htons(ETH_P_ARP);
	sa.sll_ifindex = ifindex;

	if (bind(s, (struct sockaddr *)&sa, sizeof(struct sockaddr_ll)) < 0) {
		err("Failed to bind arp sock\n");
		close(s);
		return -1;
	}

	arpsk->uloop.fd = s;
	arpsk->uloop.cb = recv_arp_cb;
	uloop_fd_add(&arpsk->uloop, ULOOP_READ);
	arpsk->priv = priv;
	dbg("%s: %s\n", __func__, ifname);

	return 0;
}

void hostmngr_unregister_arp_sock(struct arp_sock *arpsk)
{
	if (arpsk) {
		uloop_fd_delete(&arpsk->uloop);
		close(arpsk->uloop.fd);
		arpsk->priv = NULL;
	}
}

char *hostmngr_brport_to_ifname(struct hostmngr_private *priv, uint16_t brport)
{
	struct local_interface *iface = NULL;

	if (list_empty(&priv->iflist))
		return NULL;

	list_for_each_entry(iface, &priv->iflist, list) {
		if (iface->is_brif && iface->brport == brport)
			return iface->ifname;
	}

	return NULL;
}

struct local_interface *hostmngr_get_master_interface(struct hostmngr_private *priv,
						      int ifindex)
{
	struct local_interface *iface = NULL;

	if (list_empty(&priv->iflist))
		return NULL;

	list_for_each_entry(iface, &priv->iflist, list) {
		if (iface->is_bridge && iface->ifindex == ifindex)
			return iface;
	}

	return NULL;
}

struct local_interface *hostmngr_ifname_to_interface(struct hostmngr_private *priv,
						      const char *ifname)
{
	struct local_interface *iface;


	if (!ifname || !priv)
		return NULL;

	if (list_empty(&priv->iflist))
		return NULL;

	list_for_each_entry(iface, &priv->iflist, list) {
		if (!strncmp(iface->ifname, ifname, 16))
			return iface;
	}

	return NULL;
}

char *hostmngr_ifname_to_network(struct hostmngr_private *priv, const char *ifname)
{
	struct local_interface *iface, *mif;

	if (!ifname || !priv || list_empty(&priv->iflist))
		return NULL;

	list_for_each_entry(iface, &priv->iflist, list) {
		if (strncmp(iface->ifname, ifname, 16))
			continue;

		if (iface->is_brif && iface->br_ifindex > 0) {
			char master[16] = {0};

			if_indextoname(iface->br_ifindex, master);
			mif = hostmngr_ifname_to_interface(priv, master);
			if (mif)
				return mif->network;
		}

		return iface->network;
	}

	return NULL;
}

int hostmngr_handle_ethport_carrier_off(struct hostmngr_private *priv,
					const char *ifname)
{
	if (!priv || !ifname)
		return -1;

	neigh_set_unreachable(&priv->neigh_q, ifname);
	return 0;
}

int hostmngr_handle_ethport_carrier_on(struct hostmngr_private *priv,
				       const char *ifname)
{
	if (!priv || !ifname)
		return -1;

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

void heartbeat_timer_cb(atimer_t *t)
{
	struct hostmngr_private *p = container_of(t, struct hostmngr_private, hbtimer);

	UNUSED(p);
	switch (signal_pending) {
	case SIGUSR2:
		signal_pending = 0;
		err("%s", "Received SIGUSR2\n");
		break;
	default:
		break;
	}

	timer_set(t, 1000);
}

static void hostmngr_history_sync(atimer_t *t)
{
	struct hostmngr_private *p = container_of(t, struct hostmngr_private, history_sync_timer);

	neigh_history_sync(p);
	timer_set(t, NEIGH_HISTORY_SYNC_INTERVAL);
}

static void hostmngr_periodic_refresh(atimer_t *t)
{
	struct hostmngr_private *p = container_of(t, struct hostmngr_private, refreshtimer);

	hostmngr_get_interface_network(p);
	hostmngr_get_1905_aladdr(p);
	hostmngr_get_1905_topology(p);
	timer_set(t, 11000);
}

int hostmngr_init_private(struct hostmngr_private *priv, void *useropts)
{
	int ret;

#if 0
	priv->use_ieee1905 = false;
	priv->i1905 = lookup_object(priv->bus, IEEE1905_OBJECT);
	if (priv->i1905 != OBJECT_INVALID) {
		dbg("%s: Successfully got %s object (id = %u)\n",
			__func__, IEEE1905_OBJECT, priv->i1905);
		priv->i1905_topology = lookup_object(priv->bus, IEEE1905_TOPOLOGY_OBJECT);
		if (priv->i1905_topology != OBJECT_INVALID) {
			dbg("%s: Successfully got %s object (id = %u)\n",
				__func__, IEEE1905_TOPOLOGY_OBJECT, priv->i1905_topology);
			priv->use_ieee1905 = true;
		}
	} else {
		dbg("%s: Failed to get %s object\n", __func__, IEEE1905_OBJECT);
	}
#endif
	INIT_LIST_HEAD(&priv->hostlist);
	INIT_LIST_HEAD(&priv->iflist);
	ret = hostmngr_enumerate_interfaces(priv);
	if (!ret)
		hostmngr_print_interfaces(priv);

	hostmngr_include_interface(priv, DEFAULT_LAN_IFNAME);
	hostmngr_exclude_interfaces(priv, useropts);
	hostmngr_init_interface_private(priv);
	hostmngr_get_interface_network(priv);
	hostmngr_get_1905_aladdr(priv);

	neigh_queue_init(&priv->neigh_q);

	if (priv->cfg.history && priv->cfg.history_persist) {
		neigh_history_load_from_json_file(priv, priv->cfg.history_file);
		hosts_load_from_json_file(priv, priv->cfg.hosts_file);
	}

	hostmngr_get_interface_neighbors(priv);
	hostmngr_get_neighbors_hostname(priv);

	hostmngr_register_notifier_file(priv, "/var/dhcp.leases");

	nfct_get_entries_nolo(priv);

	return ret;
}

void hostmngr_exit_private(struct hostmngr_private *priv)
{

	neigh_queue_free(&priv->neigh_q);

	if (priv->cfg.history) {
		neigh_history_store_to_json_file(priv, priv->cfg.history_file);
		hosts_store_to_json_file(priv, priv->cfg.hosts_file);
	}

	neigh_flowtable_flush(priv);
	neigh_iptable_flush(priv);
	hostmngr_free_interfaces(priv);

	neigh_history_free(&priv->neigh_q);
	hostlist_flush(priv);

	return;
}

int hostmngr_init(void **priv, void *opts)
{
	struct hostmngr_useropts *uopts = (struct hostmngr_useropts *)opts;
	struct hostmngr_private *p;
	int ret;


	set_sighandler(SIGUSR2, hostmngr_sighandler);
	set_sighandler(SIGPIPE, SIG_IGN);
	/* and SIGINT/SIGTERM handlers from uloop cancel uloop */

	*priv = NULL;
	p = calloc(1, sizeof(struct hostmngr_private));
	if (!p)
		return -1;


	uloop_init();
	p->bus = ubus_connect(uopts->ubus_sockpath);
	if (!p->bus) {
		err("Failed to connect to ubus\n");
		goto out_err;
	}
	ubus_add_uloop(p->bus);

	hostmngr_config_defaults(&p->cfg);
	ret = hostmngr_reconfig(&p->cfg, uopts->confpath, uopts->conffile);
	if (ret) {
		err("Invalid config\n");
		goto out_cfg;
	}
	hostmngr_dump_config(&p->cfg);

	ret = hostmngr_init_private(p, opts);
	if (ret)
		goto out_cfg;

	ret = hostmngr_publish_object(p, uopts->objname);
	if (ret)
		goto out_priv;

	ret = hostmngr_register_local_events(p);
	if (ret)
		goto out_priv;

	ret = hostmngr_register_nlevents(p);
	if (ret)
		goto out_priv;

	timer_init(&p->hbtimer, heartbeat_timer_cb);
	timer_init(&p->refreshtimer, hostmngr_periodic_refresh);
	timer_init(&p->history_sync_timer, hostmngr_history_sync);

	timer_set(&p->hbtimer, 1000);
	timer_set(&p->refreshtimer, 2000);
	timer_set(&p->history_sync_timer, NEIGH_HISTORY_SYNC_INTERVAL);

	*priv = p;
	return 0;

out_priv:
	hostmngr_exit_private(p);
out_cfg:
	hostmngr_config_free(&p->cfg);
	ubus_free(p->bus);
out_err:
	uloop_done();
	free(p);
	return -1;
}

void hostmngr_run(void *handle)
{
	UNUSED(handle);

	uloop_run();
}

int hostmngr_exit(void *handle)
{
	struct hostmngr_private *priv = (struct hostmngr_private *)handle;

	if (!priv)
		return 0;

	timer_del(&priv->hbtimer);
	timer_del(&priv->refreshtimer);
	timer_del(&priv->history_sync_timer);

	nfct_cleanup();

	hostmngr_unregister_local_events(priv);
	hostmngr_unregister_nlevents(priv);

	hostmngr_remove_object(priv);
	hostmngr_exit_private(priv);
	hostmngr_config_free(&priv->cfg);

	ubus_free(priv->bus);
	uloop_done();
	free(priv);
	dbg("hostmngr_exit\n");
	return 0;
}

int hostmngr_main(void *useropts)
{
	struct hostmngr_useropts *opts = (struct hostmngr_useropts *)useropts;
	void *ctx;
	int ret;


	if (opts->daemonize)
		do_daemonize(opts->pidfile);

	start_logging(opts);

	ret = hostmngr_init(&ctx, opts);
	if (ret) {
		err("%s : Failed to init.\n", HOSTS_OBJECT);
		return -1;
	}

	hostmngr_run(ctx);
	hostmngr_exit(ctx);

	stop_logging();

	return 0;
}
