4.3BSD-UWisc/src/usr.etc/rarpd/rarpd.c

Compare this file to the similar file:
Show the results in this format:

#ifndef lint
/* @(#)rarpd.c	2.1 86/04/16 NFSSRC */
static  char sccsid[] = "@(#)rarpd.c 1.1 86/02/05 Copyr 1985 Sun Micro";
#endif

/*
 * rarpd.c  Reverse-ARP server.
 * Copyright (c) 1985 by Sun Microsystems, Inc.
 */

#include <stdio.h>
#include <sys/file.h>
#include <sys/ioctl.h>
#include <errno.h>
#include <sys/time.h>

#include <sys/types.h>
#include <sys/socket.h>
#include <net/if.h>
#include <net/nit.h>
#include <netinet/in.h>
#include <netinet/if_ether.h>
#include <netinet/ip_var.h>
#include <netinet/udp.h>
#include <netinet/udp_var.h>
#include <netdb.h>

#define BUFSIZE 10000
char *malloc();

static int
	received, 	/* total packets read */
	bad,		/* packets that did not look like rarp packets */
	unknown,	/* unknown ether -> ip address mapping */
	processed,	/* answer known and sent */
	delayed,	/* answer was delayed before sending */
	weird;		/* unexpected, yet valid */
	/* ignored = received - (bad + unknown + processed) */
	
static int if_fd;
static struct ether_addr my_etheraddr, *eap;
static u_char my_ipaddr[4];  /* in network order */
static char *cmdname;
static int debug;

extern int errno;

/*
 * When the rarp packet is a request (arp_op = REVARP_REQUEST = 3), then
 *	arp_xsha is the ethernet address of the sender of the request;
 *	arp_xspa is undefined;
 *	arp_xtha is the 'target' hardware address, i.e. the sender's address,
 *	arp_xtpa is undefined.
 * The rarp reply (arp_op = REVARP_REPLY = 4) looks like:
 *	arp_xsha is the responder's (our) ethernet address;
 *	arp_xspa is the responder's (our) IP address;
 *	arp_xtha is identical to the request packet;
 *	arp_xtpa is the request's desired IP address.
 */
struct rarp_request {
	struct	ether_header rr_eheader;
	struct ether_arp rr_arpheader;
	u_short rr_len;  /* not part of the protocol; fill in at "read" time */
};

