/*
 * Rdisc (this program) was developed by Sun Microsystems, Inc. and is
 * provided for unrestricted use provided that this legend is included on
 * all tape media and as a part of the software program in whole or part.
 * Users may copy or modify Rdisc without charge, and they may freely
 * distribute it.
 *
 * RDISC IS PROVIDED AS IS WITH NO WARRANTIES OF ANY KIND INCLUDING THE
 * WARRANTIES OF DESIGN, MERCHANTIBILITY AND FITNESS FOR A PARTICULAR
 * PURPOSE, OR ARISING FROM A COURSE OF DEALING, USAGE OR TRADE PRACTICE.
 *
 * Rdisc is provided with no support and without any obligation on the
 * part of Sun Microsystems, Inc. to assist in its use, correction,
 * modification or enhancement.
 *
 * SUN MICROSYSTEMS, INC. SHALL HAVE NO LIABILITY WITH RESPECT TO THE
 * INFRINGEMENT OF COPYRIGHTS, TRADE SECRETS OR ANY PATENTS BY RDISC
 * OR ANY PART THEREOF.
 *
 * In no event will Sun Microsystems, Inc. be liable for any lost revenue
 * or profits or other special, indirect and consequential damages, even if
 * Sun has been advised of the possibility of such damages.
 *
 * Sun Microsystems, Inc.
 * 2550 Garcia Avenue
 * Mountain View, California  94043
 */
#include <stdio.h>
#include <errno.h>
#include <signal.h>
#include <sys/types.h>
#include <sys/time.h>

#include <sys/param.h>
#include <sys/socket.h>
#include <sys/file.h>

#include <sys/ioctl.h>
#include <net/if.h>

#include <netinet/in_systm.h>
#include <netinet/in.h>
#include <netinet/ip.h>
#include <netinet/ip_icmp.h>

#include <netdb.h>
#include <arpa/inet.h>

#include <string.h>
#include <syslog.h>
#include <stdlib.h>
#include <unistd.h>
#include <err.h>
#include <sys/cdefs.h>

#if __STDC__
#include <stdarg.h>
#else
#include <varargs.h>
#endif

/*
 * TBD
 *	Use 255.255.255.255 for broadcasts - not the interface broadcast
 *	address.
 */

#ifdef lint
#define ALLIGN(ptr)	(ptr ? 0 : 0)
#else
#define ALLIGN(ptr)	(ptr)
#endif


#define ICMP_ROUTER_ADVERTISEMENT	9
#define ICMP_ROUTER_SOLICITATION	10

#define ALL_HOSTS_ADDRESS		"224.0.0.1"
#define ALL_ROUTERS_ADDRESS		"224.0.0.2"

#define MAXIFS 32

/* Router constants */
#define	MAX_INITIAL_ADVERT_INTERVAL	16
#define	MAX_INITIAL_ADVERTISEMENTS  	3
#define	MAX_RESPONSE_DELAY		2	/* Not used */

/* Host constants */
#define MAX_SOLICITATIONS 		3
#define SOLICITATION_INTERVAL 		3
#define MAX_SOLICITATION_DELAY		1	/* Not used */

#define IGNORE_PREFERENCE	0x80000000	/* Maximum negative */

#define MAX_ADV_INT 600

/* Statics */
int num_interfaces;
struct interface {
	struct in_addr 	address;	/* Used to identify the interface */
	struct in_addr	localaddr;	/* Actual address if the interface */
	int 		preference;
	int		flags;
	struct in_addr	bcastaddr;
	struct in_addr	remoteaddr;
	struct in_addr	netmask;
};
struct interface *interfaces;
int interfaces_size;			/* Number of elements in interfaces */


#define	MAXPACKET	4096	/* max packet size */
u_char	packet[MAXPACKET];

char usage[] =
"Usage:	rdisc [-s] [-v] [-f] [-a] [send_address] [receive_address]\n\
        rdisc -r [-v] [-p <preference>] [-T <secs>] \n\
		[send_address] [receive_address]\n";


int s;			/* Socket file descriptor */
struct sockaddr_in whereto;/* Address to send to */

/* Common variables */
int verbose = 0;
int debug = 0;
int trace = 0;
int solicit = 0;
int responder;
int ntransmitted = 0;
int nreceived = 0;
int forever = 0;	/* Never give up on host. If 0 defer fork until
			 * first response.
			 */

/* Router variables */
int max_adv_int = MAX_ADV_INT;
int min_adv_int;
int lifetime;
int initial_advert_interval = MAX_INITIAL_ADVERT_INTERVAL;
int initial_advertisements = MAX_INITIAL_ADVERTISEMENTS;
int preference = 0;		/* Setable with -p option */

/* Host variables */
int max_solicitations = MAX_SOLICITATIONS;
unsigned int solicitation_interval = SOLICITATION_INTERVAL;
int best_preference = 1;  	/* Set to record only the router(s) with the
				   best preference in the kernel. Not set
				   puts all routes in the kernel. */

/* Prototypes */
void	do_fork		__P((void));
int	main		__P((int, char **));
void	timer		__P((int));
void	solicitor	__P((struct sockaddr_in *));
void	advertise	__P((struct sockaddr_in *));
char *	pr_type		__P((int));
char *	pr_name		__P((struct in_addr));
void	pr_pack		__P((char *, int, struct sockaddr_in *));
int	in_cksum	__P((u_short *, int));
void	finish		__P((int));
int	isbroadcast	__P((struct sockaddr_in *));
int	ismulticast	__P((struct sockaddr_in *));
int	sendbcast	__P((int, char *, int));
int	sendbcastif	__P((int, char *, int, struct interface *));
int	sendmcast	__P((int, char *, int, struct sockaddr_in *));
int	sendmcastif	__P((int, char *, int, struct sockaddr_in *, struct interface *));
void	init		__P((void));
void	initifs		__P((int));
int	support_multicast	__P((void));
int	is_directly_connected	__P((struct in_addr));
struct table * find_router	__P((struct in_addr));
int	max_preference	__P((void));
void	age_table	__P((int));
void	record_router	__P((struct in_addr, long, int));
void	add_route	__P((struct in_addr));
void	del_route	__P((struct in_addr));
void	rtioctl		__P((struct in_addr, int));
void	initlog		__P((void));
void	logerr		__P((char *fmt, ...));
void	logtrace	__P((char *fmt, ...));
void	logdebug	__P((char *fmt, ...));
void	logperror	__P((char *str));
void	prusage		__P((void));
int	join		__P((int sock, struct sockaddr_in *sin));


