/*==============================================================================

	I F . C

	This module hides all the network interface junk from the other modules.

	if.c,v 1.2 1998/10/12 02:13:16 rneswold Exp
==============================================================================*/
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <limits.h>
#include <err.h>
#include <sys/param.h>
#include <sys/types.h>
#include <sys/ioctl.h>
#include <sys/socket.h>
#include <net/if.h>
#if (__FreeBSD_version >= 300003)
#include <net/if_var.h>
#include <net/if_types.h>
#endif
#include <fcntl.h>
#include <kvm.h>
#include <nlist.h>
#include <assert.h>
#include "wmnet.h"

#define	IF_STEP	10

/*
	Local data types...
*/
typedef struct {
	unsigned long ifNetAddr;

	unsigned long flags;

	unsigned current;
	unsigned long xmt[G_WIDTH], xmtLast;
	unsigned long rcv[G_WIDTH], rcvLast;
} IfData;

/*
	Local prototypes...
*/
static int addIfData(unsigned long);
static struct ifnet const* dereference(unsigned long);
static void ifTerm(void);

/*
	Local data...
*/
static kvm_t* kd = 0;
static unsigned long root = 0;

static int total = 0;
static int size = 0;
static IfData* ifData = 0;

/*------------------------------------------------------------------------------
	addIfData

	This function adds a network interface to the list. If the list is
	out of space, more space is allocated. If the interface already
	exists in the list, we don't do anything.
------------------------------------------------------------------------------*/
static int addIfData(unsigned long theAddr)
{
	struct ifnet const* const theData = dereference(theAddr);

	assert(total <= size);

	/* Now read in the data so we can get to some of its goodies. */

	if (0 != theData) {
		IfData* ptr = 0;
		int ii;

		/* First check to see if we have enough space in the list. If
		   we don't, we'll have to add more space. */

		if (total == size) {
			IfData* const newData =
				(IfData*) realloc(ifData,
								  (size += IF_STEP) * sizeof(IfData));

			if (!newData) {
				size -= IF_STEP;
				fprintf(stderr, "wmnet: Warning -- low memory; "
						"ignoring %s interface\n", theData->if_name);
				return 0;
			}

			/* Good. we got more space. */

			ifData = newData;
		}

		assert(0 != ifData);
		assert(total < size);

		/* If we reached here, then 'total' points to the next
		   bucket. Initialize the new bucket. XXX: This would be a
		   good spot to insert-sort the new record. As it stands, the
		   list builds up by the order that the interfaces were
		   bootstrapped. */

		ptr = ifData + total;

		ptr->ifNetAddr = theAddr;
		for (ii = 0; ii < G_WIDTH; ++ii)
			ptr->rcv[ii] = ptr->xmt[ii] = 0;
		ptr->rcvLast = theData->if_ibytes;
		ptr->xmtLast = theData->if_obytes;
		ptr->current = 0;
		ptr->flags = 0;

#ifndef NDEBUG
		printf("Added '%.*s%d' to list.\n", IFNAMSIZ, theData->if_name,
			   theData->if_unit);
#endif

		// Bump the total.

		++total;
	} else
		fprintf(stderr, "Couldn't read interface information\n");
	return 1;
}

/*------------------------------------------------------------------------------
	dereference

	Takes the kernel memory "pointer" and copies its pointed-to data
	into a local buffer. The only type of "pointer" we're using is a
	struct ifnet*, so this function only returns this type of data.

	Passing in 0 will reset the address cache and force the function
	to reload the data.
------------------------------------------------------------------------------*/
static struct ifnet const* dereference(unsigned long a)
{
	static unsigned long c = 0;
	static struct ifnet d;
	static char name[IFNAMSIZ];

	/* If we are passed a NULL, the caller wants us to stop caching
	   the current interface. */

	if (!a) {
		c = 0;
		return 0;
	}

	/* If the requested address is the same that has been cached, just
	   return the data. */

	else if (a == c)
		return &d;
	else if (sizeof(d) == kvm_read(kd, a, &d, sizeof(d))) {

		/* We've read the structure's data, but the 'name' field still
		   points to kernel memory. We transfer the name to a local
		   buffer, and then modify the pointer to point to our
		   buffer. */

		ssize_t const n =  kvm_read(kd, (unsigned long) d.if_name, name,
									sizeof(name));

		if (sizeof(name) == n) {
			name[IFNAMSIZ - 1] = '\0';
			d.if_name = name;
#ifndef NDEBUG

			/* These are other pointer fields that we shouldn't need
			   to look at. While debugging, set these to NULL to trap
			   any attempts. */

			d.if_softc = 0;
#if (__FreeBSD_version >= 300003)
			d.if_addrhead.tqh_first = 0;
#else
			d.if_addrlist = 0;
#endif
			d.if_bpf = 0;
			d.if_linkmib = 0;
			d.if_poll_slowq = 0;
#endif
			c = a;
			return &d;
		} else
			return 0;
	} else
		return 0;
}

