
/*
 * Copyright (C) 1996, Jonathan Layes <layes@loran.com>
 *
 * See the file "Copyright" for copyright information
 *
 * Conversion from kerneld msg queue to netlink by Alexey Kuznetsov (960415)
 *
 */

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <syslog.h>
#include <fcntl.h>
#include <string.h>
#include <net/if_arp.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>

#ifdef DEBUG
static void arpd_print(char*, struct arpd_request*);
#else
#define arpd_print(x,y) 
#endif

/* these shouldn't change for ipv4 */
#define ARPD_TRIEWIDTH	256
#define ARPD_TRIEDEPTH	4

union arpd_triedef
{
	union arpd_triedef * hash;
	struct arpd_request * entry;
} arpd_trie;

static void arpd_update (struct arpd_request *);
static void arpd_flush (struct arpd_request *);
static struct arpd_request * arpd_lookup (struct arpd_request *);
void arpd_walk_procfs(struct arpd_request *, void *);
void arpd_walk_flush(struct arpd_request *, void *);
void arpd_walk (void (*fn)(struct arpd_request *, void *), void *);
static struct arpd_request * arpd_find (unsigned long, __u32,
						 struct arpd_request *);

int main(int argc, char **argv)
{
	int status;
	int fd;
	struct arpd_request req;
	struct arpd_request *rep;

#ifndef DEBUG
	if (fork())
		exit(0);

	for (fd=0; fd < OPEN_MAX; fd++)
		close(fd);

	fd = open("/dev/null", O_RDWR);
	if (fd) {
		if (fd != 0)
			dup2(fd, 0);
		if (fd != 1)
			dup2(fd, 1);
		if (fd != 2)
			dup2(fd, 2);
		if (fd > 2)
			close(fd);
	}
	setsid();
#endif

	openlog ("arpd", LOG_PID | LOG_CONS, LOG_DAEMON);

	syslog(LOG_NOTICE, "Initializing, version %s\n", ARPD_VERSION);

	fd = open("/dev/arpd", O_RDWR);
	if (fd < 0) {
		syslog(LOG_CRIT, "cannot open /dev/arpd: %m");
		exit(-1);
	}

	arpd_trie.hash = NULL;

	while (1) {
		status = read(fd, &req, sizeof(req));
		if (status < 0) {
			if (errno == EINTR)
				continue;
			syslog(LOG_CRIT, "cannot read from /dev/arpd: %m");
			exit(-1);
		}
		if (status != sizeof(req))
		{
			syslog(LOG_CRIT, "read from /dev/arpd returns %d",
				 status);
			exit(-1);
		}
		arpd_walk(arpd_walk_procfs, NULL);
		switch (req.req)
		{
		case ARPD_UPDATE:
			arpd_print("UPDATE", &req);
			arpd_update (&req);
			break;
		case ARPD_LOOKUP:
			arpd_print("LOOKUP", &req);
			if ((rep = arpd_lookup (&req)) == NULL) {
				req.updated = 0;
				rep = &req;
			}
			arpd_print("REPLY", rep);
			status = write(fd, rep, sizeof(*rep));
			if (status < 0) {
				 syslog(LOG_CRIT,
					"cannot write to /dev/arpd: %m");
				exit(-1);
			}
			if (status != sizeof(*rep)) {
				syslog(LOG_CRIT, 
					"write to /dev/arpd returns %d",
					 status);
				exit(-1);
			}
			break;
		case ARPD_FLUSH:
			arpd_print("FLUSH", &req);
			arpd_flush (&req);
			break;
		}
	}

	return 0;
}

static void arpd_update (struct arpd_request * entry)
{
	arpd_find (entry->dev, entry->ip, entry);
}

static struct arpd_request* arpd_lookup (struct arpd_request * entry)
{
	return arpd_find (entry->dev, entry->ip, NULL);
}

/*  if newent == NULL, we are doing a lookup, else its an update */

static struct arpd_request * arpd_find (unsigned long dev, __u32 ip,
						 struct arpd_request * newent)
{
	int depth, i;
	union arpd_triedef * tptr;
	unsigned char * key;