void
prusage()
{
	(void) fprintf(stderr, usage);
	exit(1);
}

void
do_fork()
{
	int t;
	long pid;

	if (trace)
		return;

	if ((pid = fork()))
		exit(0);

	for (t = 0; t < 20; t++)
		if (t != s)
			(void) close(t);

	(void) setsid ();

	initlog();
}

/*
 * 			M A I N
 */
char    *sendaddress, *recvaddress;

int
main(argc, argv)
	int argc;
	char *argv[];
{
	struct sigaction sa;
	struct sockaddr_in from;
	struct sockaddr_in *to = &whereto;
	struct sockaddr_in joinaddr;
	int val;
	int c;

	min_adv_int =( max_adv_int * 3 / 4);
	lifetime = (3*max_adv_int);

	while ((c = getopt(argc, argv, "dtvsrabfT:p:")) != EOF) {
		switch(c) {
		case 'd':
			debug = 1;
			break;
		case 't':
			trace = 1;
			break;
		case 'v':
			verbose++;
			break;
		case 's':
			solicit = 1;
			break;
		case 'r':
			responder = 1;
			break;
		case 'a':
			best_preference = 0;
			break;
		case 'b':
			best_preference = 1;
			break;
		case 'f':
			forever = 1;
			break;
		case 'T':
			val = strtol(optarg, (char **)NULL, 0);
			if (val < 4 || val > 1800) {
				(void) fprintf(stderr,
			          "Bad Max Advertizement Interval\n");
				exit(1);
			}
			max_adv_int = val;
			min_adv_int =( max_adv_int * 3 / 4);
			lifetime = (3*max_adv_int);
			break;
		case 'p':
			val = strtol(optarg, (char **)NULL, 0);
			preference = val;
			break;
		default:
			prusage();
			/* NOTREACHED*/
		}
	}
	argc -= optind;
	argv += optind;

	if( argc < 1)  {
		if (support_multicast()) {
			if (responder)
				sendaddress = ALL_HOSTS_ADDRESS;
			else
				sendaddress = ALL_ROUTERS_ADDRESS;
		} else
			sendaddress = "255.255.255.255";
	} else {
		sendaddress = argv[0];
		argc--; argv++;
	}

	if (argc < 1) {
		if (support_multicast()) {
			if (responder)
				recvaddress = ALL_ROUTERS_ADDRESS;
			else
				recvaddress = ALL_HOSTS_ADDRESS;
		} else
			recvaddress = "255.255.255.255";
	} else {
		recvaddress = argv[0];
		argc--; argv++;
	}
	if (argc != 0) {
		(void) fprintf(stderr, "Extra paramaters\n");
		prusage();
		/* NOTREACHED */
	}

	if (solicit && responder) {
		prusage();
		/* NOTREACHED */
	}

	if (!(solicit && !forever)) {
		do_fork();
/*
 * Added the next line to stop forking a second time
 * Fraser Gardiner - Sun Microsystems Australia
 */
		forever = 1;
	}

	bzero( (char *)&whereto, sizeof(struct sockaddr_in) );
	to->sin_family = AF_INET;
	to->sin_addr.s_addr = inet_addr(sendaddress);

	bzero( (char *)&joinaddr, sizeof(struct sockaddr_in) );
	joinaddr.sin_family = AF_INET;
	joinaddr.sin_addr.s_addr = inet_addr(recvaddress);

	if (responder) {
		/* TBD fix this to be more random */
		srandom((int)gethostid()+(int)time((time_t *)NULL));
	}

	if ((s = socket(AF_INET, SOCK_RAW, IPPROTO_ICMP)) < 0) {
		logperror("socket");
		exit(5);
	}

	setvbuf( stdout, NULL, _IOLBF, 0 );

	(void) signal( SIGINT, finish );
	(void) signal( SIGTERM, finish );
	(void) signal( SIGHUP, initifs );

	init();
	if (join(s, &joinaddr) < 0) {
		logerr("Failed joining addresses\n");
		exit (2);
	}


	/*
	 * Make sure that this signal actually interrupts (rather than
	 * restarts) the recvfrom call below.
	 */
	memset(&sa, 0, sizeof(sa));
	sa.sa_handler = timer;
	sigemptyset(&sa.sa_mask);
	sa.sa_flags = 0;	/* note: no SA_RESTART */
	(void) sigaction(SIGALRM, &sa, (struct sigaction *)NULL);
	timer(0);	/* start things going */

	for (;;) {
		int len = sizeof (packet);
		int fromlen = sizeof (from);
		int cc;

		if ( (cc=recvfrom(s, (char *)packet, len, 0,
				  (struct sockaddr *)&from, &fromlen)) < 0) {
			if( errno == EINTR )
				continue;
			logperror("recvfrom");
			continue;
		}
		pr_pack( (char *)packet, cc, &from );
	}
	/*NOTREACHED*/
}

#define TIMER_INTERVAL 	3
#define GETIFCONF_TIMER	30

int left_until_advertise;

