/*
 * $Id: src/arpdig/doarp.c,v 1.25 2006/05/06 08:43:01 marck Exp $
 */

#include <sys/types.h>
#include <sys/ioctl.h>
#include <sys/socket.h>
#include <sys/time.h>

#include <net/bpf.h>
#include <net/ethernet.h>
#include <net/if.h>
#include <net/if_dl.h>
#include <net/if_types.h>
#include <netinet/in.h>
#include <netinet/if_ether.h>
#include <arpa/inet.h>

#include <err.h>
#include <errno.h>
#include <fcntl.h>
#include <ifaddrs.h>
#include <netdb.h>
#include <pcap.h>
#include <signal.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>

#include "arpdig.h"


static int verbose = 0;

static struct _iparp *parp;
static int narp;


static int bpfopen(void)
{
	char	devnm[BPFLEN];
	int	i, fd;

	for (i=0; i<MAXBPFN; i++) {
		snprintf(devnm, BPFLEN, "/dev/bpf%d", i);
		if (verbose)
			printf("Opening %s...", devnm);
		if ((fd = open(devnm, O_RDWR)) != -1) {
			if (verbose)
				printf(" ok\n");
			return fd;
		}
		if (verbose)
			printf(" got %d (errno=%d)\n", fd, errno);
		if (errno != EBUSY)
			return -1;
	}
	return -1;
}

static int ifattach(char *ifname)
{
	int	fd;
	struct	ifreq ifr;

	if ((fd = bpfopen()) == -1)
		return -1;
	strncpy(ifr.ifr_name, ifname, sizeof(ifr.ifr_name));
	if (ioctl(fd, BIOCSETIF, &ifr) == -1)
		return -1;
	return fd;
}

/* mostly stolen from if_ether.c:arprequest() */
static int arpreq(int fd, struct in_addr *sip, struct in_addr *tip,
		  u_char *enaddr)
{
	u_char	buf[sizeof(struct ether_header) + sizeof(struct arphdr) +
			2*(sizeof(struct in_addr) + ETHER_ADDR_LEN)];
static	u_char	etherbroadcastaddr[ETHER_ADDR_LEN] =
			{ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff };
	struct	arphdr *ah;
	struct	ether_header *eh;
	int	res;

	eh = (struct ether_header *)buf;
	eh->ether_type = htons(ETHERTYPE_ARP);
	memcpy(eh->ether_dhost, etherbroadcastaddr,
	    sizeof(eh->ether_dhost));

	ah = (struct arphdr *)(buf + sizeof(struct ether_header));
	ah->ar_hrd = htons(ARPHRD_ETHER);
	ah->ar_pro = htons(ETHERTYPE_IP);
	ah->ar_hln = ETHER_ADDR_LEN; /* we're always at ethernet */
	ah->ar_pln = sizeof(struct in_addr);
	ah->ar_op = htons(ARPOP_REQUEST);
	memcpy(ar_sha(ah), enaddr, ETHER_ADDR_LEN);
	memset(ar_tha(ah), 0, ETHER_ADDR_LEN);
	memcpy(ar_spa(ah), sip, sizeof(struct in_addr));
	memcpy(ar_tpa(ah), tip, sizeof(struct in_addr));

	res = write(fd, buf, sizeof(buf));
	return (res == sizeof(buf)) ? 0 : -1;
}


static pcap_t  *pcap_setup(char *ifname, char *errb)
{
	pcap_t	*p;
	struct bpf_program bp;
static	char	bpf_filt[] = "arp";

	if ((p = pcap_open_live(ifname, 100, 0, 10, errb)) == NULL)
		return NULL;
	if (pcap_compile(p, &bp, bpf_filt, 0, -1) == -1)
		return NULL;
	if (pcap_setfilter(p, &bp) == -1)
		return NULL;
	return p;
}

