/* df_ping: un ping con el bit DF prendido y un tamaño exacto. El ping de la imagen de los nodos
 * (busybox) no tiene esa opción.
 *
 *   df_ping <ip> <bytes_de_datos> [intentos]
 *
 * El paquete mide bytes_de_datos + 28 (20 de IP y 8 de ICMP). Sale con 0 si llegó alguna
 * respuesta, con 1 si no. Necesita un socket crudo, así que corre como root en los nodos.
 */
#include <arpa/inet.h>
#include <errno.h>
#include <netinet/in.h>
#include <poll.h>
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/socket.h>
#include <unistd.h>

#define MAX_DATA 9000
#define ICMP_ECHO_REQUEST 8
#define ICMP_ECHO_REPLY 0
#define ICMP_HEADER 8
#define WAIT_MS 800

static uint16_t checksum(const unsigned char *data, size_t size) {
    uint32_t sum = 0;
    for (size_t i = 0; i + 1 < size; i += 2) {
        sum += (uint32_t)(data[i] << 8 | data[i + 1]);
    }
    if (size % 2) {
        sum += (uint32_t)(data[size - 1] << 8);
    }
    while (sum >> 16) {
        sum = (sum & 0xffff) + (sum >> 16);
    }
    return (uint16_t)~sum;
}

int main(int argc, char **argv) {
    if (argc != 3 && argc != 4) {
        fprintf(stderr, "uso: %s <ip> <bytes_de_datos> [intentos]\n", argv[0]);
        return 2;
    }
    struct sockaddr_in remote = {.sin_family = AF_INET};
    if (inet_pton(AF_INET, argv[1], &remote.sin_addr) != 1) {
        fprintf(stderr, "dirección inválida: %s\n", argv[1]);
        return 2;
    }
    long data = strtol(argv[2], NULL, 10);
    long tries = argc == 4 ? strtol(argv[3], NULL, 10) : 3;
    if (data < 0 || data > MAX_DATA || tries < 1 || tries > 100) {
        fprintf(stderr, "valores fuera de rango\n");
        return 2;
    }
    int fd = socket(AF_INET, SOCK_RAW, IPPROTO_ICMP);
    if (fd < 0) {
        perror("socket");
        return 1;
    }
    int discover = IP_PMTUDISC_DO;
    if (setsockopt(fd, IPPROTO_IP, IP_MTU_DISCOVER, &discover, sizeof discover) < 0) {
        perror("setsockopt");
        return 1;
    }
    uint16_t id = (uint16_t)getpid();
    static unsigned char packet[ICMP_HEADER + MAX_DATA];
    size_t size = ICMP_HEADER + (size_t)data;
    long replies = 0;
    for (long attempt = 0; attempt < tries && replies == 0; attempt++) {
        memset(packet, 0x5a, size);
        packet[0] = ICMP_ECHO_REQUEST;
        packet[1] = 0;
        packet[2] = packet[3] = 0;
        packet[4] = (unsigned char)(id >> 8);
        packet[5] = (unsigned char)id;
        packet[6] = (unsigned char)(attempt >> 8);
        packet[7] = (unsigned char)attempt;
        uint16_t sum = checksum(packet, size);
        packet[2] = (unsigned char)(sum >> 8);
        packet[3] = (unsigned char)sum;
        if (sendto(fd, packet, size, 0, (struct sockaddr *)&remote, sizeof remote) < 0) {
            /* EMSGSIZE: este mismo host ya sabe que el paquete no cabe. */
            if (errno == EMSGSIZE) {
                printf("%zu bytes: el host no lo envía (EMSGSIZE)\n", size + 20);
                close(fd);
                return 1;
            }
            perror("sendto");
            close(fd);
            return 1;
        }
        struct pollfd waiting = {.fd = fd, .events = POLLIN};
        while (replies == 0) {
            int ready = poll(&waiting, 1, WAIT_MS);
            if (ready < 0) {
                perror("poll");
                close(fd);
                return 1;
            }
            if (ready == 0) {
                break;
            }
            unsigned char reply[20 + ICMP_HEADER + MAX_DATA];
            ssize_t got = recv(fd, reply, sizeof reply, 0);
            if (got < 0) {
                if (errno == EMSGSIZE) {
                    printf("%zu bytes: llegó un aviso de que no cabe (EMSGSIZE)\n", size + 20);
                    close(fd);
                    return 1;
                }
                perror("recv");
                close(fd);
                return 1;
            }
            size_t header = got > 0 ? (size_t)(reply[0] & 0x0f) * 4 : 0;
            if ((size_t)got >= header + ICMP_HEADER && reply[header] == ICMP_ECHO_REPLY &&
                reply[header + 4] == (unsigned char)(id >> 8) && reply[header + 5] == (unsigned char)id) {
                replies++;
            }
        }
    }
    if (close(fd) < 0) {
        perror("close");
    }
    printf("%zu bytes: %s\n", size + 20, replies ? "respuesta" : "sin respuesta");
    return replies ? 0 : 1;
}