/* Called every TIMER_INTERVAL */
void timer(sig)
     int sig;
{
	static int left_until_getifconf;
	static int left_until_solicit;

	left_until_getifconf -= TIMER_INTERVAL;
	left_until_advertise -= TIMER_INTERVAL;
	left_until_solicit -= TIMER_INTERVAL;

	if (left_until_getifconf < 0) {
		initifs(0);
		left_until_getifconf = GETIFCONF_TIMER;
	}
	if (responder && left_until_advertise <= 0) {
		ntransmitted++;
		advertise(&whereto);
		if (ntransmitted < initial_advertisements)
			left_until_advertise = initial_advert_interval;
		else
			left_until_advertise = min_adv_int +
				((max_adv_int - min_adv_int) *
				 (random() % 1000)/1000);
	} else if (solicit && left_until_solicit <= 0) {
		ntransmitted++;
		solicitor(&whereto);
		if (ntransmitted < max_solicitations)
			left_until_solicit = solicitation_interval;
		else {
			solicit = 0;
			if (!forever && nreceived == 0)
				exit(5);
		}
	}
	age_table(TIMER_INTERVAL);
	(void) alarm(TIMER_INTERVAL);
}

/*
 * 			S O L I C I T O R
 *
 * Compose and transmit an ICMP ROUTER SOLICITATION REQUEST packet.
 * The IP packet will be added on by the kernel.
 */
void
solicitor(sin)
	struct sockaddr_in *sin;
{
	static u_char outpack[MAXPACKET];
	register struct icmp *icp = (struct icmp *) ALLIGN(outpack);
	int packetlen, i;

	if (verbose) {
		logtrace("Sending solicitation to %s\n",
			 pr_name(sin->sin_addr));
	}
	icp->icmp_type = ICMP_ROUTER_SOLICITATION;
	icp->icmp_code = 0;
	icp->icmp_cksum = 0;
	icp->icmp_void = 0; /* Reserved */
	packetlen = 8;

	/* Compute ICMP checksum here */
	icp->icmp_cksum = in_cksum( (u_short *)icp, packetlen );

	if (isbroadcast(sin))
		i = sendbcast(s, (char *)outpack, packetlen);
	else if (ismulticast(sin))
		i = sendmcast( s, (char *)outpack, packetlen, sin);
	else
		i = sendto( s, (char *)outpack, packetlen, 0,
			   (struct sockaddr *)sin, sizeof(struct sockaddr));

	if( i < 0 || i != packetlen )  {
		if( i<0 ) {
		    logperror("sendto");
		}
		logerr("wrote %s %d chars, ret=%d\n",
			sendaddress, packetlen, i );
	}
}

/*
 * 			A V E R T I S E
 *
 * Compose and transmit an ICMP ROUTER ADVERTISEMENT packet.
 * The IP packet will be added on by the kernel.
 */
void
advertise(sin)
	struct sockaddr_in *sin;
{
	static u_char outpack[MAXPACKET];
	register struct icmp *rap = (struct icmp *) ALLIGN(outpack);
	struct icmp_ra_addr *ap;
	int packetlen, i, cc;

	if (verbose) {
		logtrace("Sending advertisement to %s\n",
			 pr_name(sin->sin_addr));
	}

	for (i = 0; i < num_interfaces; i++) {
		rap->icmp_type = ICMP_ROUTER_ADVERTISEMENT;
		rap->icmp_code = 0;
		rap->icmp_cksum = 0;
		rap->icmp_num_addrs = 0;
		rap->icmp_wpa = 2;
		rap->icmp_lifetime = lifetime;
		packetlen = 8;

		/*
		 * TODO handle multiple logical interfaces per
		 * physical interface. (increment with rap->icmp_wpa * 4 for
		 * each address.)
		 */
		ap = (struct icmp_ra_addr *)ALLIGN(outpack + ICMP_MINLEN);
		ap->ira_addr = interfaces[i].localaddr.s_addr;
		ap->ira_preference = interfaces[i].preference;
		packetlen += rap->icmp_wpa * 4;
		rap->icmp_num_addrs++;


		/* Compute ICMP checksum here */
		rap->icmp_cksum = in_cksum( (u_short *)rap, packetlen );

		if (isbroadcast(sin))
			cc = sendbcastif(s, (char *)outpack, packetlen,
					&interfaces[i]);
		else if (ismulticast(sin))
			cc = sendmcastif( s, (char *)outpack, packetlen, sin,
					&interfaces[i]);
		else {
			struct interface *ifp = &interfaces[i];
			/*
			 * Verify that the interface matches the destination
			 * address.
			 */
			if ((sin->sin_addr.s_addr & ifp->netmask.s_addr) ==
			    (ifp->address.s_addr & ifp->netmask.s_addr)) {
				if (debug) {
					logdebug("Unicast to %s ",
						 pr_name(sin->sin_addr));
					logdebug("on interface %s\n",
						 pr_name(ifp->address));
				}
				cc = sendto( s, (char *)outpack, packetlen, 0,
					    (struct sockaddr *)sin,
					    sizeof(struct sockaddr));
			} else
				cc = packetlen;
		}
		if( cc < 0 || cc != packetlen )  {
			if (cc < 0) {
				logperror("sendto");
			} else {
				logerr("wrote %s %d chars, ret=%d\n",
				       sendaddress, packetlen, cc );
			}
		}
	}
}

/*
 * 			P R _ T Y P E
 *
 * Convert an ICMP "type" field to a printable string.
 */
char *
pr_type( t )
register int t;
{
	static char *ttab[] = {
		"Echo Reply",
		"ICMP 1",
		"ICMP 2",
		"Dest Unreachable",
		"Source Quench",
		"Redirect",
		"ICMP 6",
		"ICMP 7",
		"Echo",
		"Router Advertise",
		"Router Solicitation",
		"Time Exceeded",
		"Parameter Problem",
		"Timestamp",
		"Timestamp Reply",
		"Info Request",
		"Info Reply",
		"Netmask Request",
		"Netmask Reply"
	};

	if ( t < 0 || t > 16 )
		return("OUT-OF-RANGE");

	return(ttab[t]);
}

