/*
 * myarp.c
 *
 * to make the switch aware of new ip / mac
 * let the switch know that ip address <ip> is at mac <mac>
 * ./myarp <device> <ip> <mac>
 * example:
 * ./myarp eth0 192.168.1.223 00:04:76:19:FE:07
 *
 * this could be used to tell the switch the ip changed from one machine to the other without having to wait
 *
 * compiles on linux with:
 * gcc -o myarp myarp.c -g -Wall --std gnu9x -pedantic
 * 
 * Copyright (c) 2005, Rene Luria <rene@luria.ch> aka herel
 * All rights reserved.
 * 
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are met:
 * 
 *     * Redistributions of source code must retain the above copyright notice,
 *     * this list of conditions and the following disclaimer.
 *     * 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.
 *     * Neither the name of the Rene Luria 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 <stdlib.h>
#include <string.h>
#include <stdio.h>
#include <errno.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <sys/ioctl.h>
#include <net/if.h>
#include <net/ethernet.h>
#include <net/if_arp.h>
#include <netpacket/packet.h>
#include <netinet/in_systm.h>
#include <netinet/in.h>
#include <netinet/ip.h>
#include <netinet/if_ether.h>
#include <sys/select.h>
#include <arpa/nameser_compat.h>
#include <arpa/inet.h>
#include <resolv.h>
#include <netdb.h>
#include <time.h>
#include <unistd.h>

#define SENDNUM 3
#define SENDWAIT 100000

struct arpdata {
    unsigned char ar_sha[ETH_ALEN];   /* Sender hardware address.  */
    unsigned long ar_sip;      /* Sender IP address.  */
    unsigned char ar_tha[ETH_ALEN];   /* Target hardware address.  */
    unsigned long ar_tip;      /* Target IP address.  */
};

extern int errno;
int yes = 1;
unsigned char bcast_mac[6] = {0xff, 0xff, 0xff, 0xff, 0xff, 0xff};
unsigned char zero_mac[6] = {0x00, 0x00, 0x00, 0x00, 0x00, 0x00};

void
convert_mac(char *macaddr_str, unsigned char macaddr[6]) {
    int i, pos;
    char bits[3], *endbits = bits + 3;

    bits[2] = '\0';
    pos = 0;
    for (i = 0; i < 6; i++) {
        if (macaddr_str[pos] == ':') {
            pos++;
        }
        bits[0] = macaddr_str[pos++];
        bits[1] = macaddr_str[pos++];
        macaddr[i] = strtol(bits, &endbits, 16);
    }
}


unsigned long
convert_ip(char *ip_str) {
    struct in_addr inp;

    inet_aton(ip_str, &inp);
    return inp.s_addr;
}


int
build_packet(unsigned char *packet, unsigned long ip, unsigned char *macaddr, unsigned short arptype,
        char *device, int sockfd) {
    struct ether_header *ethhdr = (struct ether_header *)packet;
    struct ether_arp *arphdr = (struct ether_arp *)(packet + sizeof *ethhdr);
    unsigned char *target_mac;
    unsigned char device_mac[6];
    struct ifreq ifr;
    int i;

    /* guess target mac address */
/*  if (arptype == ARPOP_REQUEST) {
        target_mac = zero_mac;
    } else {
        target_mac = macaddr;
    }*/
    target_mac = bcast_mac;
    /* guess my mac */
    memset(&ifr, 0, sizeof(ifr));
    strncpy(ifr.ifr_name, device, IF_NAMESIZE);
    if (ioctl(sockfd, SIOCGIFHWADDR, &ifr) == -1) {
        fprintf(stderr, "cannot get source mac addr: %s\n", strerror(errno));
        return -1;
    }
    for (i = 0; i < 6; i++) {
        device_mac[i] = ifr.ifr_ifru.ifru_hwaddr.sa_data[i];
    }
    /* ethhdr->h_source[i] = ifr.ifr_hwaddr.sa_data[i]; */
    /* build headers */
    /* ether header */
    /* destination => broadcast */
    memcpy(ethhdr->ether_dhost, bcast_mac, ETH_ALEN);
    /* ethhdr->ether_shost; */
    memcpy(ethhdr->ether_shost, device_mac, ETH_ALEN);
    /* this is my mac */
    ethhdr->ether_type = htons(ETHERTYPE_ARP);
    /* arp header */
    arphdr->ea_hdr.ar_hrd = htons(ARPHRD_ETHER);      /* Format of hardware address.  */
    arphdr->ea_hdr.ar_pro = htons(ETHERTYPE_IP);      /* Format of protocol address.  */
    arphdr->ea_hdr.ar_hln = ETH_ALEN;       /* Length of hardware address.  */
    arphdr->ea_hdr.ar_pln = 4;       /* Length of protocol address.  */
    arphdr->ea_hdr.ar_op = htons(arptype);       /* ARP opcode (command).  */
    /* arp data */
    memcpy(arphdr->arp_sha, macaddr, ETH_ALEN); /* Sender hardware address.  */
    memcpy(arphdr->arp_tha, target_mac, ETH_ALEN); /* Target hardware address.  */
    for (i = 0; i < 4; i++) {
        arphdr->arp_spa[i] = ip & 0x000000ff; /* Sender IP address.  */
        arphdr->arp_tpa[i] = ip & 0x000000ff;; /* Target IP address.  */
        ip = ip >> 8;
    }
    return 0;
}


