/* iftable - table of network interfaces
 *
 * (C) 2004 by Astaro AG, written by Harald Welte <hwelte@astaro.com>
 * (C) 2008 by Pablo Neira Ayuso <pablo@netfilter.org>
 *
 * This software is Free Software and licensed under GNU GPLv2+.
 */

/* IFINDEX handling */

#include <unistd.h>
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <sys/types.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <errno.h>
#include <assert.h>

#include <linux/netdevice.h>

#include <libnfnetlink/libnfnetlink.h>
#include "rtnl.h"
#include "linux_list.h"

struct ifindex_node {
	struct list_head head;

	uint32_t	index;
	uint32_t	type;
	uint32_t	alen;
	uint32_t	flags;
	char		addr[8];
	char		name[16];
};

struct nlif_handle {
	struct list_head ifindex_hash[16];
	struct rtnl_handle *rtnl_handle;
	struct rtnl_handler ifadd_handler;
	struct rtnl_handler ifdel_handler;
};

/* iftable_add - Add/Update an entry to/in the interface table
 * @n:		netlink message header of a RTM_NEWLINK message
 * @arg:	not used
 *
 * This function adds/updates an entry in the intrface table.
 * Returns -1 on error, 1 on success.
 */
static int iftable_add(struct nlmsghdr *n, void *arg)
{
	unsigned int hash, found = 0;
	struct ifinfomsg *ifi_msg = NLMSG_DATA(n);
	struct ifindex_node *this;
	struct rtattr *cb[IFLA_MAX+1];
	struct nlif_handle *h = arg;

	if (n->nlmsg_type != RTM_NEWLINK)
		return -1;

	if (n->nlmsg_len < NLMSG_LENGTH(sizeof(ifi_msg)))
		return -1;

	rtnl_parse_rtattr(cb, IFLA_MAX, IFLA_RTA(ifi_msg), IFLA_PAYLOAD(n));

	if (!cb[IFLA_IFNAME])
		return -1;

	hash = ifi_msg->ifi_index & 0xF;
	list_for_each_entry(this, &h->ifindex_hash[hash], head) {
		if (this->index == ifi_msg->ifi_index) {
			found = 1;
			break;
		}
	}

	if (!found) {
		this = malloc(sizeof(*this));
		if (!this)
			return -1;

		this->index = ifi_msg->ifi_index;
	}

	this->type = ifi_msg->ifi_type;
	this->flags = ifi_msg->ifi_flags;
	if (cb[IFLA_ADDRESS]) {
		unsigned int alen;
		this->alen = alen = RTA_PAYLOAD(cb[IFLA_ADDRESS]);
		if (alen > sizeof(this->addr))
			alen = sizeof(this->addr);
		memcpy(this->addr, RTA_DATA(cb[IFLA_ADDRESS]), alen);
	} else {
		this->alen = 0;
		memset(this->addr, 0, sizeof(this->addr));
	}
	strcpy(this->name, RTA_DATA(cb[IFLA_IFNAME]));

	if (!found)
		list_add(&this->head, &h->ifindex_hash[hash]);

	return 1;
}

/* iftable_del - Delete an entry from the interface table
 * @n:		netlink message header of a RTM_DELLINK nlmsg
 * @arg:	not used
 *
 * Delete an entry from the interface table.  
 * Returns -1 on error, 0 if no matching entry was found or 1 on success.
 */
static int iftable_del(struct nlmsghdr *n, void *arg)
{
	struct ifinfomsg *ifi_msg = NLMSG_DATA(n);
	struct rtattr *cb[IFLA_MAX+1];
	struct nlif_handle *h = arg;
	struct ifindex_node *this, *tmp;
	unsigned int hash;

	if (n->nlmsg_type != RTM_DELLINK)
		return -1;

	if (n->nlmsg_len < NLMSG_LENGTH(sizeof(ifi_msg)))
		return -1;

	rtnl_parse_rtattr(cb, IFLA_MAX, IFLA_RTA(ifi_msg), IFLA_PAYLOAD(n));

	hash = ifi_msg->ifi_index & 0xF;
	list_for_each_entry_safe(this, tmp, &h->ifindex_hash[hash], head) {
		if (this->index == ifi_msg->ifi_index) {
			list_del(&this->head);
			free(this);
			return 1;
		}
	}

	return 0;
}

/** Get the name for an ifindex
 *
 * \param nlif_handle A pointer to a ::nlif_handle created
 * \param index ifindex to be resolved
 * \param name interface name, pass a buffer of IFNAMSIZ size
 * \return -1 on error, 1 on success 
 */