/*
 *			P R _ N A M E
 *
 * Return a string name for the given IP address.
 */
char *pr_name(addr)
    struct in_addr addr;
{
	char *inet_ntoa();
	struct hostent *phe;
	static char buf[256];

	phe = gethostbyaddr((char *)&addr.s_addr, 4, AF_INET);
	if (phe == NULL)
		return( inet_ntoa(addr));
	(void) sprintf(buf, "%s (%s)", phe->h_name, inet_ntoa(addr));
	return(buf);
}

/*
 *			P R _ P A C K
 *
 * Print out the packet, if it came from us.  This logic is necessary
 * because ALL readers of the ICMP socket get a copy of ALL ICMP packets
 * which arrive ('tis only fair).  This permits multiple copies of this
 * program to be run without having intermingled output (or statistics!).
 */
void
pr_pack( buf, cc, from )
char *buf;
int cc;
struct sockaddr_in *from;
{
	struct ip *ip;
	register struct icmp *icp;
	register int i;
	int hlen;

	ip = (struct ip *) ALLIGN(buf);
	hlen = ip->ip_hl << 2;
	if (cc < hlen + ICMP_MINLEN) {
		if (verbose)
			logtrace("packet too short (%d bytes) from %s\n", cc,
				 pr_name(from->sin_addr));
		return;
	}
	cc -= hlen;
	icp = (struct icmp *)ALLIGN(buf + hlen);
	if (ip->ip_p == 0) {
		/*
		 * Assume that we are running on a pre-4.3BSD system
		 * such as SunOS before 4.0
		 */
		 icp = (struct icmp *)ALLIGN(buf);
	}
	switch (icp->icmp_type) {
	case ICMP_ROUTER_ADVERTISEMENT: {
		struct icmp *rap = (struct icmp *)ALLIGN(icp);
		struct icmp_ra_addr *ap;

		if (responder)
			break;

		/* TBD verify that the link is multicast or broadcast */
		/* XXX Find out the link it came in over? */
#ifdef notdef
		if (debug) {
			logdebug("ROUTER_ADVERTISEMENT: \n");
			pr_hex(buf+hlen, cc);
		}
#endif notdef
		if (in_cksum((u_short *)ALLIGN(buf+hlen), cc)) {
			if (verbose)
				logtrace("ICMP %s from %s: Bad checksum\n",
					 pr_type((int)rap->icmp_type),
					 pr_name(from->sin_addr));
			return;
		}
		if (rap->icmp_code != 0) {
			if (verbose)
				logtrace("ICMP %s from %s: Code = %d\n",
					 pr_type((int)rap->icmp_type),
					 pr_name(from->sin_addr),
					 rap->icmp_code);
			return;
		}
		if (rap->icmp_num_addrs < 1) {
			if (verbose)
				logtrace("ICMP %s from %s: No addresses\n",
					 pr_type((int)rap->icmp_type),
					 pr_name(from->sin_addr));
			return;
		}
		if (rap->icmp_wpa < 2) {
			if (verbose)
				logtrace("ICMP %s from %s: Words/addr = %d\n",
					 pr_type((int)rap->icmp_type),
					 pr_name(from->sin_addr),
					 rap->icmp_wpa);
			return;
		}
		if ((unsigned)cc <
		    ICMP_MINLEN + rap->icmp_num_addrs * rap->icmp_wpa * 4) {
			if (verbose)
				logtrace("ICMP %s from %s: Too short %d, %d\n",
					      pr_type((int)rap->icmp_type),
					      pr_name(from->sin_addr),
					      cc,
					      ICMP_MINLEN +
					      rap->icmp_num_addrs * rap->icmp_wpa * 4);
			return;
		}
		rap->icmp_lifetime = ntohs(rap->icmp_lifetime);
		if (rap->icmp_lifetime < 4) {
			if (verbose)
				logtrace("ICMP %s from %s: Lifetime = %d\n",
					      pr_type((int)rap->icmp_type),
					      pr_name(from->sin_addr),
					      rap->icmp_lifetime);
			return;
		}
		if (verbose)
			logtrace("ICMP %s from %s, lifetime %d\n",
				      pr_type((int)rap->icmp_type),
				      pr_name(from->sin_addr),
				      rap->icmp_lifetime);

		/* Check that at least one router address is a neighboor
		 * on the arriving link.
		 */
		for (i = 0; (unsigned)i < rap->icmp_num_addrs; i++) {
			struct in_addr ina;
			ap = (struct icmp_ra_addr *)
				ALLIGN(buf + hlen + ICMP_MINLEN +
				       i * rap->icmp_wpa * 4);
			ina.s_addr = ap->ira_addr;
			if (verbose)
				logtrace("\taddress %s, preference 0x%x\n",
					      pr_name(ina),
					      ntohl(ap->ira_preference));
			if (!responder) {
				if (is_directly_connected(ina))
					record_router(ina,
						      (long)ntohl(ap->ira_preference),
						      rap->icmp_lifetime);
			}
		}
		nreceived++;
		if (!forever) {
			do_fork();
			forever = 1;
/*
 * The next line was added so that the alarm is set for the new procces
 * Fraser Gardiner Sun Microsystems Australia
 */
			(void) alarm(TIMER_INTERVAL);
		}
		break;
	}

	case ICMP_ROUTER_SOLICITATION: {
		struct sockaddr_in sin;

		if (!responder)
			break;

		/* TBD verify that the link is multicast or broadcast */
		/* XXX Find out the link it came in over? */
#ifdef notdef
		if (debug) {
			logdebug("ROUTER_SOLICITATION: \n");
			pr_hex(buf+hlen, cc);
		}
#endif notdef
		if (in_cksum((u_short *)ALLIGN(buf+hlen), cc)) {
			if (verbose)
				logtrace("ICMP %s from %s: Bad checksum\n",
					      pr_type((int)icp->icmp_type),
					      pr_name(from->sin_addr));
			return;
		}
		if (icp->icmp_code != 0) {
			if (verbose)
				logtrace("ICMP %s from %s: Code = %d\n",
					      pr_type((int)icp->icmp_type),
					      pr_name(from->sin_addr),
					      icp->icmp_code);
			return;
		}

		if (cc < ICMP_MINLEN) {
			if (verbose)
				logtrace("ICMP %s from %s: Too short %d, %d\n",
					      pr_type((int)icp->icmp_type),
					      pr_name(from->sin_addr),
					      cc,
					      ICMP_MINLEN);
			return;
		}

		if (verbose)
			logtrace("ICMP %s from %s\n",
				      pr_type((int)icp->icmp_type),
				      pr_name(from->sin_addr));

 		if (!responder)
			break;

		/* Check that ip_src is either a neighboor
		 * on the arriving link or 0.
		 */
		sin.sin_family = AF_INET;
		if (ip->ip_src.s_addr == 0) {
			/* If it was sent to the broadcast address we respond
			 * to the broadcast address.
			 */
			if (IN_CLASSD(ip->ip_dst.s_addr))
				sin.sin_addr.s_addr = INADDR_ALLHOSTS_GROUP;
			else
				sin.sin_addr.s_addr = INADDR_BROADCAST;
			/* Restart the timer when we broadcast */
			left_until_advertise = min_adv_int +
				((max_adv_int - min_adv_int)
				 * (random() % 1000)/1000);
		}
		else {
			if (!is_directly_connected(ip->ip_src)) {
				if (verbose)
					logtrace("ICMP %s from %s: source not directly connected\n",
						      pr_type((int)icp->icmp_type),
						      pr_name(from->sin_addr));
				break;
			}
			sin.sin_addr.s_addr = ip->ip_src.s_addr;
		}
		nreceived++;
		ntransmitted++;
		advertise(&sin);
		break;
	}
	} /* end switch */
}


