#include <stdio.h>
#include <errno.h>

#include <sys/types.h>
#include <asm/types.h>		/* for u32 */
#include <sys/socket.h>
#include <sys/ioctl.h>

#include <net/ethernet.h> 
#include <net/if.h>
#include <net/if_arp.h>

#include <netinet/in.h>		/* for struct in_addr */

typedef unsigned int u32;
static int ioctl_sockid = 0;

#define mip0dbg(a) printf a

typedef struct arhdr{
    unsigned short  ar_hrd;         /* format of hardware address   */
    unsigned short  ar_pro;         /* format of protocol address   */
    unsigned char   ar_hln;         /* length of hardware address   */
    unsigned char   ar_pln;         /* length of protocol address   */
    unsigned short  ar_op;          /* ARP opcode (command)         */
    unsigned char ar_sha[ETH_ALEN]; /* sender hardware address      */
    unsigned char ar_sip[4];        /* sender IP address            */
    unsigned char ar_tha[ETH_ALEN]; /* target hardware address      */
    unsigned char ar_tip[4];        /* target IP address            */
} Arphdr;

/* Fcn to create ioctl_sockid */
static void initSock()
{
  if (!ioctl_sockid) {
    /* Make a socket for the various SIOCxxxx ioctl calls */
    if ((ioctl_sockid = socket(AF_INET, SOCK_DGRAM, 0)) < 0) {
        mip0dbg(("Error: could not create socket for SIOCxxxx commands.\n"));
	exit(-1);
    }
  }
}

static int getIPAddr (char *dev, struct sockaddr *addr,
		      struct sockaddr *netmask)
{
    struct ifreq ifr;
    char *buf;
  
    errno = 0;
    if (addr) {
      bzero((char *)&ifr, sizeof(ifr));
      strncpy(ifr.ifr_name, dev, strlen(dev)); 
      if (ioctl(ioctl_sockid, SIOCGIFADDR, (char *)&ifr) < 0) 
        return(-1 * errno);
      memcpy(addr, &ifr.ifr_addr, sizeof (*addr));
    }

    if (netmask) {
      bzero((char *)&ifr, sizeof(ifr));
      strncpy(ifr.ifr_name, dev, strlen(dev)); 
      if (ioctl(ioctl_sockid, SIOCGIFNETMASK, (char *)&ifr) < 0) 
        return(-1 * errno);
      memcpy(netmask, &ifr.ifr_netmask, sizeof(*netmask));
    }

    return(0);
}

/* Get the ethernet address for the given device name */
static int getEthernetAddr(char *dev, unsigned char *addr)
{
    struct ifreq ifr;
    char *buf;
  
    bzero((char *)&ifr, sizeof(ifr));
    strncpy(ifr.ifr_name, dev, strlen(dev)); 
  
    errno = 0;
    if (ioctl(ioctl_sockid, SIOCGIFHWADDR, (char *)&ifr) < 0) 
        return(-1 * errno);
 
    memcpy(addr, (unsigned char *) &ifr.ifr_hwaddr.sa_data, ETH_ALEN);
    return(0);
}

/* 
 * Send out a gratuitous ARP packet through the interface "iface"
 * that maps IP address "ipaddr" to hardware address "ethaddr".
 * The complete packet contents are as follows:
 * 
 * Ethernet hdr
 *        src = ifaceaddr  dst = ff:ff:ff:ff:ff:ff
 *        frame type = 0x0806 (ETH_P_ARP)
 * 
 * ARP hdr
 *        hardware addr type = 1 (ARPHRD_ETHER)
 *        protocol addr type = 0x0800 (ETH_P_IP)
 *        hardware addr len = 6 (ETH_ALEN)
 *        protocol addr len = 4 
 *        op code = 1 (ARPOP_REQUEST)
 * 
 * ARP payload
 *        sender hardware addr = ethaddr
 *        sender IP addr = ipaddr
 *        dst hardware addr = don't care
 *        target IP addr = don't care
 *
 * NOTE: When this function is called, ipaddr is in network-byte order.
 *
 */

static int sendGARPpkt(char *iface, u32 ipaddr, unsigned char *ethaddr)
{
    char frame[80];
    struct ethhdr *ehdr;
    Arphdr *arphd;
    int sockid;
    struct sockaddr sa;

    errno = 0;
    if ((sockid = socket(AF_INET, SOCK_PACKET, htons(ETH_P_802_3))) < 0)
        return(-1 * errno);

    memset((void*)frame, 0, sizeof(frame));

    ehdr = (struct ethhdr*) frame;
    getEthernetAddr (iface, ehdr->h_source);
    memset(ehdr->h_dest, 0xff, ETH_ALEN);
    ehdr->h_proto = htons(ETH_P_ARP);

    arphd = (Arphdr*)(frame + sizeof(struct ethhdr));
    arphd->ar_hrd = htons(ARPHRD_ETHER);
    arphd->ar_pro = htons(ETH_P_IP);
    arphd->ar_hln = ETH_ALEN;
    arphd->ar_pln = 4;
    arphd->ar_op  = htons(ARPOP_REPLY);
    memcpy(arphd->ar_sha, ethaddr, ETH_ALEN);

    ipaddr = ntohl(ipaddr); 
    arphd->ar_sip[0] = (unsigned char)((ipaddr >> 24) & 0xff);
    arphd->ar_sip[1] = (unsigned char)((ipaddr >> 16) & 0xff);
    arphd->ar_sip[2] = (unsigned char)((ipaddr >> 8 ) & 0xff);
    arphd->ar_sip[3] = (unsigned char)(ipaddr & 0xff);
    memset(arphd->ar_tha, 0xff, ETH_ALEN);
    memcpy(arphd->ar_tip, arphd->ar_sip, 4);

    sa.sa_family = AF_INET;
    strcpy(sa.sa_data, iface);

    /* TODO: figure out the correct length */
    if (sendto(sockid, frame, sizeof(struct ethhdr) + sizeof(Arphdr),
	       0, &sa, sizeof(sa)) < 0) {
        close(sockid);
        return(-1 * errno);
    }
    
    close(sockid);
    return(0);
}

static void usage (char *whoami) {
  fprintf (stderr, "usage: %s interface a.b.c.d\n", whoami);
  exit (1);
}

main (int argc, char *argv[]) {
    char *iface;
    struct in_addr ina;
    unsigned char ethaddr[ETH_ALEN+1];
    int ret;

    if (argc != 3)
      usage (argv[0]);

    iface = argv[1];

    initSock();

    if (getEthernetAddr (iface, ethaddr) < 0) {
      perror ("getEthernetAddr");
      usage (argv[0]);
    }

    if (!inet_aton(argv[2], &ina)) {
      fprintf (stderr, "inet_aton: bad IP Address\n");
      usage (argv[0]);
    }

    if (sendGARPpkt(iface, ina.s_addr, ethaddr) < 0)
      perror ("sendGARPpkt");
}