/*------------------------------------------------------------------------------
	ifGetData
------------------------------------------------------------------------------*/
int ifGetData(unsigned ifIdx, unsigned n, unsigned long* x, unsigned long* r)
{
	assert(x && r);

	if (ifIdx < total && n < G_WIDTH) {
		IfData const* const d = ifData + ifIdx;

		*x = d->xmt[(d->current + n) % G_WIDTH];
		*r = d->rcv[(d->current + n) % G_WIDTH];

		return 1;
	} else
		return 0;
}

/*------------------------------------------------------------------------------
	ifInit

	Initialize the module. This function should be called before any
	other functions, in this module, are called.
------------------------------------------------------------------------------*/
int ifInit(void)
{
	char errBuf[_POSIX2_LINE_MAX];

	/* First try to open the kernel image. If we can't, the rest of
	   the program is essentially useless. */

	if (0 != (kd = kvm_openfiles(0, 0, 0, O_RDONLY, errBuf))) {
		static struct nlist nl[] = {
			{ "_ifnet" },
			{ "" }
		};

		/* Try to pull the address for the global kernel variable,
		   ifnet. This variable is the root of the singly-linked list
		   of network interfaces. */

		if (0 == kvm_nlist(kd, nl)) {
			ssize_t const n = kvm_read(kd, nl[0].n_value, &root, sizeof(root));

			/* We'll go ahead and make one dereference. We never
			   really want this variable. We want what it points to. */

			if (sizeof(root) == n) {
				unsigned long current = root;

				while (current) {
					addIfData(current);
#if (__FreeBSD_version >= 300003)
					current = (unsigned long) dereference(current)->if_link.tqe_next;
#else
					current = (unsigned long) dereference(current)->if_next;
#endif
				}

				/* Try to register our termination function. If it
				   returns an error, we essentially ignore it because
				   the OS will clean up our resources. */

				(void) atexit(ifTerm);
				return 1;
			}
		}

		/* If we can't get a symbol list, close the kernel image and
		   return an error. */

		kvm_close(kd);
		kd = 0;
	} else
		fprintf(stderr, "kvm_open: %s\n", errBuf);
	return 0;
}

/*------------------------------------------------------------------------------
	ifIsPromisc
------------------------------------------------------------------------------*/
int ifIsPromisc(unsigned ifIdx)
{
	return ifIdx < total ? (ifData[ifIdx].flags & IFF_PROMISC) : 0;
}

/*------------------------------------------------------------------------------
	ifIsRunning
------------------------------------------------------------------------------*/
int ifIsRunning(unsigned ifIdx)
{
	return ifIdx < total ? (ifData[ifIdx].flags & IFF_RUNNING) : 0;
}

/*------------------------------------------------------------------------------
	ifIsUp
------------------------------------------------------------------------------*/
int ifIsUp(unsigned ifIdx)
{
	return ifIdx < total ? (ifData[ifIdx].flags & IFF_UP) : 0;
}

/*------------------------------------------------------------------------------
	ifName
------------------------------------------------------------------------------*/
char const* ifName(unsigned idx)
{
	if (idx < total) {
		struct ifnet const* const ptr = dereference(ifData[idx].ifNetAddr);

		if (ptr) {
			static char buffer[IFNAMSIZ + 1];

			sprintf(buffer, "%.*s%d", IFNAMSIZ - 1, ptr->if_name, ptr->if_unit);
			return buffer;
		}
	}
	return 0;
}

/*------------------------------------------------------------------------------
	ifSample

	Samples each of the network interfaces and updates the respective
	histories.
------------------------------------------------------------------------------*/
int ifSample(void)
{
	int ii;

	/* Flush the kernel memory cache so that we're guaranteed to get
	   new data during the next dereference(). */

	dereference(0);

	/* Loop through all the network interfaces. Even though we display
	   one interface's statistics, we keep track of all of them. */

	for (ii = 0; ii < total; ++ii) {
		IfData* const d = ifData + ii;
		struct ifnet const* const ptr = dereference(d->ifNetAddr);

		if (!ptr) {
			fprintf(stderr, "couldn't sample interface.\n");
			return 0;
		}

		d->flags = ptr->if_flags;

		/* Save the new delta for the transmit channel. */

		d->xmt[d->current] = ptr->if_obytes - d->xmtLast;
		d->xmtLast = ptr->if_obytes;

		/* Save the new delta for the receive channel. */

		d->rcv[d->current] = ptr->if_ibytes - d->rcvLast;
		d->rcvLast = ptr->if_ibytes;

		/* Prep the index for the next location to be written. */

		d->current = (d->current + 1) % G_WIDTH;
	}
	return 1;
}

/*------------------------------------------------------------------------------
	ifTerm

	This function will be called once when the application is ready to exit
------------------------------------------------------------------------------*/
static void ifTerm(void)
{
#ifndef NDEBUG
	printf("Cleaning up resources.\n");
#endif

	/* If the kernel image is open, close it. */

	if (0 != kd) {
		kvm_close(kd);
		kd = 0;
	}

	/* Now free up any memory we're using. */

	if (ifData) {
		free(ifData);
		ifData = 0;
	}
}

/*------------------------------------------------------------------------------
	ifTotal
------------------------------------------------------------------------------*/
int ifTotal(void)
{
	return total;
}