/*
 *			I N _ C K S U M
 *
 * Checksum routine for Internet Protocol family headers (C Version)
 *
 */
int
in_cksum(addr, len)
u_short *addr;
int len;
{
	register int nleft = len;
	register u_short *w = addr;
	register u_short answer;
	u_short odd_byte = 0;
	register int sum = 0;

	/*
	 *  Our algorithm is simple, using a 32 bit accumulator (sum),
	 *  we add sequential 16 bit words to it, and at the end, fold
	 *  back all the carry bits from the top 16 bits into the lower
	 *  16 bits.
	 */
	while( nleft > 1 )  {
		sum += *w++;
		nleft -= 2;
	}

	/* mop up an odd byte, if necessary */
	if( nleft == 1 ) {
		*(u_char *)(&odd_byte) = *(u_char *)w;
		sum += odd_byte;
	}

	/*
	 * add back carry outs from top 16 bits to low 16 bits
	 */
	sum = (sum >> 16) + (sum & 0xffff);	/* add hi 16 to low 16 */
	sum += (sum >> 16);			/* add carry */
	answer = ~sum;				/* truncate to 16 bits */
	return (answer);
}

/*
 *			F I N I S H
 *
 * Print out statistics, and give up.
 * Heavily buffered STDIO is used here, so that all the statistics
 * will be written with 1 sys-write call.  This is nice when more
 * than one copy of the program is running on a terminal;  it prevents
 * the statistics output from becomming intermingled.
 */
void
finish(sig)
	int sig;
{
	if (responder) {
		int i;

		/* Send out a packet with a preference so that all
		 * hosts will know that we are dead.
		 */
		logerr("terminated\n");
		for (i = 0; i < num_interfaces; i++)
			interfaces[i].preference = IGNORE_PREFERENCE;
		ntransmitted++;
		advertise(&whereto);
	}
	if (verbose) {
	    logtrace("\n----%s rdisc Statistics----\n", sendaddress );
	    logtrace("%d packets transmitted, ", ntransmitted );
	    logtrace("%d packets received, ", nreceived );
	    (void) fflush(stdout);
	}
	exit(0);
}

#include <ctype.h>

#ifdef notdef
pr_hex(data, len)
int len;
unsigned char *data;
{
	FILE *out;

	out = stdout;

	while (len) {
		register int i;
		char charstring[17];

		(void)strcpy(charstring,"                "); /* 16 spaces */
		for (i = 0; i < 16; i++) {
			/* output the bytes one at a time,
			 * not going pas "len" bytes
			 */
			if (len) {
				char ch = *data & 0x7f; /* strip parity */
				if (!isprint((u_char)ch))
					ch = ' '; /* ensure printable */
				charstring[i] = ch;
				(void) fprintf(out,"%02x ",*data++);
				len--;
			} else
				(void) fprintf(out,"   ");
			if (i==7)
				(void) fprintf(out,"   ");
		}

		(void) fprintf(out,"    *%s*\n",charstring);
	}
}
#endif notdef

int
isbroadcast(sin)
	struct sockaddr_in *sin;
{
	return (sin->sin_addr.s_addr == INADDR_BROADCAST);
}

int
ismulticast(sin)
	struct sockaddr_in *sin;
{
	return (IN_CLASSD(ntohl(sin->sin_addr.s_addr)));
}

/* From libc/rpc/pmap_rmt.c */

int
sendbcast(s, packet, packetlen)
	int s;
	char *packet;
	int packetlen;
{
	int i, cc;

	for (i = 0; i < num_interfaces; i++) {
		if ((interfaces[i].flags & IFF_BROADCAST) == 0)
			continue;
		cc = sendbcastif(s, packet, packetlen, &interfaces[i]);
		if (cc!= packetlen) {
			return (cc);
		}
	}
	return (packetlen);
}