main(argc, argv)
	int argc;
	char **argv;
{
	register int i;
	struct ifreq ifr;
	int delay_fd[2], d_fd;
	char *device, *buf, *cause;
	register struct rarp_request *rqst;
	struct rarp_request ans;
	char host[256];
	register struct hostent *hp;
	struct timeval now, then;
	struct timezone zone;

	cmdname = argv[0];
	while (argc > 1 && argv[1][0] == '-' && argv[1][2] == '\0') {
		switch (argv[1][1]) {
		case 'd':
			debug++;
			break;
		default:
			usage();
		}
		argc--, argv++;
	}
	if (argc != 3)
		usage();
	/*
	 * Using the hostname, get the associated IP address.
	 */
	device = argv[2];  /* really host name */
	if (((hp = gethostbyname(device)) == (struct hostent *) NULL) ||
	    (hp->h_length != sizeof (my_ipaddr))) {
		fprintf(stderr, "%s: cannot find host entry for %s\n",
		    argv[0], device);
		exit(2);
	}
	bcopy(hp->h_addr, my_ipaddr, sizeof (my_ipaddr));
	/*
	 * Open the ether device and pull out the ether address.
	 */
	device = argv[1];
        if_fd = rarp_open(device, htons((u_short)ETHERPUP_REVARPTYPE));
        if (if_fd < 0)
                exit(5);
	strncpy(ifr.ifr_name, device, sizeof ifr.ifr_name);
        if (ioctl (if_fd, SIOCGIFADDR, (caddr_t) &ifr) < 0) {
		fprintf(stderr, "%s: (ioctl) cannot find ether entry for %s\n",
                    argv[0], device);
	    	exit(3);
        }
	eap = (struct ether_addr *) &ifr.ifr_addr.sa_data[0];
	my_etheraddr = *eap;

	if (!debug) {
		/*
		 * Background
		 */
		while (if_fd < 3) {
			if_fd = dup(if_fd);
		}
		switch (fork()) {
		
		case -1:
			fprintf(stderr, "%s: fork failure\n", argv[0]);
			exit(6);
			break;
		
		case 0:
			break;
		
		default:
			exit(0);
			break;
		}
		for (i = 0; i < 32; i++) {
			if (i != if_fd) {
				close(i);
			}
		}
		(void) open("/", O_RDONLY, 0);
		(void) dup2(0, 1);
		(void) dup2(0, 2);
		/*
		 * Detach terminal
		 */
		if ((i = open("/dev/tty", O_RDWR, 0)) >= 0) {
			ioctl(i, TIOCNOTTY, 0);
			close(i);
		}
	}
	/*
	 * Fork off a delayed responder which uses a pipe.
	 */
	 if (pipe(delay_fd)) {
	 	perror("rarpd: pipe");
	 	exit(7);
	}

	 switch (fork()) {
	 
	 case -1:
	 	perror("rarpd: delayed fork");
	 	exit(8);
	 	break;
	 
	 case 0:
	 	/* child reads the pipe and sends responses */
	 	close(delay_fd[1]);  /* no writing */
	 	d_fd = delay_fd[0];
	 	buf = host;
	 	i = sizeof (ans) - sizeof (ans.rr_len);
	 	for (;;) {
	 		if ((read(d_fd, &then, sizeof (then)) != sizeof (then))
	 		    || (read(d_fd, buf, i) != i)) {
	 			perror("delayed rarpd: read");
	 			exit(9);
	 		}
	 		received++;
	 		/*
	 		 * It is our job to never send a (delayed) reply
	 		 * in the same second of time that it was created;
	 		 * rather wait up to three seconds before responding.
	 		 */
	 		gettimeofday(&now, &zone);
	 		if (now.tv_sec == then.tv_sec) {
	 			sleep(3);
	 			delayed++;
	 		}
	 		if (rarp_write(if_fd, buf, i) != i) {
	 			perror("delayed rarpd: rarp_write");
	 			exit(10);
	 			break;  /* eliminate warning msgs */
	 		};
	 	}
	 	/*NOTREACHED*/
	 	exit(0);
	 	break;
	 
	 default:
	 	/* parent does most processing and maybe writes to the child */
	 	close(delay_fd[0]);  /* no reading */
	 	d_fd = delay_fd[1];
	 	break;
	 }
	/*
	 * read RARP packets and respond to them.
	 *
	 * This server adapts to heavy loads by ignoring requests;
	 * this is accomplished by processing only the last packet
	 * of a multi-packet read call.
	 *
	 * This server delays answering requests if it cannot
	 * "tftp" boot the requestor.
	 */
	buf = malloc(BUFSIZE);
	for (;;) {
		char *bp, *last;
		struct nit_hdr *nh;
		int datalen, cnt;

		if ((i = read(if_fd, buf, BUFSIZE)) < 0) {
			perror("rarpd: read");
			exit(11);
		}
		if (debug > 1)
			fprintf(stderr, "DEBUG: read read %d\n", i);
		/*
		 * find the last request in the requests' buffer.
		 */
		cnt = 0;
		last = 0;
		for (bp = buf; bp < buf+i;
		    bp += ((sizeof(*nh)+datalen+sizeof(int)-1)
				& ~(sizeof (int)-1))) {
			nh = (struct nit_hdr *)bp;
			if (nh->nh_state != NIT_CATCH)
				datalen = 0;
			else {
				cnt++;
				last = bp;
				datalen = nh->nh_datalen;
			}
		}
		/*
		 * Consistency check: verify that the total read length
		 * matches the cumulative length obtained by using the
		 * individual lengths contained in the nit headers.  This
		 * check must take header alignment into account.
		 */
		if (last == 0 || ((u_int)(last+sizeof(*nh)+datalen+sizeof(int)-1)
					& ~(sizeof(int)-1)) !=
		    ((u_int)(buf+i+sizeof(int)-1) & ~(sizeof(int)-1))) {
			fprintf(stderr, "%s: badly aligned read.\n", argv[0]);
			exit(12);
		}

		received += cnt;
		if (debug > 1)
			fprintf(stderr, "DEBUG: received %d packets\n", cnt);
		nh = (struct nit_hdr *)last;
		ans = *(struct rarp_request *)(last+sizeof(*nh));
		ans.rr_len = nh->nh_wirelen;
		/* 
		 * Sanity checks ... set i to sizeof an rarp packet
		 */
		i = sizeof (ans) - sizeof (ans.rr_len);
		cause = 0;

		if (ans.rr_len < i)
			cause="rr_len";
		else if (ans.rr_eheader.ether_type !=
		    (u_short)ETHERPUP_REVARPTYPE)
			cause="type";
		else if (ans.rr_arpheader.arp_hrd != htons(ARPHRD_ETHER))
			cause="hrd";
		else if (ans.rr_arpheader.arp_pro != ETHERPUP_IPTYPE)
			cause="pro";
		else if (ans.rr_arpheader.arp_hln != 6)
			cause="hln";
		else if (ans.rr_arpheader.arp_pln != 4)
			cause="pln";
		if (cause) {
			if (debug)
				fprintf(stderr,
				    "DEBUG: sanity check failed; cause: %s\n",
				    cause);
			continue;
		}
		switch (ans.rr_arpheader.arp_op) {
		
		case REVARP_REQUEST:
			if (debug > 1)
				fprintf(stderr, "DEBUG: REVARP_REQUEST\n");
			break;
		
		case ARPOP_REQUEST:
			if (debug > 1)
				fprintf(stderr, "DEBUG: ARPOP_REQUEST\n");
			arp_request(&ans);
			continue;
		
		default:
			if (debug)
				fprintf(stderr,
				    "DEBUG: INVALID 0x%xd\n",
				    ans.rr_arpheader.arp_op);
			bad++;
			continue;
		}
		/*
		 * Check for weird (although valid) requests
		 */
		if (bcmp(ans.rr_arpheader.arp_xsha,
		    ans.rr_arpheader.arp_xtha, 6)  |
		    bcmp(ans.rr_arpheader.arp_xsha,
		    &ans.rr_eheader.ether_shost, 6)) {
			if (debug)
				fprintf(stderr, "DEBUG: WEIRD\n");
			weird++;
		}
		/*
		 * process the RARP request.
		 */
		ans.rr_eheader.ether_dhost =
		    *(struct ether_addr *)ans.rr_arpheader.arp_xsha;
		ans.rr_arpheader.arp_op = REVARP_REPLY;
		bcopy(&my_etheraddr, ans.rr_arpheader.arp_xsha,
		    sizeof (ans.rr_arpheader.arp_xsha));
		bcopy(my_ipaddr, ans.rr_arpheader.arp_xspa,
		    sizeof (ans.rr_arpheader.arp_xspa));
		if ((ether_ntohost(host, ans.rr_arpheader.arp_xtha) != 0) ||
		    ((hp = gethostbyname(host)) == (struct hostent *)NULL) ||
		    (hp->h_length != sizeof (ans.rr_arpheader.arp_xtpa))) {
			if (debug)
			    fprintf(stderr, "DEBUG: UNKNOWN\n");
			unknown++;
			continue;
		}
		bcopy(hp->h_addr, ans.rr_arpheader.arp_xtpa,
		    sizeof (ans.rr_arpheader.arp_xtpa));
		/*
		 * Add the requestor's ARP entry in anticipation of
		 * further conversation.
		 */
		 add_arp(ans.rr_arpheader.arp_xtpa,
		     &ans.rr_eheader.ether_dhost);
		/*
		 * send the answer.
		 * It is delayed if we cannot tftp boot the requestor
		 */
		sprintf(host, "/tftpboot/%08X",
		    ntohl(*(u_long *)ans.rr_arpheader.arp_xtpa));
		if ((!debug) && stat(host, buf)) {
			gettimeofday(&now, &zone);
			if ((write(d_fd, &now, sizeof (now)) != sizeof (now))
			    || (write(d_fd, &ans, i) != i)) {
				perror("rarpd: delayed write");
				exit(13);
			}
			delayed++;
		} else if (rarp_write(if_fd, &ans, i) != i) {
			perror("rarpd: rarp_write");
			exit(14);
		}
		processed++;
	}
}

