/*
 * Copyright (c) 2004, Stockholms universitet
 * (Stockholm University, Stockholm Sweden)
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 *
 * 1. Redistributions of source code must retain the above copyright
 *    notice, this list of conditions and the following disclaimer.
 *
 * 2. Redistributions in binary form must reproduce the above copyright
 *    notice, this list of conditions and the following disclaimer in the
 *    documentation and/or other materials provided with the distribution.
 *
 * 3. Neither the name of the university nor the names of its contributors
 *    may be used to endorse or promote products derived from this software
 *    without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
 * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
 * ARE DISCLAIMED.  IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
 * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
 * POSSIBILITY OF SUCH DAMAGE.
 */

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

#include <net/if.h>
#include <net/bpf.h>
#include <net/if_arp.h>
#include <net/if_dl.h>
#include <net/if_types.h>
#include <netinet/in.h>
#if defined(__NetBSD__)
#include <net/ethertypes.h>
#include <net/if_ether.h>
#elif defined(__FreeBSD__)
#include <net/ethernet.h>
#endif

#include <arpa/inet.h>

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

static char source_mac[ETHER_ADDR_LEN], target_mac[ETHER_ADDR_LEN];
static struct in_addr source_ip, target_ip, source_mask;
static char *interface_name;
static int got_packet;

#ifdef __FreeBSD__

int
open_write_bpf(const char *interface)
{
    int fd, n = 0;
    char device[sizeof "/dev/bpf0000000000"];
    struct ifreq ifr;

    do {
	snprintf(device, sizeof(device), "/dev/bpf%d", n++);
	fd = open(device, O_RDWR);
    } while (fd < 0 && errno == EBUSY);

    if (fd < 0)
	err(1, "no fd left or us, tried up to %d", n); 

    memset(&ifr, 0, sizeof(ifr));
    strncpy(ifr.ifr_name, interface, sizeof(ifr.ifr_name));
    if (ioctl(fd, BIOCSETIF, (caddr_t)&ifr) < 0)
	errx(1, "can't set interface for write fd %s", interface);

    return (fd);
}
#endif


static void
print_arp(u_char *data, const struct pcap_pkthdr *hdr, const u_char *pkt)
{
    struct ether_header ehdr;
    struct arphdr *ah;
    size_t len = hdr->caplen;

    if (len < ETHER_HDR_LEN)
	return;

    memcpy(&ehdr, pkt, ETHER_HDR_LEN);
    pkt += ETHER_HDR_LEN;
    len -= ETHER_HDR_LEN;

    if (len < sizeof(*ah))
	return;

    ah = (void *)pkt;
    len -= sizeof(*ah);
    pkt += sizeof(*ah);

    if (ah->ar_hln != ETHER_ADDR_LEN)
	return;
    if (ah->ar_pln != sizeof(struct in_addr))
	return;
    if (ah->ar_op != htons(ARPOP_REPLY))
	return;
    if (ah->ar_hrd != htons(ARPHRD_ETHER))
	return;
    if (ah->ar_pro != htons(ETHERTYPE_IP))
	return;
    if (len < ah->ar_pln * 2 + ah->ar_hln * 2)
	return;

    if (memcmp(ar_spa(ah), &target_ip, ah->ar_pln) != 0)
	return;
    if (memcmp(ar_tpa(ah), &source_ip, ah->ar_pln) != 0)
	return;

    memcpy(target_mac, ar_sha(ah), ah->ar_hln);
    got_packet = 1;
}

static void
make_query(int fd)
{
    char pkt[200], *p;
    size_t len = 0;
    struct ether_header *ehdr;
    struct arphdr *ah;
	
    memset(pkt, 0, sizeof(pkt));

    p = pkt;
    ehdr = (void *)p;
    p += ETHER_HDR_LEN;
    len += ETHER_HDR_LEN;

    memset(ehdr->ether_dhost, 0xff, ETHER_ADDR_LEN);
    ehdr->ether_type = htons(ETHERTYPE_ARP);

    ah = (void *)p;
    p += sizeof(*ah);
    len += sizeof(*ah);

    ah->ar_hrd = htons(ARPHRD_ETHER);
    ah->ar_pro = htons(ETHERTYPE_IP);
    ah->ar_hln = ETHER_ADDR_LEN;
    ah->ar_pln = sizeof(struct in_addr);
    ah->ar_op = htons(ARPOP_REQUEST);

    memcpy(ar_sha(ah), source_mac, ah->ar_hln);
    memcpy(ar_spa(ah), &source_ip, ah->ar_pln);
    memcpy(ar_tpa(ah), &target_ip, ah->ar_pln);

    len += ah->ar_pln * 2 + ah->ar_hln * 2;

    /* XXX this should really be pcap_inject */
    write(fd, pkt, len);
}

