/*
 * DF-0604 — Live cross-CPU race trigger for pfi_buffer
 *
 * Drives concurrent ifaddr_event firings on multiple CPUs by issuing
 * SIOCAIFADDR / SIOCDIFADDR ioctls in tight loops.  Each alias/-alias
 * cycle fires EVENTHANDLER_INVOKE(ifaddr_event) -> pfi_ifaddr_event
 * -> pfi_kif_update -> pfi_dynaddr_update -> pfi_table_update on the
 * caller's CPU.  pfi_table_update zeroes the global pfi_buffer_cnt on
 * the caller's CPU, dispatches the fill to netisr0 via netisr_domsg,
 * then reads pfi_buffer_cnt back — all without any cross-CPU lock.
 * Two concurrent callers race on the shared global buffer.
 *
 * This program forks N children, each pinned to a different CPU via
 * cpuset, each hammering alias/-alias on the target interface.
 *
 * Must be run as root with pf loaded and dynamic-interface rules
 * configured (see setup in run.sh).
 *
 * Build:  cc -O2 -o race_churn race_churn.c
 * Run:    ./race_churn <interface> <num_procs> <seconds>
 */

#include <sys/param.h>
#include <sys/socket.h>
#include <sys/ioctl.h>
#include <sys/types.h>
#include <sys/lwp.h>
#include <machine/cpumask.h>
#include <net/if.h>
#include <netinet/in.h>
#include <netinet/in_var.h>
#include <arpa/inet.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <signal.h>
#include <errno.h>
#include <sys/wait.h>
#include <sys/sysctl.h>

static volatile sig_atomic_t stop = 0;

static void
sighandler(int sig)
{
    (void)sig;
    stop = 1;
}

/*
 * Pin the calling LWP to a specific CPU using lwp_setaffinity(2).
 */
static int
pin_cpu(int cpu)
{
    cpumask_t mask;
    CPUMASK_ASSBIT(mask, cpu);
    if (lwp_setaffinity(0, -1, &mask) != 0) {
        fprintf(stderr, "pin to cpu %d failed: %s (continuing)\n",
                cpu, strerror(errno));
        return (-1);
    }
    return (0);
}

static unsigned long long count_total;

/*
 * Add and remove an IPv4 alias on the given interface.
 * Returns 0 on success, -1 on error.
 */
static int
churn_alias(int s, const char *ifname, struct in_addr addr, int cidr)
{
    struct ifaliasreq ifra;
    struct sockaddr_in *sin;

    /* Add alias */
    memset(&ifra, 0, sizeof(ifra));
    strlcpy(ifra.ifra_name, ifname, sizeof(ifra.ifra_name));
    sin = (struct sockaddr_in *)&ifra.ifra_addr;
    sin->sin_family = AF_INET;
    sin->sin_len = sizeof(*sin);
    sin->sin_addr = addr;
    sin = (struct sockaddr_in *)&ifra.ifra_mask;
    sin->sin_family = AF_INET;
    sin->sin_len = sizeof(*sin);
    /* Build netmask from cidr */
    uint32_t mask = cidr ? (0xFFFFFFFF << (32 - cidr)) : 0;
    sin->sin_addr.s_addr = htonl(mask);

    if (ioctl(s, SIOCAIFADDR, &ifra) < 0) {
        if (errno != EEXIST)
            return (-1);
    }

    /* Remove alias */
    memset(&ifra, 0, sizeof(ifra));
    strlcpy(ifra.ifra_name, ifname, sizeof(ifra.ifra_name));
    sin = (struct sockaddr_in *)&ifra.ifra_addr;
    sin->sin_family = AF_INET;
    sin->sin_len = sizeof(*sin);
    sin->sin_addr = addr;

    if (ioctl(s, SIOCDIFADDR, &ifra) < 0) {
        if (errno != EADDRNOTAVAIL)
            return (-1);
    }
    return (0);
}

int
main(int argc, char *argv[])
{
    const char *ifname = "vtnet0";
    int num_procs = 4;
    int duration = 10;
    int s, i, status;
    pid_t *pids;

    if (argc > 1) ifname = argv[1];
    if (argc > 2) num_procs = atoi(argv[2]);
    if (argc > 3) duration = atoi(argv[3]);

    if (num_procs < 1) num_procs = 1;

    fprintf(stderr, "DF-0604 race churn: if=%s procs=%d duration=%ds\n",
            ifname, num_procs, duration);

    signal(SIGALRM, sighandler);
    signal(SIGINT, sighandler);

    pids = calloc(num_procs, sizeof(pid_t));

    for (i = 0; i < num_procs; i++) {
        pid_t pid = fork();
        if (pid == 0) {
            /* Child */
            int cpu = i; /* pin child i to CPU i */
            unsigned long long mycount = 0;
            struct in_addr addr;

            pin_cpu(cpu);

            s = socket(AF_INET, SOCK_DGRAM, 0);
            if (s < 0) { perror("socket"); _exit(1); }

            /* Each child uses a unique base address to make
             * cross-contamination visible if we had two interfaces */
            addr.s_addr = htonl(0x0A000000 | (cpu + 1)); /* 10.0.0.(cpu+1) */

            alarm(duration);

            while (!stop) {
                if (churn_alias(s, ifname, addr, 24) < 0) {
                    /* ignore errors, keep churning */
                }
                mycount++;
            }
            fprintf(stderr, "  child %d (cpu %d): %llu cycles\n",
                    i, cpu, mycount);
            _exit(0);
        } else if (pid > 0) {
            pids[i] = pid;
        } else {
            perror("fork");
            exit(1);
        }
    }

    /* Wait for all children */
    for (i = 0; i < num_procs; i++) {
        waitpid(pids[i], &status, 0);
    }

    fprintf(stderr, "DF-0604 race churn complete.\n");
    free(pids);
    return (0);
}
