/* $Header: /mit/nit/src/nip/RCS/verify.c,v 1.11 90/01/05 12:12:09 mar Exp $
 *
 * Verify that an address is not in use (uses ARP)
 */

#include <stdio.h>
#include <strings.h>
#include <sys/types.h>
#include <sys/time.h>
#include <sys/socket.h>
#include <net/if.h>
#include <netinet/in.h>
#include <netinet/if_ether.h>
#include <sys/ioctl.h>
#include <net/nit.h>
#include <net/nip.h>
#include "netconfig.h"


extern int debug;


verify(s, ni, pa)
int	s;
struct nipinfo *ni;
u_char	*pa;
{
    int i, rfds, xfds, count, errorcount = 0;
    struct sockaddr sa;
    struct nit_ioc nic;
    struct ether_header *eh;
    struct ether_arp *ea;
    struct nit_hdr *nh;
    unsigned char pkt[sizeof(*nh) + sizeof(*eh) + sizeof(*ea)];
    struct timeval starttime, tmout;

    if (debug)
      printf("verifying address %s\n", inet_ntoa(ni->ni_recommend));

    if ((ni->ni_recommend & ni->ni_netmask) != ni->ni_netaddr) {
	fprintf(stderr, "Address %s is not valid on this network.\n",
		inet_ntoa(ni->ni_recommend));
	return(ERROR);
    }
    if (((ntohl(ni->ni_recommend) & 0x000000ff) < 1) ||
	((ntohl(ni->ni_recommend) & 0x000000ff) > 254) ||
	(ni->ni_recommend == ni->ni_gateway)) {
	fprintf(stderr, "Address %s is a reserved address.\n",
		inet_ntoa(ni->ni_recommend));
	return(ERROR);
    }

    nic.nioc_bufspace = BUFSIZE;
    nic.nioc_chunksize = sizeof(struct nit_hdr) +
      sizeof(struct ether_header) + sizeof(struct ether_arp);
    nic.nioc_typetomatch = ETHERTYPE_ARP;
    nic.nioc_snaplen = sizeof(struct ether_arp) + sizeof(struct ether_header);
    nic.nioc_flags = 0;
    if (ioctl(s, SIOCSNIT, &nic) < 0) {
	perror("ioctl");
	return(ERROR);
    }

    nh = (struct nit_hdr *) &pkt[0];
    eh = (struct ether_header *) &pkt[sizeof(*nh)];
    ea = (struct ether_arp *) &pkt[sizeof(*nh) + sizeof(*eh)];

    for (count = 0; count < ARPTIMES; count++) {
	if (debug > 5)
	  printf("ARP %d\n", count+1);
	/* Compose and send request */
	bzero(&pkt[0], sizeof(pkt));
	ea->ea_hdr.ar_hrd = htons(ARPHRD_ETHER);
	ea->ea_hdr.ar_pro = htons(ETHERTYPE_IP);
	ea->ea_hdr.ar_hln = sizeof(struct ether_addr);
	ea->ea_hdr.ar_pln = sizeof(struct in_addr);
	ea->ea_hdr.ar_op = htons(ARPOP_REQUEST);
	bcopy(pa, &(ea->arp_sha[0]), sizeof(struct ether_addr));
	bcopy(&ni->ni_recommend, &(ea->arp_tpa[0]), sizeof(long));
	sa.sa_family = AF_UNSPEC;
	eh = (struct ether_header *) &(sa.sa_data[0]);
	for (i = 0; i < 6; i++)
	  eh->ether_dhost[i] = -1;
	eh->ether_type = ETHERTYPE_ARP;
	if (sendto(s, ea, sizeof(*ea), 0, &sa, sizeof(sa)) < 0) {
	    perror("sendto");
	    return(ERROR);
	}

	/* wait for replies */
	gettimeofday(&starttime, NULL);
	starttime.tv_sec += ARPWAIT;
	rfds = xfds = (1 << s);
	while (1) {
	    gettimeofday(&tmout, NULL);
	    if (timercmp(&tmout, &starttime, >=)) {
		if (debug > 5)
		  printf("timed out\n");
		break;
	    }
	    tmout.tv_sec = starttime.tv_sec - tmout.tv_sec;
	    tmout.tv_usec = starttime.tv_usec - tmout.tv_usec;
	    if (tmout.tv_usec < 0) {
		tmout.tv_usec += 1000000;
		tmout.tv_sec--;
	    }
	    if ((i = select(s+1, &rfds, NULL, &xfds, &tmout)) == 0)
	      break;
#ifdef DEBUG
	    printf("select returned %d, starting read ", i);
#endif

	    /* get & validate packet */
	    i = read(s, pkt, sizeof(pkt));
	    if (debug > 5)
	      printf("Got one! ");

#ifdef DEBUG
	    printf("sender: %d.%d.%d.%d %02x%02x%02x%02x%02x%02x\n",
		   ea->arp_spa[0],ea->arp_spa[1],ea->arp_spa[2],ea->arp_spa[3],
		   ea->arp_sha[0],ea->arp_sha[1],ea->arp_sha[2],
		   ea->arp_sha[3],ea->arp_sha[4],ea->arp_sha[5]);
	    printf("target: %d.%d.%d.%d %02x%02x%02x%02x%02x%02x\n",
		   ea->arp_tpa[0],ea->arp_tpa[1],ea->arp_tpa[2],ea->arp_tpa[3],
		   ea->arp_tha[0],ea->arp_tha[1],ea->arp_tha[2],
		   ea->arp_tha[3],ea->arp_tha[4],ea->arp_tha[5]);
#endif

	    if (nh->nh_state != NIT_CATCH) {
		if (errorcount++ > 5) {
		    fprintf(stderr, "Too many errors verifying address.\n");
		    return(ERROR);
		}
		count--;
		continue;
	    }

	    if ((nh->nh_datalen < sizeof(*eh) + sizeof(*ea)) ||
		(ea->arp_hrd != htons(ARPHRD_ETHER)) ||
		(ea->arp_pro != htons(ETHERTYPE_IP)) ||
		(ea->arp_hln != sizeof(struct ether_addr)) ||
		(ea->arp_pln != sizeof(struct in_addr)) ||
		(ea->arp_op != htons(ARPOP_REPLY)) ||
		(ntohl(_getlong(&(ea->arp_spa[0]))) != ni->ni_recommend))
	      continue;

	    if (debug)
	      printf("address is in use\n");
	    return(FAIL);
	}
    }
    if (debug)
      printf("address passed\n");
    return(OK);
}
