/*
 * arp_probe.c: arma un frame de Ethernet con una consulta ARP, byte por byte, y espera
 * la respuesta. Hace a mano lo que el kernel hace antes de enviar el primer paquete a un
 * vecino.
 *
 *   arp_probe <interfaz> <ip del vecino>
 *
 * Necesita permiso para abrir un socket AF_PACKET (root dentro de un nodo).
 */
#define _DEFAULT_SOURCE
#include <arpa/inet.h>
#include <errno.h>
#include <linux/if_packet.h>
#include <net/ethernet.h>
#include <net/if.h>
#include <netinet/in.h>
#include <poll.h>
#include <stdint.h>
#include <stdio.h>
#include <string.h>
#include <sys/ioctl.h>
#include <sys/socket.h>
#include <time.h>
#include <unistd.h>

#define ETH_HEADER_LEN 14
#define ARP_LEN 28
#define FRAME_LEN (ETH_HEADER_LEN + ARP_LEN)
#define ARP_OP_REQUEST 1
#define ARP_OP_REPLY 2
#define TIMEOUT_MS 1000

/* Escribe un valor de 16 bits en orden de red, sin depender del orden de la máquina. */
static void put_u16(uint8_t *p, uint16_t value)
{
    p[0] = (uint8_t)(value >> 8);
    p[1] = (uint8_t)(value & 0xff);
}

static uint16_t get_u16(const uint8_t *p)
{
    return (uint16_t)((p[0] << 8) | p[1]);
}

static void print_mac(const uint8_t *mac)
{
    printf("%02x:%02x:%02x:%02x:%02x:%02x", mac[0], mac[1], mac[2], mac[3], mac[4], mac[5]);
}

static void print_hex(const uint8_t *data, size_t len)
{
    for (size_t i = 0; i < len; i++) {
        printf("%02x%c", data[i], (i % 14 == 13 || i + 1 == len) ? '\n' : ' ');
    }
}

/* Frame completo: encabezado de Ethernet y mensaje ARP (RFC 826). */
static void build_request(uint8_t *frame, const uint8_t *my_mac, const uint8_t *my_ip,
                          const uint8_t *target_ip)
{
    memset(frame, 0xff, 6);                 /* destino: broadcast */
    memcpy(frame + 6, my_mac, 6);           /* origen */
    put_u16(frame + 12, ETHERTYPE_ARP);     /* EtherType */

    uint8_t *arp = frame + ETH_HEADER_LEN;
    put_u16(arp + 0, 1);                    /* tipo de hardware: Ethernet */
    put_u16(arp + 2, ETHERTYPE_IP);         /* tipo de protocolo: IPv4 */
    arp[4] = 6;                             /* largo de una dirección de hardware */
    arp[5] = 4;                             /* largo de una dirección de protocolo */
    put_u16(arp + 6, ARP_OP_REQUEST);
    memcpy(arp + 8, my_mac, 6);             /* quién pregunta */
    memcpy(arp + 14, my_ip, 4);
    memset(arp + 18, 0, 6);                 /* MAC buscada: todavía no se conoce */
    memcpy(arp + 24, target_ip, 4);
}

static double elapsed_ms(const struct timespec *start)
{
    struct timespec now;
    if (clock_gettime(CLOCK_MONOTONIC, &now) < 0) {
        perror("clock_gettime");
        return -1.0;
    }
    return (double)(now.tv_sec - start->tv_sec) * 1000.0 +
           (double)(now.tv_nsec - start->tv_nsec) / 1e6;
}