	key = (unsigned char *) &ip;
	tptr = &arpd_trie;
	for (depth = 0; depth < ARPD_TRIEDEPTH; ++depth)
	{
		if (tptr->hash == NULL)
		{
			/* no point in continuing if it's a lookup */
			if (newent == NULL)
				return NULL;
			tptr->hash = (union arpd_triedef *)
					malloc(ARPD_TRIEWIDTH *
					sizeof(union arpd_triedef));
			if (tptr->hash == NULL)
			{
				syslog(LOG_CRIT, "malloc() failed");
				exit (-1);
			}
			for (i = 0; i < ARPD_TRIEWIDTH; ++i)
				tptr->hash[i].hash = NULL;
		}
		tptr = &(tptr->hash[key[depth]]);
	}
	/* it was a lookup, if dev matches then return the found ent */
	if (newent == NULL)
	{
		if ((tptr->entry == NULL) || (dev != tptr->entry->dev))
			return NULL;
		else
			return tptr->entry;
	}
	/* otherwise it is an update - store the new ent */
	if (tptr->entry == NULL)
	{
		tptr->entry = (struct arpd_request *)
				malloc(sizeof(struct arpd_request));
		if (tptr->entry == NULL)
		{
			syslog(LOG_CRIT, "malloc() failed");
			exit (-1);
		}
	}
	memcpy (tptr->entry, newent, sizeof(struct arpd_request));
	return tptr->entry;
}

void arpd_walk (void (*fn)(struct arpd_request *, void *), void * ptr)
{
	int depth = 0;
	union arpd_triedef *mkr[ARPD_TRIEDEPTH + 1];
	struct arpd_request * request;
	int pos[ARPD_TRIEDEPTH + 1];

	/* mental note: iteratively walking trees sucks */
	mkr[depth] = arpd_trie.hash;
	pos[depth] = 0;
	for (;;)
	{
		if (depth < 0)
		{
			return;
		}
		else if (depth == ARPD_TRIEDEPTH)
		{
			--depth;
			request = (mkr[depth]+pos[depth])->entry;
			if (request->dev != 0)
				(*fn)(request, ptr);
		}
		else if (pos[depth] == ARPD_TRIEWIDTH)
		{
			--depth;
		}
		else if (mkr[depth] == NULL)
		{
			--depth;
		}
		else
		{
			if ((mkr[depth] + pos[depth])->hash != NULL)
			{
				mkr[depth + 1] = 
					(mkr[depth] + pos[depth])->hash;
				pos[depth + 1] = 0;
				++depth;
				continue;
			}
		}
		if (depth >= 0)
			++pos[depth];
	}
}

static void arpd_flush (struct arpd_request * entry)
{
	arpd_walk (arpd_walk_flush, entry);
}

void arpd_delete (struct arpd_request * entry)
{
	struct arpd_request * ent;

	ent = arpd_find (entry->dev, entry->ip, entry);
	memset (ent, 0, sizeof (struct arpd_request));
}

void arpd_walk_procfs(struct arpd_request * request, void * foo)
{
        struct in_addr inaddr;

	inaddr.s_addr = request->ip;
	fprintf(stderr, "Tbl: %s (%08lx)\n", 
		inet_ntoa(inaddr), ntohl(request->ip));
	return;
}

void arpd_walk_flush(struct arpd_request * entry, void * ptr)
{
	struct arpd_request * ent = ptr;

	if (entry->dev == ent->dev)
		memset (entry, 0, sizeof (struct arpd_request));
	return;
}

#ifdef DEBUG
static void arpd_print(char * type, struct arpd_request* request)
{
	unsigned char *ha=request->ha;
        struct in_addr inaddr;

	fprintf(stderr, "Type: %s\n", type);
	inaddr.s_addr = request->ip;
	fprintf(stderr, "Dst: %s (%08lx)\n", 
		inet_ntoa(inaddr), ntohl(request->ip));
	fprintf(stderr, "Dev: %08lx Stamp: %08lx Updated: %ld\n",
		request->dev, request->stamp, request->updated);
	fprintf(stderr, "HA: %02x:%02x:%02x:%02x:%02x:%02x\n\n",
		ha[0], ha[1], ha[2], ha[3], ha[4], ha[5]);
}
#endif

