/**
 * @file linux/rt.c  Routing table code for Linux. See rtnetlink(7)
 *
 * Copyright (C) 2010 Creytiv.com
 */
#include <string.h>
#include <unistd.h>
#define __USE_POSIX 1  /**< Use POSIX flag */
#include <netdb.h>
#define __USE_MISC 1
#include <net/if.h>
#undef __STRICT_ANSI__
#include <linux/types.h>
#include <linux/netlink.h>
#include <linux/rtnetlink.h>
#include <re_types.h>
#include <re_mbuf.h>
#include <re_fmt.h>
#include <re_sa.h>
#include <re_net.h>


#define DEBUG_MODULE "linuxrt"
#define DEBUG_LEVEL 5
#include <re_dbg.h>


/* Override macros to avoid casting alignment warning */
#undef RTM_RTA
#define RTM_RTA(r) (void *)(((char *)(r)) + NLMSG_ALIGN(sizeof(struct rtmsg)))
#undef RTA_NEXT
#define RTA_NEXT(rta, len) ((len) -= RTA_ALIGN((rta)->rta_len), \
		(void *)(((char *)(rta)) + RTA_ALIGN((rta)->rta_len)))
#undef NLMSG_NEXT
#define NLMSG_NEXT(nlh,len)	 ((len) -= NLMSG_ALIGN((nlh)->nlmsg_len), \
		  (void*)(((char*)(nlh)) + NLMSG_ALIGN((nlh)->nlmsg_len)))


enum {BUFSIZE = 8192};


/** Defines a network route */
struct net_rt {
	char ifname[IFNAMSIZ];  /**< Interface name                 */
	struct sa dst;          /**< Destination IP address/network */
	int dstlen;             /**< Prefix length of destination   */
	struct sa gw;           /**< Gateway IP address             */
};


static int read_sock(int fd, uint8_t *buf, size_t size, int seq, int pid)
{
	struct nlmsghdr *nlhdr;
	int n = 0, len = 0;

	do {
		/* Receive response from the kernel */
		if ((n = recv(fd, buf, size - len, 0)) < 0) {
			DEBUG_WARNING("SOCK READ: %s\n", strerror(errno));
			return -1;
		}
		nlhdr = (struct nlmsghdr *)(void *)buf;

		/* Check if the header is valid */
		if (0 == NLMSG_OK(nlhdr, (uint32_t)n) ||
		    NLMSG_ERROR == nlhdr->nlmsg_type) {
			DEBUG_WARNING("Error in received packet\n");
			return -1;
		}

		/* Check if the its the last message */
		if (NLMSG_DONE == nlhdr->nlmsg_type) {
			break;
		}
		else{
			/* Else move the pointer to buffer appropriately */
			buf += n;
			len += n;
		}

		/* Check if its a multi part message */
		if (0 == (nlhdr->nlmsg_flags & NLM_F_MULTI)) {
			/* return if its not */
			break;
		}
	} while (nlhdr->nlmsg_seq != (uint32_t)seq ||
		 nlhdr->nlmsg_pid != (uint32_t)pid);

	return len;
}


