DF-0604 / race_churn.c
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 | /* * 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); } |