int nlif_index2name(struct nlif_handle *h, 
		    unsigned int index,
		    char *name)
{
	unsigned int hash;
	struct ifindex_node *this;

	assert(h != NULL);
	assert(name != NULL);

	if (index == 0) {
		strcpy(name, "*");
		return 1;
	}

	hash = index & 0xF;
	list_for_each_entry(this, &h->ifindex_hash[hash], head) {
		if (this->index == index) {
			strcpy(name, this->name);
			return 1;
		}
	}

	errno = ENOENT;
	return -1;
}

/** Get the flags for an ifindex
 *
 * \param nlif_handle A pointer to a ::nlif_handle created
 * \param index ifindex to be resolved
 * \param flags pointer to variable used to store the interface flags
 * \return -1 on error, 1 on success 
 */
int nlif_get_ifflags(const struct nlif_handle *h,
		     unsigned int index,
		     unsigned int *flags)
{
	unsigned int hash;
	struct ifindex_node *this;

	assert(h != NULL);
	assert(flags != NULL);

	if (index == 0) {
		errno = ENOENT;
		return -1;
	}

	hash = index & 0xF;
	list_for_each_entry(this, &h->ifindex_hash[hash], head) {
		if (this->index == index) {
			*flags = this->flags;
			return 1;
		}
	}
	errno = ENOENT;
	return -1;
}

/** Initialize interface table
 *
 * Initialize rtnl interface and interface table
 * Call this before any nlif_* function
 *
 * \return file descriptor to netlink socket
 */
struct nlif_handle *nlif_open(void)
{
	int i;
	struct nlif_handle *h;

	h = calloc(1,  sizeof(struct nlif_handle));
	if (h == NULL)
		goto err;

	for (i=0; i<16; i++)
		INIT_LIST_HEAD(&h->ifindex_hash[i]);

	h->ifadd_handler.nlmsg_type = RTM_NEWLINK;
	h->ifadd_handler.handlefn = iftable_add;
	h->ifadd_handler.arg = h;
	h->ifdel_handler.nlmsg_type = RTM_DELLINK;
	h->ifdel_handler.handlefn = iftable_del;
	h->ifdel_handler.arg = h;

	h->rtnl_handle = rtnl_open();
	if (h->rtnl_handle == NULL)
		goto err;

	if (rtnl_handler_register(h->rtnl_handle, &h->ifadd_handler) < 0)
		goto err_close;

	if (rtnl_handler_register(h->rtnl_handle, &h->ifdel_handler) < 0)
		goto err_unregister;

	return h;

err_unregister:
	rtnl_handler_unregister(h->rtnl_handle, &h->ifadd_handler);
err_close:
	rtnl_close(h->rtnl_handle);
	free(h);
err:
	return NULL;
}

/** Destructor of interface table
 *
 * \param nlif_handle A pointer to a ::nlif_handle created 
 * via nlif_open()
 */
void nlif_close(struct nlif_handle *h)
{
	int i;
	struct ifindex_node *this, *tmp;

	assert(h != NULL);

	rtnl_handler_unregister(h->rtnl_handle, &h->ifadd_handler);
	rtnl_handler_unregister(h->rtnl_handle, &h->ifdel_handler);
	rtnl_close(h->rtnl_handle);

	for (i=0; i<16; i++) {
		list_for_each_entry_safe(this, tmp, &h->ifindex_hash[i], head) {
			list_del(&this->head);
			free(this);
		}
	}

	free(h);
	h = NULL; /* bugtrap */
}

/** Receive message from netlink and update interface table
 *
 * \param nlif_handle A pointer to a ::nlif_handle created
 * \return 0 if OK
 */
int nlif_catch(struct nlif_handle *h)
{
	assert(h != NULL);

	if (h->rtnl_handle)
		return rtnl_receive(h->rtnl_handle);

	return -1;
}

static int nlif_catch_multi(struct nlif_handle *h)
{
	assert(h != NULL);

	if (h->rtnl_handle)
		return rtnl_receive_multi(h->rtnl_handle);

	return -1;
}

/** 
 * nlif_query - request a dump of interfaces available in the system
 * @h: pointer to a valid nlif_handler
 */
int nlif_query(struct nlif_handle *h)
{
	assert(h != NULL);

	if (rtnl_dump_type(h->rtnl_handle, RTM_GETLINK) < 0)
		return -1;

	return nlif_catch_multi(h);
}

/** Returns socket descriptor for the netlink socket
 *
 * \param nlif_handle A pointer to a ::nlif_handle created
 * \return The fd or -1 if there's an error
 */
int nlif_fd(struct nlif_handle *h)
{
	assert(h != NULL);

	if (h->rtnl_handle)
		return h->rtnl_handle->rtnl_fd;

	return -1;
}