/* Parse one route */
static int rt_parse(const struct nlmsghdr *nlhdr, struct net_rt *rt)
{
	struct rtmsg *rtmsg;
	struct rtattr *rtattr;
	int len;

	rtmsg = (struct rtmsg *)NLMSG_DATA(nlhdr);

	/* If the route does not belong to main routing table then return. */
	if (RT_TABLE_MAIN != rtmsg->rtm_table)
		return EINVAL;

	sa_init(&rt->dst, rtmsg->rtm_family);
	rt->dstlen = rtmsg->rtm_dst_len;

	/* get the rtattr field */
	rtattr = (struct rtattr *)RTM_RTA(rtmsg);
	len = RTM_PAYLOAD(nlhdr);
	for (;RTA_OK(rtattr, len); rtattr = RTA_NEXT(rtattr, len)) {

		switch (rtattr->rta_type) {

		case RTA_OIF:
			if_indextoname(*(int *)RTA_DATA(rtattr), rt->ifname);
			break;

		case RTA_GATEWAY:
			switch (rtmsg->rtm_family) {

			case AF_INET:
				sa_init(&rt->gw, AF_INET);
				rt->gw.u.in.sin_addr.s_addr
					= *(uint32_t *)RTA_DATA(rtattr);
				break;

#ifdef HAVE_INET6
			case AF_INET6:
				sa_set_in6(&rt->gw, RTA_DATA(rtattr), 0);
				break;
#endif

			default:
				DEBUG_WARNING("RTA_DST: unknown family %d\n",
					      rtmsg->rtm_family);
				break;
			}
			break;

#if 0
		case RTA_PREFSRC:
			rt->srcaddr = *(uint32_t *)RTA_DATA(rtattr);
			break;
#endif

		case RTA_DST:
			switch (rtmsg->rtm_family) {

			case AF_INET:
				sa_init(&rt->dst, AF_INET);
				rt->dst.u.in.sin_addr.s_addr
					= *(uint32_t *)RTA_DATA(rtattr);
				break;

#ifdef HAVE_INET6
			case AF_INET6:
				sa_set_in6(&rt->dst, RTA_DATA(rtattr), 0);
				break;
#endif

			default:
				DEBUG_WARNING("RTA_DST: unknown family %d\n",
					      rtmsg->rtm_family);
				break;
			}
			break;
		}
	}

	return 0;
}


/**
 * List all entries in the routing table
 *
 * @param rth Route entry handler
 * @param arg Handler argument
 *
 * @return 0 if success, otherwise errorcode
 */
int net_rt_list(net_rt_h *rth, void *arg)
{
	union {
		uint8_t buf[BUFSIZE];
		struct nlmsghdr msg[1];
	} u;
	struct nlmsghdr *nlmsg;
	int sock, len, seq = 0, err = 0;

	if (!rth)
		return EINVAL;

	/* Create Socket */
	if ((sock = socket(PF_NETLINK, SOCK_DGRAM, NETLINK_ROUTE)) < 0) {
		DEBUG_WARNING("list: socket(): (%s)\n", strerror(errno));
		return errno;
	}

	/* Initialize the buffer */
	memset(u.buf, 0, sizeof(u.buf));

	/* point the header and the msg structure pointers into the buffer */
	nlmsg = u.msg;

	/* Fill in the nlmsg header*/
	nlmsg->nlmsg_len   = NLMSG_LENGTH(sizeof(struct rtmsg));
	nlmsg->nlmsg_type  = RTM_GETROUTE;
	nlmsg->nlmsg_flags = NLM_F_DUMP | NLM_F_REQUEST;
	nlmsg->nlmsg_seq   = seq++;
	nlmsg->nlmsg_pid   = getpid();

	/* Send the request */
	if (send(sock, nlmsg, nlmsg->nlmsg_len, 0) < 0) {
		err = errno;
		DEBUG_WARNING("list: write to socket failed (%s)\n",
			      strerror(err));
		goto out;
	}

	/* Read the response */
	if ((len = read_sock(sock, u.buf, sizeof(u.buf), seq, getpid())) < 0) {
		err = errno;
		DEBUG_WARNING("list: read from socket failed (%s)\n",
			      strerror(err));
		goto out;
	}

	/* Parse and print the response */
	for (;NLMSG_OK(nlmsg,(uint32_t)len);nlmsg = NLMSG_NEXT(nlmsg,len)) {
		struct net_rt rt;

		memset(&rt, 0, sizeof(struct net_rt));
		if (0 != rt_parse(nlmsg, &rt))
			continue;

#ifdef HAVE_INET6
		if (AF_INET6 == sa_af(&rt.dst)
		    && IN6_IS_ADDR_UNSPECIFIED(&rt.dst.u.in6.sin6_addr))
			continue;
#endif

		if (rth(rt.ifname, &rt.dst, rt.dstlen, &rt.gw, arg))
			break;
	}

 out:
	(void)close(sock);

	return err;
}