/* buf should be at least 3*6 bytes long */
static u_char *printhw(u_char *buf, u_char *hwaddr)
{
	char	*s = (char *)buf;
	int	i;

	for (i=0; i<ETHER_ADDR_LEN; i++) {
		s += sprintf(s, "%02x", (int)*hwaddr++);
		if (i < ETHER_ADDR_LEN-1)
			*s++ = ':';
	}
	return buf;
}

static char const *printarop(u_short op)
{
static	char const *arpops[] = { "OP 0", "REQUEST", "REPLY", "REVREQUEST",
			   "REVREPLY", "OP 5", "OP 6", "OP 7",
			   "INVREQUEST", "INVREPLY" };
static	char buf[15];

	if (op > MAXARPOP) {
		sprintf(buf, "op %d", op);
		return buf;
	} else
		return arpops[op];
}

static void rechwaddr(struct in_addr ip, u_char *hw)
{
	int	i;

	for (i = 0; i < narp; i++)
		if (parp[i].addr.s_addr == ip.s_addr) {
			memcpy(parp[i].hwaddr, hw, ETHER_ADDR_LEN);
			parp[i].recd++;
			return;
		}
	/* shouldn't be reached */
}

/* ARGSUSED */
static void recvp(u_char *userp, const struct pcap_pkthdr *phdr,
		  const u_char *packet)
{
	struct ether_header *eh;
	struct arphdr *ah;
	struct timeval tv, *et;
	u_char buf[18];
	int	op;
	int	pline;

	eh = (struct ether_header *)packet;
	ah = (struct arphdr *)(&packet[sizeof(struct ether_header)]);

	gettimeofday(&tv, NULL);
	et = (struct timeval *)userp;
	pline = 0;
	op = ntohs(ah->ar_op);
	if (verbose) {
		printf("%02d:%02d.%03d ARP ", (int)((tv.tv_sec%3600)/60),
		    (int)(tv.tv_sec%60), (int)(tv.tv_usec/1000) );
		printf("%s ", printarop(op));
		pline = 1;
	}
	switch (op) {
	case ARPOP_REQUEST:
		if (!verbose)
			break;
		printf("%s whois ", inet_ntoa(*(struct in_addr*)ar_spa(ah)));
		printf("%s ?", inet_ntoa(*(struct in_addr*)ar_tpa(ah)));
		pline = 1;
		break;
	case ARPOP_REPLY:
		rechwaddr(*(struct in_addr*)ar_spa(ah), (u_char *)ar_sha(ah));
		if (!verbose)
			break;
		printf("%s -> ", inet_ntoa(*(struct in_addr*)ar_spa(ah)));
		printf("%s is ", inet_ntoa(*(struct in_addr*)ar_tpa(ah)));
		printf("%s", printhw(buf, (u_char *)ar_sha(ah)));
		pline = 1;
		break;
	default:
		if (!verbose)
			break;
		printf("%s -> ", printhw(buf, (u_char *)ar_sha(ah)));
		printf("%s", printhw(buf, (u_char *)ar_tha(ah)));
		pline = 1;
	}
	if (pline)
		printf("\n");
}


static char *filladdr(char *ifname, unsigned char hwaddr[ETHER_ADDR_LEN])
{
	struct	ifaddrs *ifap, *p;
	struct	sockaddr *sa;

	if (getifaddrs(&ifap) == -1)
		return NULL;
	for (p = ifap; p; p = p->ifa_next) {
		if (ifname == NULL)
			ifname = strdup(p->ifa_name);
		if (strncmp(p->ifa_name,ifname,IFNAMSIZ) != 0)
			continue;
		if ((sa = p->ifa_addr) != NULL && sa->sa_family == AF_LINK) {
			const struct sockaddr_dl *sdl;
			sdl = (struct sockaddr_dl *)sa;
			if (sdl->sdl_type == IFT_ETHER &&
			    sdl->sdl_alen == ETHER_ADDR_LEN) {
				memcpy(hwaddr, LLADDR(sdl), ETHER_ADDR_LEN);
				break;
			}
		}
	}
	freeifaddrs(ifap);
	return (p) ? ifname : NULL;
}