/*
 * down loads regular ARP entries to the kernel.
 * NB: Only down loads if an entry does not already exist.
 */
static
add_arp(ipap, eap)
	char *ipap;  /* IP address pointer */
	struct ether_addr *eap;
{
	struct arpreq ar;
	struct sockaddr_in *sin;
	int s;
	
	/*
	 * Common part of query or set
	 */
	bzero((caddr_t)&ar, sizeof (ar));
	ar.arp_pa.sa_family = AF_INET;
	sin = (struct sockaddr_in *)&ar.arp_pa;
	sin->sin_addr = *(struct in_addr *)ipap;
	s = socket(AF_INET, SOCK_DGRAM, 0);
	/*
	 * If one exits, return
	 */
	if ((ioctl(s, SIOCGARP, (caddr_t)&ar) == 0) || (errno != ENXIO)) {
		close(s);
		return;
	}
	/*
	 * Set the entry
	 */
	bcopy(eap, ar.arp_ha.sa_data, sizeof (*eap));
	ar.arp_flags = 0;
        (void) ioctl(s, SIOCSARP, (caddr_t)&ar);
        close(s);
}

/*
 * The RARP spec says we must be able to process ARP requests, even through
 * the packet type is RARP.  Let's hope this feature is not heavily used.
 */
static
arp_request(a)
	struct rarp_request *a;
{

