#include <stdio.h>
#include <fcntl.h>
#include <errno.h>
#include <string.h>
#include <unistd.h>
#include <net/if.h>
#include <sys/ioctl.h>
#include <linux/if_tun.h>

#include "tfe_utils.h"

#ifndef TUN_PATH
#define TUN_PATH "/dev/net/tun"
#endif

extern void *g_default_logger;

/*
 * IFF_TUN         : TUN device (IP)
 * IFF_TAP         : TAP device (Ethernet)
 * IFF_NO_PI       : Do not provide packet information
 * IFF_MULTI_QUEUE : Create a queue of multiqueue device
 * IFF_ONE_QUEUE   : Create a queue of oniequeue device
 */
int tap_open(const char *eth, int flags)
{
	int sockfd = open(TUN_PATH, O_RDWR);
	if (sockfd == -1)
	{
		TFE_LOG_ERROR(g_default_logger, "unable to open " TUN_PATH ", %s.\n", strerror(errno));
		return -1;
	}

	struct ifreq ifr;
	memset(&ifr, 0, sizeof(ifr));
	ifr.ifr_flags = flags;
	strcpy(ifr.ifr_name, eth);
	if (ioctl(sockfd, TUNSETIFF, &ifr) == -1)
	{
		TFE_LOG_ERROR(g_default_logger, "unable to attach %s, %s.\n", eth, strerror(errno));
		goto error;
	}

	/*
	 * The TUNSETPERSIST ioctl can be used to make the TUN/TAP interface persistent.
	 * In this mode, the interface won't be destroyed when the last process closes the associated /dev/net/tun file descriptor.
	 */
	if (ioctl(sockfd, TUNSETPERSIST, 1) == -1)
	{
		TFE_LOG_ERROR(g_default_logger, "unable to set persist on %s, %s.\n", eth, strerror(errno));
		goto error;
	}

	if (ioctl(sockfd, TUNSETOWNER, 0) == -1)
	{
		TFE_LOG_ERROR(g_default_logger, "unable to set owner on %s, %s.\n", eth, strerror(errno));
		goto error;
	}

	if (ioctl(sockfd, TUNSETGROUP, 0) == -1)
	{
		TFE_LOG_ERROR(g_default_logger, "unable to set group on %s, %s.\n", eth, strerror(errno));
		goto error;
	}

	if (fcntl(sockfd, F_SETFL, fcntl(sockfd, F_GETFL) | O_NONBLOCK) == -1)
	{
		TFE_LOG_ERROR(g_default_logger, "unable to set nonblock on fd: %d, %s.\n", sockfd, strerror(errno));
		return -1;
	}

	return sockfd;

error:
	if (sockfd > 0)
	{
		close(sockfd);
	}

	return -1;
}

void tap_close(int sockfd)
{
	if (sockfd > 0)
	{
		close(sockfd);
	}
}

int tap_up_link(const char *eth)
{
	int sockfd = socket(PF_INET, SOCK_DGRAM, 0);
	if (sockfd == -1)
	{
		TFE_LOG_ERROR(g_default_logger, "unable to create socket, %s.\n", strerror(errno));
		return -1;
	}

	struct ifreq ifr;
	memset(&ifr, 0, sizeof(ifr));
	strcpy(ifr.ifr_name, eth);
	if (ioctl(sockfd, SIOCGIFFLAGS, &ifr) == -1)
	{
		TFE_LOG_ERROR(g_default_logger, "unable to get link status on %s, %s.\n", eth, strerror(errno));
		close(sockfd);
		return -1;
	}

	if ((ifr.ifr_flags & IFF_UP) == 0)
	{
		ifr.ifr_flags |= IFF_UP;
		if (ioctl(sockfd, SIOCSIFFLAGS, &ifr) < 0)
		{
			TFE_LOG_ERROR(g_default_logger, "unable to set link up on %s, %s.\n", eth, strerror(errno));
			close(sockfd);
			return -1;
		}
	}

	close(sockfd);
	return 0;
}

int tap_get_mtu(const char *eth)
{
	int sockfd = socket(PF_INET, SOCK_DGRAM, 0);
	if (sockfd == -1)
	{
		TFE_LOG_ERROR(g_default_logger, "unable to create socket, %s.\n", strerror(errno));
		return -1;
	}

	struct ifreq ifr;
	memset(&ifr, 0, sizeof(ifr));
	strcpy(ifr.ifr_name, eth);
	if (ioctl(sockfd, SIOCGIFMTU, &ifr) < 0)
	{
		TFE_LOG_ERROR(g_default_logger, "unable to get mtu on %s, %s.\n", eth, strerror(errno));
		close(sockfd);
		return -1;
	}

	close(sockfd);
	return ifr.ifr_mtu;
}