/*
 * nicutils.c
 *
 *  Created on: 16 Aug 2012
 *      Author: daniel
 */

#include "nicutils.h"

#include <stdbool.h>
#include <string.h>
#include <stdlib.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <net/if.h>
#include <netinet/if_ether.h>
#include <netdb.h>
#include <netinet/in.h>
#include <linux/netlink.h>
#include <linux/rtnetlink.h>
#include <netinet/ether.h>
#include <stdio.h>
#include <sys/ioctl.h>
#include <sys/types.h>
#include <string.h>
#include <stdlib.h>
#include <unistd.h>

#define BUFSIZE 8192

static int netlink_createsock()
{
	return socket(PF_NETLINK, SOCK_DGRAM, NETLINK_ROUTE);
}

static int netlink_sendmessage(int sock, struct nlmsghdr* msg)
{
	return send(sock, msg, msg->nlmsg_len, 0);
}

static int netlink_readsock(int sockFd, char *bufPtr, int seqNum, int pId, int buffsize)
{

	struct nlmsghdr *nlHdr;
	int readLen = 0, msgLen = 0;

	do {
		/* Receive response from the kernel */
		if ((readLen = recv(sockFd, bufPtr, buffsize - msgLen, 0)) < 0) {
			return -1;
		}

		nlHdr = (struct nlmsghdr *) bufPtr;

		/* Check if the header is valid */
		if ((NLMSG_OK(nlHdr, readLen) == 0) || (nlHdr->nlmsg_type == NLMSG_ERROR)) {
			return -1;
		}

		/* Check if the its the last message */
		if (nlHdr->nlmsg_type == NLMSG_DONE) {
			break;
		}

		else {
			/* Else move the pointer to buffer appropriately */
			bufPtr += readLen;
			msgLen += readLen;
		}

		/* Check if its a multi part message */
		if ((nlHdr->nlmsg_flags & NLM_F_MULTI) == 0) {
			break;
		}
	} while ((nlHdr->nlmsg_seq != seqNum) || (nlHdr->nlmsg_pid != pId));

	return msgLen;
}

static bool route_finddefault(struct nlmsghdr *nlHdr, char* ifname)
{

	// unwrap the data
	struct rtmsg* rtMsg = (struct rtmsg *) NLMSG_DATA(nlHdr);

	/* If the route is not for AF_INET or does not belong to main routing table
	 then return. */
	if ((rtMsg->rtm_family != AF_INET) || (rtMsg->rtm_table != RT_TABLE_MAIN)) {
		return NULL;
	}

	/* get the rtattr field */
	struct rtattr* rtAttr = (struct rtattr *) RTM_RTA(rtMsg);

	int rtLen = RTM_PAYLOAD(nlHdr);

	// the idea here is that this doesn't get touched if the
	// dst is 0 as the response doesn't contain an RTA_DST attribute
	u_int dst = 0;

	// bash through the attributes
	for (; RTA_OK(rtAttr, rtLen); rtAttr = RTA_NEXT(rtAttr, rtLen)) {
		switch (rtAttr->rta_type) {
		case RTA_OIF:
			if_indextoname(*(int *) RTA_DATA(rtAttr), ifname);
			break;
		case RTA_DST:
			dst = *((u_int*) RTA_DATA(rtAttr));
			break;
		}
		// We already know we don't want this route, so get out of here
		if (dst != 0) {
			break;
		}
	}

	return dst == 0;
}