int
send_arp(char *ip_str, char *macaddr_str, char *device) {
    int packetsize = sizeof(struct ether_header) + sizeof(struct ether_arp);
    unsigned char packet[sizeof(struct ether_header) + sizeof(struct ether_arp)];
    int sockfd;
    struct sockaddr_ll sa;
    struct ifreq ifr;
    unsigned char macaddr[6];
    unsigned long ip;
    int i;

    /* convert mac input */
    convert_mac(macaddr_str, macaddr);
    ip = convert_ip(ip_str);
    /* open socket */
    sockfd = socket(PF_PACKET, SOCK_RAW, htons(ETH_P_ALL));
    if (sockfd == -1) {
        fprintf(stderr, "Cannot create socket: %s\n", strerror(errno));
        return 1;
    }
    /* reuse */
    if (setsockopt(sockfd, SOL_SOCKET,
#ifdef SO_REUSEPORT
                SO_REUSEPORT,
#else
                SO_REUSEADDR,
#endif
                &yes, sizeof yes) == -1) {
        fprintf(stderr, "Cannot set socket options: %s\n", strerror(errno));
        close(sockfd);
        return 1;
    }
    /* get ifindex */
    memset(&ifr, 0, sizeof(ifr));
    strncpy(ifr.ifr_name, device, IF_NAMESIZE);
    if (ioctl(sockfd, SIOCGIFINDEX, &ifr) == -1) {
        fprintf(stderr, "Cannot get ifindex: %s\n", strerror(errno));
        close(sockfd);
        return -1;
    }
    /* setup addr */
    memset(&sa, 0, sizeof(sa));
    sa.sll_family = AF_PACKET;
    sa.sll_protocol = htons(ETH_P_ALL);
    sa.sll_ifindex = ifr.ifr_ifindex;
    /* and bind */
    if (bind(sockfd, (struct sockaddr *)&sa, sizeof(sa)) == -1) {
        fprintf(stderr, "Cannot bind address: %s\n", strerror(errno));
        close(sockfd);
        return -1;
    }
    /* build packet and send it */
    for (i = 0; i < SENDNUM; i++) {
        /* request */
        if (build_packet(packet, ip, macaddr, ARPOP_REQUEST, device, sockfd)) {
            fprintf(stderr, "Could not build packet\n");
            return 1;
        }
        if (sendto(sockfd, packet, packetsize, 0,  (struct sockaddr *)&sa, sizeof(sa)) == -1) {
            fprintf(stderr, "Could not send data: %s\n", strerror(errno));
            close(sockfd);
            return -1;
        }
        /* reply */
        if (build_packet(packet, ip, macaddr, ARPOP_REPLY, device, sockfd)) {
            fprintf(stderr, "Could not build packet\n");
            return 1;
        }
        if (sendto(sockfd, packet, packetsize, 0,  (struct sockaddr *)&sa, sizeof(sa)) == -1) {
            fprintf(stderr, "Could not send data: %s\n", strerror(errno));
            close(sockfd);
            return -1;
        }
        usleep(SENDWAIT);
    }
    close(sockfd);
    return 0;
}


int
main(int argc, char **argv) {
    if (argc < 4) {
        fprintf(stderr, "error - bad arguments\n");
        fprintf(stderr, "Usage: %s <device> <ip> <mac>\n", argv[0]);
        return EXIT_FAILURE;
    }
    send_arp(argv[2], argv[3], argv[1]);
    return EXIT_SUCCESS;
}