static void parpres(void)
{
	int	i;
	u_char	buf[3*ETHER_ADDR_LEN];
	struct	hostent *he;

	for (i = 0; i < narp; i++) {
		if (parp[i].recd) {
			printf("%s %s", inet_ntoa(parp[i].addr),
			    printhw(buf, parp[i].hwaddr));
			if (!noresolve) {
				he = gethostbyaddr((char *)&parp[i].addr,
				    sizeof(struct in_addr), AF_INET);
				printf(" (%s)", he ? he->h_name : "?");
			}
			printf("\n");
		} else if (verbose)
			printf("%s -\n", inet_ntoa(parp[i].addr));
	}
}

static int wd_expired = 0;

/* ARGSUSED */
static void watchdog(int sig)
{
	wd_expired = 1;
}

int arpdig(char *ifname, struct in_addr if_ia, struct in_addr ipstart, int iplen)
{
	int	fd, nbuf, level;
	u_long	addr, startaddr, endaddr;
	u_char	hwaddr[ETHER_ADDR_LEN];
	char	errb[PCAP_ERRBUF_SIZE];
	pcap_t  *p;
	struct	timeval ct, et, st;	/* current, emitted and start time */
	struct	_iparp *pa;

	if ((ifname = filladdr(ifname, hwaddr)) == NULL)
		err(1, "Can't obtain own lladdr for %s", ifname);

	/* become suser again */
	if (getuid() == 0)
		seteuid(0);
	p = pcap_setup(ifname, errb);
	if (p == NULL)
		errx(1, "Can't setup capture: %s", errb);
	fd = ifattach(ifname);
	if (fd == -1)
		err(1, "Can't attach to %s", ifname);

	/* paranoia */
	if (getuid() == 0)
		seteuid(-1);

	/* make a list of addresses to check */
	startaddr = ntohl(ipstart.s_addr);
	if (iplen == 32) {
		startaddr = ntohl(ipstart.s_addr);
		endaddr = startaddr + 1;
	} else {
		startaddr++;
		endaddr = ntohl(ipstart.s_addr) | ~((u_long)-1 << (32-iplen));
	}
	narp = 0;
	nbuf = DEFNARP;
	parp = malloc(sizeof(struct _iparp)*nbuf);
	if (parp == NULL)
		err(1, "Out of memory");
	for (addr = startaddr; addr < endaddr; addr++) {
		struct in_addr sa;
		sa.s_addr = htonl(addr);
		if (narp == nbuf - 1) {
			nbuf *= 2;
			parp = realloc(parp, sizeof(struct _iparp)*nbuf);
			if (parp == NULL)
				err(1, "Out of memory");
		}
		pa = &parp[narp++];
		memset(pa, 0, sizeof(struct _iparp));
		pa->addr = sa;
	}

	signal(SIGALRM, watchdog);
	alarm(WDSECS);

	/* loop for it */
	gettimeofday(&st, NULL);
	et = st;
	pa = parp;
	level = 0;
	for(;;) {
		if (wd_expired)
			break;
		gettimeofday(&ct, NULL);

		/* XXX need to restrict emitter!!! */
		/* find address to arping */
		while (pa < &parp[narp] && pa->emitted > level)
			pa++;
		if (pa == &parp[narp]) {
			level++;
			if (level >= MAXTRIES)
				break;
			pa = parp;
			usleep(100);
		}

		/* emit a packet */
		if (arpreq(fd, &if_ia, &pa->addr, hwaddr) == -1)
			err(1, "arpreq");
		pa->emitted++;

		/* and listen for the answers... */
		pcap_dispatch(p, 1, recvp, (void *)&et);

		/* sleep a bit as a guard */
		usleep(100);
	}
	alarm(0);
	close(fd);
	pcap_close(p);

	parpres();
	return 0;
}

/* End of $RCSfile: doarp.c,v $ */


syntax highlighted by Code2HTML, v. 0.9.1