	if (!bcmp(my_ipaddr, a->rr_arpheader.arp_xtpa, sizeof (my_ipaddr))) {
		return;
	}
	a->rr_eheader.ether_dhost =
	    *(struct ether_addr *)a->rr_arpheader.arp_xsha;
	a->rr_arpheader.arp_op = ARPOP_REPLY;
	bcopy(a->rr_arpheader.arp_xsha, a->rr_arpheader.arp_xtha,
	    sizeof (a->rr_arpheader.arp_xsha));
	bcopy(a->rr_arpheader.arp_xspa, a->rr_arpheader.arp_xtpa,
	    sizeof (a->rr_arpheader.arp_xspa));
	bcopy(&my_etheraddr, a->rr_arpheader.arp_xsha,
	    sizeof (a->rr_arpheader.arp_xsha));
	bcopy(my_ipaddr, a->rr_arpheader.arp_xspa,
	    sizeof (a->rr_arpheader.arp_xspa));
	add_arp(a->rr_arpheader.arp_xtpa, &a->rr_eheader.ether_dhost);
	(void) rarp_write(if_fd, a, sizeof (a) - sizeof(a->rr_len));
}

usage()
{
	fprintf(stderr, "Usage: %s [-d] device hostname\n", cmdname);
	exit(1);
}
static int
rarp_open(device, type)
	char *device;
	u_short type;
{
	int if_fd;
	struct sockaddr_nit snit;
	struct nit_ioc nioc;
	
	if_fd = socket(AF_NIT, SOCK_RAW, NITPROTO_RAW);
	if (if_fd < 0) {
		perror("nit socket");
		return (-1);
	}

	snit.snit_family = AF_NIT;
	strncpy(snit.snit_ifname, device, NITIFSIZ);
	if (bind(if_fd, &snit, sizeof(snit)) != 0) {
		perror(device);
		return (-2);
	}

	bzero(&nioc, sizeof(nioc));
	nioc.nioc_bufspace = NITBUFSIZ;
	nioc.nioc_chunksize = NITBUFSIZ;
	nioc.nioc_typetomatch = type;
	nioc.nioc_snaplen = 32767;
	nioc.nioc_flags = NF_TIMEOUT;
	nioc.nioc_timeout.tv_usec = 200;
	if (ioctl(if_fd, SIOCSNIT, &nioc) != 0) {
		perror("nit ioctl");
		return (-3);
	}
	return (if_fd);
}

static int
rarp_write(fd, buf, len)
	int fd, len;
	char *buf;
{
	struct sockaddr sa;
	int offset = sizeof(sa.sa_data);
	int result;

	sa.sa_family = AF_UNSPEC;
	bcopy(buf, sa.sa_data, offset);
	result = sendto(fd, buf+offset, len-offset, 0, &sa, sizeof(sa));
	return (result+offset);
}

/*
 * Read and copy the last packet into <pkt>,
 * stuffing the wirelength at the end.
 */
static int
rarp_last(fd, pkt, plen)
	int fd;
	char *pkt;
	int plen;
{
	register char *bp, *last;
	register struct nit_hdr *nh;
	register int len, datalen;
	char buf[NITBUFSIZ];

	if ((len = read(fd, buf, sizeof(buf))) < 0) {
		perror("rarpd: read");
		return (-1);
	}
	/*
	 * find the last request in the requests' buffer.
	 */
	last = 0;
	for (bp = buf; bp < buf+len; bp += sizeof(*nh)+datalen) {
		nh = (struct nit_hdr *)bp;
		if (nh->nh_state != NIT_CATCH)
			datalen = 0;
		else {
			last = bp;
			datalen = nh->nh_datalen;
		}
	}
	if (last == 0) {
		fprintf(stderr, "rarp_last: no packets returned\n");
		return (-2);
	}
	if (last+sizeof(*nh)+datalen != buf+len) {
		fprintf(stderr, "rarp_last: truncated packet\n");
		return (-3);
	}

#define MIN(s, t)	((s)<(t)?(s):(t))
	datalen = MIN(datalen, plen - sizeof (u_short));
	nh = (struct nit_hdr *)last;
	bcopy(last+sizeof(*nh), pkt, datalen);
	((u_short *)(pkt+plen))[-1] = nh->nh_wirelen;

	return (datalen + sizeof (u_short));
}