/* Espera un ARP Reply del vecino. Devuelve 1 si llegó, 0 si venció el plazo, -1 si hay error. */
static int wait_reply(int fd, const uint8_t *target_ip, const struct timespec *start)
{
    uint8_t buf[2048];
    struct pollfd pfd = {.fd = fd, .events = POLLIN};

    for (;;) {
        double spent = elapsed_ms(start);
        if (spent < 0.0) {
            return -1;
        }
        if (spent >= TIMEOUT_MS) {
            return 0;
        }
        int ready = poll(&pfd, 1, TIMEOUT_MS - (int)spent);
        if (ready < 0) {
            if (errno == EINTR) {
                continue;
            }
            perror("poll");
            return -1;
        }
        if (ready == 0) {
            return 0;
        }
        ssize_t n = recv(fd, buf, sizeof buf, 0);
        if (n < 0) {
            perror("recv");
            return -1;
        }
        if (n < FRAME_LEN) {
            continue;
        }
        const uint8_t *arp = buf + ETH_HEADER_LEN;
        if (get_u16(arp + 6) == ARP_OP_REPLY && memcmp(arp + 14, target_ip, 4) == 0) {
            printf("respuesta en %.3f ms, la MAC del vecino es ", elapsed_ms(start));
            print_mac(arp + 8);
            printf("\nframe recibido (%zd bytes):\n", n);
            print_hex(buf, (size_t)n);
            return 1;
        }
    }
}

int main(int argc, char *argv[])
{
    if (argc != 3) {
        fprintf(stderr, "uso: %s <interfaz> <ip del vecino>\n", argv[0]);
        return 2;
    }
    const char *ifname = argv[1];
    if (strlen(ifname) >= IFNAMSIZ) {
        fprintf(stderr, "nombre de interfaz demasiado largo\n");
        return 2;
    }
    struct in_addr target;
    if (inet_pton(AF_INET, argv[2], &target) != 1) {
        fprintf(stderr, "dirección IPv4 no válida: %s\n", argv[2]);
        return 2;
    }

    /* SOCK_RAW en AF_PACKET: lo que se envía y se recibe incluye el encabezado de Ethernet. */
    int fd = socket(AF_PACKET, SOCK_RAW, htons(ETHERTYPE_ARP));
    if (fd < 0) {
        perror("socket");
        return 1;
    }

    struct ifreq ifr;
    memset(&ifr, 0, sizeof ifr);
    strcpy(ifr.ifr_name, ifname);
    if (ioctl(fd, SIOCGIFINDEX, &ifr) < 0) {
        perror("ioctl(SIOCGIFINDEX)");
        close(fd);
        return 1;
    }
    int ifindex = ifr.ifr_ifindex;

    if (ioctl(fd, SIOCGIFHWADDR, &ifr) < 0) {
        perror("ioctl(SIOCGIFHWADDR)");
        close(fd);
        return 1;
    }
    uint8_t my_mac[6];
    memcpy(my_mac, ifr.ifr_hwaddr.sa_data, 6);

    if (ioctl(fd, SIOCGIFADDR, &ifr) < 0) {
        perror("ioctl(SIOCGIFADDR)");
        close(fd);
        return 1;
    }
    struct sockaddr_in my_addr;
    memcpy(&my_addr, &ifr.ifr_addr, sizeof my_addr);

    struct sockaddr_ll where;
    memset(&where, 0, sizeof where);
    where.sll_family = AF_PACKET;
    where.sll_protocol = htons(ETHERTYPE_ARP);
    where.sll_ifindex = ifindex;
    if (bind(fd, (struct sockaddr *)&where, sizeof where) < 0) {
        perror("bind");
        close(fd);
        return 1;
    }

    uint8_t frame[FRAME_LEN];
    build_request(frame, my_mac, (const uint8_t *)&my_addr.sin_addr,
                  (const uint8_t *)&target);
    printf("frame enviado (%d bytes):\n", FRAME_LEN);
    print_hex(frame, FRAME_LEN);

    struct timespec start;
    if (clock_gettime(CLOCK_MONOTONIC, &start) < 0) {
        perror("clock_gettime");
        close(fd);
        return 1;
    }
    if (send(fd, frame, FRAME_LEN, 0) < 0) {
        perror("send");
        close(fd);
        return 1;
    }

    int result = wait_reply(fd, (const uint8_t *)&target, &start);
    if (close(fd) < 0) {
        perror("close");
        return 1;
    }
    if (result == 0) {
        printf("sin respuesta en %d ms\n", TIMEOUT_MS);
        return 1;
    }
    return result == 1 ? 0 : 1;
}