static char* route_defaultrouteint()
{

	int sock, len, msgSeq = 0;

	/* Create Socket */
	if ((sock = netlink_createsock()) < 0) {
		goto exit;
	}

	/* Initialize the buffer */
	char* msgBuf = calloc(BUFSIZE, 1);
	if (msgBuf == NULL) {
		goto exit;
	}

	/* point the header and the msg structure pointers into the buffer */
	struct nlmsghdr* nlMsg = (struct nlmsghdr *) msgBuf;
	struct rtmsg* rtMsg = (struct rtmsg *) NLMSG_DATA(nlMsg);

	/* Fill in the nlmsg header*/
	nlMsg->nlmsg_len = NLMSG_LENGTH(sizeof(struct rtmsg)); // Length of message.
	nlMsg->nlmsg_type = RTM_GETROUTE; // Get the routes from kernel routing table .
	nlMsg->nlmsg_flags = NLM_F_DUMP | NLM_F_REQUEST; // The message is a request for dump.
	nlMsg->nlmsg_seq = msgSeq++; // Sequence of the message packet.
	nlMsg->nlmsg_pid = getpid(); // PID of process sending the request.

	/* Send the request */
	if (netlink_sendmessage(sock, nlMsg) < 0) {
		goto exit;
	}

	/* Read the response */
	if ((len = netlink_readsock(sock, msgBuf, msgSeq, getpid(), BUFSIZE)) < 0) {
		goto exit;
	}

	static char ifname[256];
	for (; NLMSG_OK(nlMsg,len); nlMsg = NLMSG_NEXT(nlMsg,len)) {
		if (route_finddefault(nlMsg, ifname)) {
			break;
		}
	}

exit:

	if (sock >= 0) {
		close(sock);
	}

	if (msgBuf != NULL) {
		free(msgBuf);
	}

	return ifname;
}

static bool nicutils_info(char* intefacename, nicinfo* result)
{

	bool ret = false;

	// get a socket to do the ioctl requests with
	int sock = socket(PF_INET, SOCK_DGRAM, 0);
	if (sock < 0) {
		goto exit;
	}

	// setup an ifconf struct, allocate a buffer for the data and get
	// all of the interfaces attached to the system that are up
	struct ifconf interfaces;
	interfaces.ifc_len = sizeof(struct ifreq) * 32; // enough space for 32 interfaces.. more than enough
	interfaces.ifc_buf = malloc(interfaces.ifc_len);
	if (ioctl(sock, SIOCGIFCONF, &interfaces) < 0) {
		goto exit;
	}

	struct ifreq *ifr = interfaces.ifc_req;
	int i;
	for (i = 0; i < interfaces.ifc_len / sizeof(struct ifreq); i++) {
		// is this the interface we want?
		if (strcmp(ifr[i].ifr_name, intefacename) != 0) {
			continue;        // skip over this one
		}

		// get the address
		struct sockaddr* addr = &(ifr[i].ifr_addr);

		// handle ipv4 and ipv6 addresses..
		switch (addr->sa_family) {
		case AF_INET:
			inet_ntop(AF_INET, &(((struct sockaddr_in *) addr)->sin_addr), result->ip, INET6_ADDRSTRLEN);
			break;

		case AF_INET6:
			inet_ntop(AF_INET6, &(((struct sockaddr_in6 *) addr)->sin6_addr), result->ip, INET6_ADDRSTRLEN);
			break;

		default:
			snprintf(result->ip, sizeof(result->ip), "shouldneverhappen");
			break;
		}

		// get the mac address
		if (ioctl(sock, SIOCGIFHWADDR, &ifr[i]) < 0) {
			goto exit;
		}

		// turn it into a nice string
		snprintf(result->mac, sizeof(result->mac), "%02x:%02x:%02x:%02x:%02x:%02x",
		         (unsigned char) ifr[i].ifr_hwaddr.sa_data[0],
		         (unsigned char) ifr[i].ifr_hwaddr.sa_data[1],
		         (unsigned char) ifr[i].ifr_hwaddr.sa_data[2],
		         (unsigned char) ifr[i].ifr_hwaddr.sa_data[3],
		         (unsigned char) ifr[i].ifr_hwaddr.sa_data[4],
		         (unsigned char) ifr[i].ifr_hwaddr.sa_data[5]);

		ret = true;
		break; // break out of the loop
	}

exit:
	if (interfaces.ifc_buf != NULL) {
		free(interfaces.ifc_buf);
	}

	return ret;
}

bool nicutils_infofordefaultroute(nicinfo* result)
{

	char* defaultroute = route_defaultrouteint();
	if (defaultroute == NULL) {
		return false;
	}

	return nicutils_info(defaultroute, result);
}