int
sendbcastif(s, packet, packetlen, ifp)
	int s;
	char *packet;
	int packetlen;
	struct interface *ifp;
{
	int cc;
	struct sockaddr_in baddr;

	baddr.sin_family = AF_INET;

	if ((ifp->flags & IFF_BROADCAST) == 0)
		return (packetlen);

	baddr.sin_addr = ifp->bcastaddr;
	if (debug)
		logdebug("Broadcast to %s\n",
			 pr_name(baddr.sin_addr));
	cc = sendto(s, packet, packetlen, 0,
		    (struct sockaddr *)&baddr, sizeof (struct sockaddr));
	if (cc!= packetlen) {
		logperror("sendbcast: sendto");
		logerr("Cannot send broadcast packet to %s\n",
		       pr_name(baddr.sin_addr));
	}
	return (cc);
}

int
sendmcast(s, packet, packetlen, sin)
	int s;
	char *packet;
	int packetlen;
	struct sockaddr_in *sin;
{
	int i, cc;

	for (i = 0; i < num_interfaces; i++) {
		if ((interfaces[i].flags & IFF_MULTICAST) == 0)
			continue;
		cc = sendmcastif(s, packet, packetlen, sin, &interfaces[i]);
		if (cc!= packetlen) {
			return (cc);
		}
	}
	return (packetlen);
}

int
sendmcastif(s, packet, packetlen, sin, ifp)
	int s;
	char *packet;
	int packetlen;
	struct sockaddr_in *sin;
	struct interface *ifp;
{
	int cc;
	struct sockaddr_in ifaddr;

	ifaddr.sin_family = AF_INET;

	if ((ifp->flags & IFF_MULTICAST) == 0)
		return (packetlen);

	ifaddr.sin_addr = ifp->address;
	if (debug)
		logdebug("Multicast to interface %s\n",
			 pr_name(ifaddr.sin_addr));
	if (setsockopt(s, IPPROTO_IP, IP_MULTICAST_IF,
		       (char *)&ifaddr.sin_addr,
		       sizeof(ifaddr.sin_addr)) < 0) {
		logperror("setsockopt (IP_MULTICAST_IF)");
		logerr("Cannot send multicast packet over interface %s\n",
		       pr_name(ifaddr.sin_addr));
		return (-1);
	}
	cc = sendto(s, packet, packetlen, 0,
		    (struct sockaddr *)sin, sizeof (struct sockaddr));
	if (cc!= packetlen) {
		logperror("sendmcast: sendto");
		logerr("Cannot send multicast packet over interface %s\n",
		       pr_name(ifaddr.sin_addr));
	}
	return (cc);
}

void
init()
{
	int i;

	initifs(0);
	for (i = 0; i < interfaces_size; i++)
		interfaces[i].preference = preference;
}

void
initifs(sig)
	int sig;
{
	int	sock;
	struct ifconf ifc;
	struct ifreq ifreq, *ifrp, *ifend;
	struct sockaddr_in *sin;
	int n, i;
	char *buf;
	int numifs;
	unsigned bufsize;

	sock = socket(AF_INET, SOCK_DGRAM, 0);
	if (sock < 0) {
		logperror("initifs: socket");
		return;
	}
#ifdef SIOCGIFNUM
	if (ioctl(sock, SIOCGIFNUM, (char *)&numifs) < 0) {
		numifs = MAXIFS;
	}
#else
	numifs = MAXIFS;
#endif
	bufsize = numifs * sizeof(struct ifreq);
	buf = (char *)malloc(bufsize);
	if (buf == NULL) {
		logerr("out of memory\n");
		(void) close(sock);
		return;
	}
	bzero(buf, bufsize);
	if (interfaces)
		interfaces = (struct interface *)ALLIGN(realloc((char *)interfaces,
					 numifs * sizeof(struct interface)));
	else
		interfaces = (struct interface *)ALLIGN(malloc(numifs *
						sizeof(struct interface)));
	if (interfaces == NULL) {
		logerr("out of memory\n");
		(void) close(sock);
		(void) free(buf);
		return;
	}
	interfaces_size = numifs;

	ifc.ifc_len = bufsize;
	ifc.ifc_buf = buf;
	if (ioctl(sock, SIOCGIFCONF, (char *)&ifc) < 0) {
		logperror("initifs: ioctl (get interface configuration)");
		(void) close(sock);
		(void) free(buf);
		return;
	}
	ifrp = (struct ifreq *)buf;
	ifend = (struct ifreq *)(buf + ifc.ifc_len);
	for (i = 0; ifrp < ifend; ifrp = (struct ifreq *)((char *)ifrp + n)) {
		ifreq = *ifrp;
		if (ioctl(sock, SIOCGIFFLAGS, (char *)&ifreq) < 0) {
			logperror("initifs: ioctl (get interface flags)");
			n = 0;
			continue;
		}
		n = ifrp->ifr_addr.sa_len + sizeof(ifrp->ifr_name);
		if (n < sizeof(*ifrp))
		    n = sizeof(*ifrp);
		if (ifrp->ifr_addr.sa_family != AF_INET)
			continue;
		if ((ifreq.ifr_flags & IFF_UP) == 0)
			continue;
		if (ifreq.ifr_flags & IFF_LOOPBACK)
			continue;
		if ((ifreq.ifr_flags & (IFF_MULTICAST | IFF_BROADCAST)) == 0)
			continue;
		sin = (struct sockaddr_in *)ALLIGN(&ifrp->ifr_addr);
		interfaces[i].localaddr = sin->sin_addr;
		interfaces[i].flags = ifreq.ifr_flags;
		interfaces[i].netmask.s_addr = (unsigned long)0xffffffff;
		if (ifreq.ifr_flags & IFF_POINTOPOINT) {
			if (ioctl(sock, SIOCGIFDSTADDR, (char *)&ifreq) < 0) {
				logperror("initifs: ioctl (get destination addr)");
				continue;
			}
			sin = (struct sockaddr_in *)ALLIGN(&ifreq.ifr_addr);
			/* A pt-pt link is identified by the remote address */
			interfaces[i].address = sin->sin_addr;
			interfaces[i].remoteaddr = sin->sin_addr;
			/* Simulate broadcast for pt-pt */
			interfaces[i].bcastaddr = sin->sin_addr;
			interfaces[i].flags |= IFF_BROADCAST;
		} else {
			/* Non pt-pt links are identified by the local address */
			interfaces[i].address = interfaces[i].localaddr;
			interfaces[i].remoteaddr = interfaces[i].address;
			if (ioctl(sock, SIOCGIFNETMASK, (char *)&ifreq) < 0) {
				logperror("initifs: ioctl (get netmask)");
				continue;
			}
			sin = (struct sockaddr_in *)ALLIGN(&ifreq.ifr_addr);
			interfaces[i].netmask = sin->sin_addr;
			if (ifreq.ifr_flags & IFF_BROADCAST) {
				if (ioctl(sock, SIOCGIFBRDADDR, (char *)&ifreq) < 0) {
					logperror("initifs: ioctl (get broadcast address)");
					continue;
				}
				sin = (struct sockaddr_in *)ALLIGN(&ifreq.ifr_addr);
				interfaces[i].bcastaddr = sin->sin_addr;
			}
		}
#ifdef notdef
		if (debug)
			logdebug("Found interface %s, flags 0x%x\n",
				 pr_name(interfaces[i].localaddr),
				 interfaces[i].flags);
#endif
		i++;
	}
	num_interfaces = i;
#ifdef notdef
	if (debug)
		logdebug("Found %d interfaces\n", num_interfaces);
#endif
	(void) close(sock);
	(void) free(buf);
}

