/*
 * $Id: linux.c,v 1.5 2009/02/03 12:51:50 c4chris Exp $
 *
 * Christian Iseli
 * Ludwig Institute for Cancer Research
 * UNIL - BEP
 * CH-1015 Lausanne
 * Switzerland
 *
 */
#include "os.h"

#ifdef USE_LINUX

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <sys/ioctl.h>
#include <netpacket/packet.h>
#include <net/ethernet.h>
#include <net/if.h>
#include <net/if_arp.h>
#include <netinet/in.h>
#include <errno.h>

#include "nfswatch.h"
#include "externs.h"

static void
linux_get_def_dev(char **device)
{
	int n, s;
	struct ifreq *ifrp;
	struct ifconf ifc;
	char buf[BUFSIZ];

	/*
	 * Grab a socket.
	 */
	if ((s = socket(AF_INET, SOCK_DGRAM, 0)) < 0) {
		error("socket");
		finish(-1);
	}

	ifc.ifc_buf = buf;
	ifc.ifc_len = sizeof(buf);

	/*
	 * See what devices we've got.
	 */
	if (ioctl(s, SIOCGIFCONF, (char *) &ifc) < 0) {
		error("ioctl: SIOCGIFCONF");
		finish(-1);
	}

	/*
	 * Take the first device we encounter.
	 */
	ifrp = ifc.ifc_req;
	for (n = ifc.ifc_len/sizeof(struct ifreq); n > 0; n--,ifrp++) {
		/*
		 * Skip the loopback interface.
		 */
		if (strcmp(ifrp->ifr_name, "lo") == 0)
			continue;

		*device = strdup(ifrp->ifr_name);
		break;
	}

	(void) close(s);
}

/*
 * setup_linux_dev - set up the network interface tap.
 */
int
setup_linux_dev(char **device, linux_socket_handle_p_t ls)
{
	char errbuf[PCAP_ERRBUF_SIZE];

	/*
	 * If the interface device was not specified,
	 * get the default one.
	 */
	if (device != NULL && *device == NULL) {
		linux_get_def_dev(device);
		if (*device == NULL) {
			error("linux: couldn't determine default device");
			finish(-1);
		}
	}
	errbuf[0] = 0;
	if (device == NULL) {
		ls->pcap = pcap_open_live(NULL, 65536, 0, 10, errbuf);
		ls->device = "any";
	} else {
		ls->pcap = pcap_open_live(*device, 65536, 1, 10, errbuf);
		ls->device = *device;
	}
	if (ls->pcap == NULL) {
		fprintf(stderr, "Failed to open device: %s\n", errbuf);
		finish(-1);
	}
	if (errbuf[0] != 0)
		fprintf(stderr, "Warning: %s\n", errbuf);
	ls->s = pcap_get_selectable_fd(ls->pcap);
	if (ls->s == -1) {
		fprintf(stderr, "Failed to get a selectable file descriptor\n");
		finish(-1);
	}

	/* Determine link type.  */
	ls->linktype = pcap_datalink(ls->pcap);
	ls->offset = 2;

	/* Grab a big enough buffer.  */
	ls->bufsize = 65536;
	ls->buffer = (u_char *)malloc(ls->bufsize + ls->offset);
	if (ls->buffer == NULL) {
		perror("malloc");
		finish(-1);
	}

	return(ls->s);
}

int
linux_read_packet(linux_socket_handle_p_t ls)
{
	u_char	*bp;
	int	res;
	struct pcap_pkthdr *hdr;
	const u_char *data;

	/* Receive a single packet from the kernel */
	bp = ls->buffer + ls->offset;
	res = pcap_next_ex(ls->pcap, &hdr, &data);
	if (res == -1) {
		pcap_perror(ls->pcap, "Error reading packet: ");
		return -1;
	}
	if (res <= 0)
		return 0;
	memcpy(bp, data, hdr->caplen);
	ls->len = hdr->caplen;
	ls->ts = hdr->ts;
	ls->bp = bp;

	return 1;
}
#endif /* USE_LINUX */