static int
find_boot_info(const char *ipaddr)
{
    struct ifaddrs *ifp, *ifp0;
    struct in_addr t, s, m;
    int ret;

    t.s_addr = inet_addr(ipaddr);

    ret = getifaddrs(&ifp0);
    if (ret)
	err(1, "getifaddrs");

    /* find matching interface */
    for (ifp = ifp0 ; ifp ; ifp = ifp->ifa_next) {
	if (ifp->ifa_addr == NULL || ifp->ifa_netmask == NULL)
	    continue;
	if (ifp->ifa_addr->sa_family != AF_INET)
	    continue;
	s = ((struct sockaddr_in *)ifp->ifa_addr)->sin_addr;
	m = ((struct sockaddr_in *)ifp->ifa_netmask)->sin_addr;

	if ((t.s_addr & m.s_addr) != (s.s_addr & m.s_addr))
	    continue;
	break;
    }
    if (ifp == NULL || ifp->ifa_name == NULL)
	errx(1, "failed to find interface");

    interface_name = strdup(ifp->ifa_name);

    for (ifp = ifp0 ; ifp ; ifp = ifp->ifa_next) {
	struct sockaddr_dl *a;

	if (ifp->ifa_name == NULL)
	    continue;
	if (ifp->ifa_addr->sa_family != AF_LINK)
	    continue;
	if (strcmp(interface_name, ifp->ifa_name) != 0)
	    continue;
	a = (struct sockaddr_dl *)ifp->ifa_addr;
	if (a->sdl_type != IFT_ETHER)
	    continue;
	if (a->sdl_alen != ETHER_ADDR_LEN)
	    continue;
	memcpy(source_mac, LLADDR(a), ETHER_ADDR_LEN);
	break;
    }
    if (ifp == NULL)
	errx(1, "failed to find interface ethernet address");

    freeifaddrs(ifp0);

    source_ip = s;
    source_mask = m;
    target_ip = t;

    return 0;
}


int
main(int argc, char **argv)
{
    char ebuf[PCAP_ERRBUF_SIZE];
    struct bpf_program prog;
    pcap_t *p;
    int ret, fd, write_fd;
    fd_set fds;
    int one_shot = 0;
    int know_it = 0;
    int times_no_reply = 0;
    u_char addr[ETHER_ADDR_LEN];

    if (argc < 2)
	errx(1, "argc < 2");

    find_boot_info(argv[1]);

    printf("interface: %s\n", interface_name);
    
    p = pcap_open_live(interface_name, 100, 0, 10000, ebuf);
    if (p == NULL)
	errx(1, "pcap_open_live: %s", ebuf);

    /* only list on reply's */
    ret = pcap_compile(p, &prog, 
		       "arp and arp[7] = 2 and arp[8] = 0", 
		       1, source_mask.s_addr);
    if (ret != 0)
	errx(1, "pcap_compile_program = %d\n", ret);

    pcap_setfilter(p, &prog);

    fd = pcap_fileno(p);

    {
	u_int arg = 1;
	ret = ioctl(fd, BIOCIMMEDIATE, &arg);
	if (ret < 0)
	    warn("ioctl BIOCIMMEDIATE");
    }

#ifdef __FreeBSD__
    write_fd = open_write_bpf(interface_name);
#else
    write_fd = fd;
#endif

    make_query(write_fd);
    
    while (1) {
	struct timeval tm;
	FD_ZERO(&fds);
	FD_SET(fd, &fds);

	tm.tv_sec = 3;
	tm.tv_usec = 0;

	ret = select(fd + 1, &fds, NULL, NULL, &tm);
	if (ret < 0)
	    warn("select");
	else if (ret == 0) {
	    times_no_reply++;
	    if (times_no_reply > 5)
		errx(1, "not no longer there");
	    make_query(write_fd);
	} else
	    pcap_dispatch(p, -1, print_arp, NULL);

	if (got_packet || one_shot) {
	    int i;
	    if (know_it == 0) {
		memcpy(addr, target_mac, ETHER_ADDR_LEN);
		know_it = 1;
	    }
	    if (memcmp(addr, target_mac, ETHER_ADDR_LEN) != NULL)
		errx(1, "mac addr changed");
	    printf("remote host have mac address: ");
	    for (i = 0; i < ETHER_ADDR_LEN; i++)
		printf("%02x%s", (unsigned char)target_mac[i],
		       i < ETHER_ADDR_LEN - 1 ? ":" : "");
	    printf("\n");
	    got_packet = 0;
	    times_no_reply = 0;
	    if (one_shot)
		break;
	}
    }

    pcap_close(p);
    if (fd != write_fd)
	close(write_fd);

    return 0;
}