int
join(sock, sin)
	int sock;
	struct sockaddr_in *sin;
{
	int i;
	struct ip_mreq mreq;

	if (isbroadcast(sin))
		return (0);

	mreq.imr_multiaddr = sin->sin_addr;
	for (i = 0; i < num_interfaces; i++) {
		mreq.imr_interface = interfaces[i].address;

		if (setsockopt(sock, IPPROTO_IP, IP_ADD_MEMBERSHIP,
			       (char *)&mreq, sizeof(mreq)) < 0) {
			logperror("setsockopt (IP_ADD_MEMBERSHIP)");
			return (-1);
		}
	}
	return (0);
}

int support_multicast()
{
	int sock;
	u_char ttl = 1;

	sock = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP);
	if (sock < 0) {
		logperror("support_multicast: socket");
		return (0);
	}

	if (setsockopt(sock, IPPROTO_IP, IP_MULTICAST_TTL,
		       (char *)&ttl, sizeof(ttl)) < 0) {
		(void) close(sock);
		return (0);
	}
	(void) close(sock);
	return (1);
}

int
is_directly_connected(in)
	struct in_addr in;
{
	int i;

	for (i = 0; i < num_interfaces; i++) {
		/* Check that the subnetwork numbers match */

		if ((in.s_addr & interfaces[i].netmask.s_addr ) ==
		    (interfaces[i].remoteaddr.s_addr & interfaces[i].netmask.s_addr))
			return (1);
	}
	return (0);
}


/*
 * TABLES
 */
struct table {
	struct in_addr	router;
	int		preference;
	int		remaining_time;
	int		in_kernel;
	struct table	*next;
};

struct table *table;

struct table *
find_router(addr)
	struct in_addr addr;
{
	struct table *tp;

	tp = table;
	while (tp) {
		if (tp->router.s_addr == addr.s_addr)
			return (tp);
		tp = tp->next;
	}
	return (NULL);
}

int max_preference()
{
	struct table *tp;
	int max = (int)IGNORE_PREFERENCE;

	tp = table;
	while (tp) {
		if (tp->preference > max)
			max = tp->preference;
		tp = tp->next;
	}
	return (max);
}


/* Note: this might leave the kernel with no default route for a short time. */
void
age_table(time)
	int time;
{
	struct table **tpp, *tp;
	int recalculate_max = 0;
	int max = max_preference();

	tpp = &table;
	while (*tpp != NULL) {
		tp = *tpp;
		tp->remaining_time -= time;
		if (tp->remaining_time <= 0) {
			*tpp = tp->next;
			if (tp->in_kernel)
				del_route(tp->router);
			if (best_preference &&
			    tp->preference == max)
				recalculate_max++;
			free((char *)tp);
		} else {
			tpp = &tp->next;
		}
	}
	if (recalculate_max) {
		int max = max_preference();

		if (max != IGNORE_PREFERENCE) {
			tp = table;
			while (tp) {
				if (tp->preference == max && !tp->in_kernel) {
					add_route(tp->router);
					tp->in_kernel++;
				}
				tp = tp->next;
			}
		}
	}
}

void
record_router(router, preference, ttl)
	struct in_addr 	router;
	long		preference;
	int		ttl;
{
	struct table *tp;
	int old_max = max_preference();
	int changed_up = 0;	/* max preference could have increased */
	int changed_down = 0;	/* max preference could have decreased */

	if (debug)
		logdebug("Recording %s, preference 0x%x\n",
			      pr_name(router),
			      preference);
	tp = find_router(router);
	if (tp) {
		if (tp->preference > preference &&
		    tp->preference == old_max)
			changed_down++;
		else if (preference > tp->preference)
			changed_up++;
		tp->preference = preference;
		tp->remaining_time = ttl;
	} else {
		if (preference > old_max)
			changed_up++;
		tp = (struct table *)ALLIGN(malloc(sizeof(struct table)));
		if (tp == NULL) {
			logerr("Out of memory\n");
			return;
		}
		tp->router = router;
		tp->preference = preference;
		tp->remaining_time = ttl;
		tp->in_kernel = 0;
		tp->next = table;
		table = tp;
	}
	if (!tp->in_kernel &&
	    (!best_preference || tp->preference == max_preference()) &&
	    tp->preference != IGNORE_PREFERENCE) {
		add_route(tp->router);
		tp->in_kernel++;
	}
	if (tp->preference == IGNORE_PREFERENCE && tp->in_kernel) {
		del_route(tp->router);
		tp->in_kernel = 0;
	}
	if (best_preference && changed_down) {
		/* Check if we should add routes */
		int new_max = max_preference();
		if (new_max != IGNORE_PREFERENCE) {
			tp = table;
			while (tp) {
				if (tp->preference == new_max &&
				    !tp->in_kernel) {
					add_route(tp->router);
					tp->in_kernel++;
				}
				tp = tp->next;
			}
		}
	}
	if (best_preference && (changed_up || changed_down)) {
		/* Check if we should remove routes already in the kernel */
		int new_max = max_preference();
		tp = table;
		while (tp) {
			if (tp->preference < new_max && tp->in_kernel) {
				del_route(tp->router);
				tp->in_kernel = 0;
			}
			tp = tp->next;
		}
	}
}


#include <net/route.h>

void
add_route(addr)
	struct in_addr addr;
{
	if (debug)
		logdebug("Add default route to %s\n", pr_name(addr));
	rtioctl(addr, RTM_ADD);
}

void
del_route(addr)
	struct in_addr addr;
{
	if (debug)
		logdebug("Delete default route to %s\n", pr_name(addr));
	rtioctl(addr, RTM_DELETE);
}

void
rtioctl(addr, op)
	struct in_addr addr;
	int	op;
{
	int sock;
	struct {
		struct rt_msghdr m_rtm;
		struct sockaddr_in m_dst;
		struct sockaddr_in m_gateway;
		struct sockaddr_in m_netmask;
	} m_rtmsg;
	static int seq = 0;

	bzero(&m_rtmsg, sizeof(m_rtmsg));

	m_rtmsg.m_rtm.rtm_type = op;
	m_rtmsg.m_rtm.rtm_flags = RTF_GATEWAY | RTF_UP;		/* XXX more? */
	m_rtmsg.m_rtm.rtm_version = RTM_VERSION;
	m_rtmsg.m_rtm.rtm_seq = ++seq;
	m_rtmsg.m_rtm.rtm_addrs = RTA_DST | RTA_GATEWAY | RTA_NETMASK;

	/* initialise route metrics to zero.. tcp may fill these in */
	bzero(&m_rtmsg.m_rtm.rtm_rmx, sizeof(m_rtmsg.m_rtm.rtm_rmx));

	m_rtmsg.m_rtm.rtm_inits = 0;
	m_rtmsg.m_rtm.rtm_msglen = sizeof(m_rtmsg);

	m_rtmsg.m_dst.sin_len = sizeof(struct sockaddr_in);
	m_rtmsg.m_dst.sin_family = AF_INET;
	m_rtmsg.m_dst.sin_addr.s_addr = 0;		/* default */

	/* XXX: is setting a zero length right? */
	m_rtmsg.m_netmask.sin_len = 0;
	m_rtmsg.m_netmask.sin_family = AF_INET;
	m_rtmsg.m_netmask.sin_addr.s_addr = 0;		/* default */

	m_rtmsg.m_gateway.sin_len = sizeof(struct sockaddr_in);
	m_rtmsg.m_gateway.sin_family = AF_INET;
	m_rtmsg.m_gateway.sin_addr = addr;		/* gateway */

	sock = socket(PF_ROUTE, SOCK_RAW, 0);
	if (sock < 0) {
		logperror("rtioctl: socket");
		return;
	}
	if (write(sock, &m_rtmsg, sizeof(m_rtmsg)) != sizeof(m_rtmsg)) {
		if (!(op == RTM_ADD && errno == EEXIST))
			logperror("rtioctl: write");
	}
	close(sock);
}



/*
 * LOGGER
 */


static int logging = 0;

void
initlog()
{
	logging++;
	openlog("rdisc", LOG_PID | LOG_CONS, LOG_DAEMON);
}

/* VARARGS1 */
void
#if __STDC__
logerr(char *fmt, ...)
#else
logerr(fmt, va_alist)
	char *fmt;
	va_dcl
#endif
{
	va_list ap;

#if __STDC__
	va_start(ap, fmt);
#else
	va_start(ap);
#endif

	if (logging)
		vsyslog(LOG_ERR, fmt, ap);
	else
		(void) vfprintf(stderr, fmt, ap);

	va_end(ap);
}

/* VARARGS1 */
void
#if __STDC__
logtrace(char *fmt, ...)
#else
logtrace(fmt, va_alist)
	char *fmt;
	va_dcl
#endif
{
	va_list ap;

#if __STDC__
	va_start(ap, fmt);
#else
	va_start(ap);
#endif

	if (logging)
		vsyslog(LOG_INFO, fmt, ap);
	else
		(void) vfprintf(stdout, fmt, ap);

	va_end(ap);
}

/* VARARGS1 */
void
#if __STDC__
logdebug(char *fmt, ...)
#else
logdebug(fmt, va_alist)
	char *fmt;
	va_dcl
#endif
{
	va_list ap;

#if __STDC__
	va_start(ap, fmt);
#else
	va_start(ap);
#endif

	if (logging)
		vsyslog(LOG_DEBUG, fmt, ap);
	else
		(void) vfprintf(stdout, fmt, ap);

	va_end(ap);
}

void
logperror(str)
	char *str;
{
	if (logging)
		syslog(LOG_ERR, "%s: %m", str);
	else
		(void) warn("%s", str);
}
