1 /* 2 * ng_one2many.c 3 */ 4 5 /*- 6 * Copyright (c) 2000 Whistle Communications, Inc. 7 * All rights reserved. 8 * 9 * Subject to the following obligations and disclaimer of warranty, use and 10 * redistribution of this software, in source or object code forms, with or 11 * without modifications are expressly permitted by Whistle Communications; 12 * provided, however, that: 13 * 1. Any and all reproductions of the source or object code must include the 14 * copyright notice above and the following disclaimer of warranties; and 15 * 2. No rights are granted, in any manner or form, to use Whistle 16 * Communications, Inc. trademarks, including the mark "WHISTLE 17 * COMMUNICATIONS" on advertising, endorsements, or otherwise except as 18 * such appears in the above copyright notice or in the software. 19 * 20 * THIS SOFTWARE IS BEING PROVIDED BY WHISTLE COMMUNICATIONS "AS IS", AND 21 * TO THE MAXIMUM EXTENT PERMITTED BY LAW, WHISTLE COMMUNICATIONS MAKES NO 22 * REPRESENTATIONS OR WARRANTIES, EXPRESS OR IMPLIED, REGARDING THIS SOFTWARE, 23 * INCLUDING WITHOUT LIMITATION, ANY AND ALL IMPLIED WARRANTIES OF 24 * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE, OR NON-INFRINGEMENT. 25 * WHISTLE COMMUNICATIONS DOES NOT WARRANT, GUARANTEE, OR MAKE ANY 26 * REPRESENTATIONS REGARDING THE USE OF, OR THE RESULTS OF THE USE OF THIS 27 * SOFTWARE IN TERMS OF ITS CORRECTNESS, ACCURACY, RELIABILITY OR OTHERWISE. 28 * IN NO EVENT SHALL WHISTLE COMMUNICATIONS BE LIABLE FOR ANY DAMAGES 29 * RESULTING FROM OR ARISING OUT OF ANY USE OF THIS SOFTWARE, INCLUDING 30 * WITHOUT LIMITATION, ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, 31 * PUNITIVE, OR CONSEQUENTIAL DAMAGES, PROCUREMENT OF SUBSTITUTE GOODS OR 32 * SERVICES, LOSS OF USE, DATA OR PROFITS, HOWEVER CAUSED AND UNDER ANY 33 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 34 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF 35 * THIS SOFTWARE, EVEN IF WHISTLE COMMUNICATIONS IS ADVISED OF THE POSSIBILITY 36 * OF SUCH DAMAGE. 37 * 38 * Author: Archie Cobbs <archie@freebsd.org> 39 */ 40 41 /* 42 * ng_one2many(4) netgraph node type 43 * 44 * Packets received on the "one" hook are sent out each of the 45 * "many" hooks according to an algorithm. Packets received on any 46 * "many" hook are always delivered to the "one" hook. 47 */ 48 49 #include <sys/param.h> 50 #include <sys/systm.h> 51 #include <sys/kernel.h> 52 #include <sys/malloc.h> 53 #include <sys/ctype.h> 54 #include <sys/mbuf.h> 55 #include <sys/errno.h> 56 57 #include <netgraph/ng_message.h> 58 #include <netgraph/netgraph.h> 59 #include <netgraph/ng_parse.h> 60 #include <netgraph/ng_one2many.h> 61 62 /* Per-link private data */ 63 struct ng_one2many_link { 64 hook_p hook; /* netgraph hook */ 65 struct ng_one2many_link_stats stats; /* link stats */ 66 }; 67 68 /* Per-node private data */ 69 struct ng_one2many_private { 70 node_p node; /* link to node */ 71 struct ng_one2many_config conf; /* node configuration */ 72 struct ng_one2many_link one; /* "one" hook */ 73 struct ng_one2many_link many[NG_ONE2MANY_MAX_LINKS]; 74 u_int16_t nextMany; /* next round-robin */ 75 u_int16_t numActiveMany; /* # active "many" */ 76 u_int16_t activeMany[NG_ONE2MANY_MAX_LINKS]; 77 }; 78 typedef struct ng_one2many_private *priv_p; 79 80 /* Netgraph node methods */ 81 static ng_constructor_t ng_one2many_constructor; 82 static ng_rcvmsg_t ng_one2many_rcvmsg; 83 static ng_shutdown_t ng_one2many_shutdown; 84 static ng_newhook_t ng_one2many_newhook; 85 static ng_rcvdata_t ng_one2many_rcvdata; 86 static ng_disconnect_t ng_one2many_disconnect; 87 88 /* Other functions */ 89 static void ng_one2many_update_many(priv_p priv); 90 static void ng_one2many_notify(priv_p priv, uint32_t cmd); 91 92 /****************************************************************** 93 NETGRAPH PARSE TYPES 94 ******************************************************************/ 95 96 /* Parse type for struct ng_one2many_config */ 97 static const struct ng_parse_fixedarray_info 98 ng_one2many_enableLinks_array_type_info = { 99 &ng_parse_uint8_type, 100 NG_ONE2MANY_MAX_LINKS 101 }; 102 static const struct ng_parse_type ng_one2many_enableLinks_array_type = { 103 &ng_parse_fixedarray_type, 104 &ng_one2many_enableLinks_array_type_info, 105 }; 106 static const struct ng_parse_struct_field ng_one2many_config_type_fields[] 107 = NG_ONE2MANY_CONFIG_TYPE_INFO(&ng_one2many_enableLinks_array_type); 108 static const struct ng_parse_type ng_one2many_config_type = { 109 &ng_parse_struct_type, 110 &ng_one2many_config_type_fields 111 }; 112 113 /* Parse type for struct ng_one2many_link_stats */ 114 static const struct ng_parse_struct_field ng_one2many_link_stats_type_fields[] 115 = NG_ONE2MANY_LINK_STATS_TYPE_INFO; 116 static const struct ng_parse_type ng_one2many_link_stats_type = { 117 &ng_parse_struct_type, 118 &ng_one2many_link_stats_type_fields 119 }; 120 121 /* List of commands and how to convert arguments to/from ASCII */ 122 static const struct ng_cmdlist ng_one2many_cmdlist[] = { 123 { 124 NGM_ONE2MANY_COOKIE, 125 NGM_ONE2MANY_SET_CONFIG, 126 "setconfig", 127 &ng_one2many_config_type, 128 NULL 129 }, 130 { 131 NGM_ONE2MANY_COOKIE, 132 NGM_ONE2MANY_GET_CONFIG, 133 "getconfig", 134 NULL, 135 &ng_one2many_config_type 136 }, 137 { 138 NGM_ONE2MANY_COOKIE, 139 NGM_ONE2MANY_GET_STATS, 140 "getstats", 141 &ng_parse_int32_type, 142 &ng_one2many_link_stats_type 143 }, 144 { 145 NGM_ONE2MANY_COOKIE, 146 NGM_ONE2MANY_CLR_STATS, 147 "clrstats", 148 &ng_parse_int32_type, 149 NULL, 150 }, 151 { 152 NGM_ONE2MANY_COOKIE, 153 NGM_ONE2MANY_GETCLR_STATS, 154 "getclrstats", 155 &ng_parse_int32_type, 156 &ng_one2many_link_stats_type 157 }, 158 { 0 } 159 }; 160 161 /* Node type descriptor */ 162 static struct ng_type ng_one2many_typestruct = { 163 .version = NG_ABI_VERSION, 164 .name = NG_ONE2MANY_NODE_TYPE, 165 .constructor = ng_one2many_constructor, 166 .rcvmsg = ng_one2many_rcvmsg, 167 .shutdown = ng_one2many_shutdown, 168 .newhook = ng_one2many_newhook, 169 .rcvdata = ng_one2many_rcvdata, 170 .disconnect = ng_one2many_disconnect, 171 .cmdlist = ng_one2many_cmdlist, 172 }; 173 NETGRAPH_INIT(one2many, &ng_one2many_typestruct); 174 175 /****************************************************************** 176 NETGRAPH NODE METHODS 177 ******************************************************************/ 178 179 /* 180 * Node constructor 181 */ 182 static int 183 ng_one2many_constructor(node_p node) 184 { 185 priv_p priv; 186 187 /* Allocate and initialize private info */ 188 priv = malloc(sizeof(*priv), M_NETGRAPH, M_WAITOK | M_ZERO); 189 priv->conf.xmitAlg = NG_ONE2MANY_XMIT_ROUNDROBIN; 190 priv->conf.failAlg = NG_ONE2MANY_FAIL_MANUAL; 191 192 /* cross reference */ 193 NG_NODE_SET_PRIVATE(node, priv); 194 priv->node = node; 195 196 /* Done */ 197 return (0); 198 } 199 200 /* 201 * Method for attaching a new hook 202 */ 203 static int 204 ng_one2many_newhook(node_p node, hook_p hook, const char *name) 205 { 206 const priv_p priv = NG_NODE_PRIVATE(node); 207 struct ng_one2many_link *link; 208 int linkNum; 209 u_long i; 210 211 /* Which hook? */ 212 if (strncmp(name, NG_ONE2MANY_HOOK_MANY_PREFIX, 213 strlen(NG_ONE2MANY_HOOK_MANY_PREFIX)) == 0) { 214 const char *cp; 215 char *eptr; 216 217 cp = name + strlen(NG_ONE2MANY_HOOK_MANY_PREFIX); 218 if (!isdigit(*cp) || (cp[0] == '0' && cp[1] != '\0')) 219 return (EINVAL); 220 i = strtoul(cp, &eptr, 10); 221 if (*eptr != '\0' || i >= NG_ONE2MANY_MAX_LINKS) 222 return (EINVAL); 223 linkNum = (int)i; 224 link = &priv->many[linkNum]; 225 } else if (strcmp(name, NG_ONE2MANY_HOOK_ONE) == 0) { 226 linkNum = NG_ONE2MANY_ONE_LINKNUM; 227 link = &priv->one; 228 } else 229 return (EINVAL); 230 231 /* Is hook already connected? (should never happen) */ 232 if (link->hook != NULL) 233 return (EISCONN); 234 235 /* Setup private info for this link */ 236 NG_HOOK_SET_PRIVATE(hook, (void *)(intptr_t)linkNum); 237 link->hook = hook; 238 bzero(&link->stats, sizeof(link->stats)); 239 if (linkNum != NG_ONE2MANY_ONE_LINKNUM) { 240 priv->conf.enabledLinks[linkNum] = 1; /* auto-enable link */ 241 ng_one2many_update_many(priv); 242 } 243 244 /* Done */ 245 return (0); 246 } 247 248 /* 249 * Receive a control message 250 */ 251 static int 252 ng_one2many_rcvmsg(node_p node, item_p item, hook_p lasthook) 253 { 254 const priv_p priv = NG_NODE_PRIVATE(node); 255 struct ng_mesg *resp = NULL; 256 int error = 0; 257 struct ng_mesg *msg; 258 259 NGI_GET_MSG(item, msg); 260 switch (msg->header.typecookie) { 261 case NGM_ONE2MANY_COOKIE: 262 switch (msg->header.cmd) { 263 case NGM_ONE2MANY_SET_CONFIG: 264 { 265 struct ng_one2many_config *conf; 266 int i; 267 268 /* Check that new configuration is valid */ 269 if (msg->header.arglen != sizeof(*conf)) { 270 error = EINVAL; 271 break; 272 } 273 conf = (struct ng_one2many_config *)msg->data; 274 switch (conf->xmitAlg) { 275 case NG_ONE2MANY_XMIT_ROUNDROBIN: 276 case NG_ONE2MANY_XMIT_ALL: 277 case NG_ONE2MANY_XMIT_FAILOVER: 278 break; 279 default: 280 error = EINVAL; 281 break; 282 } 283 switch (conf->failAlg) { 284 case NG_ONE2MANY_FAIL_MANUAL: 285 case NG_ONE2MANY_FAIL_NOTIFY: 286 break; 287 default: 288 error = EINVAL; 289 break; 290 } 291 if (error != 0) 292 break; 293 294 /* Normalized many link enabled bits */ 295 for (i = 0; i < NG_ONE2MANY_MAX_LINKS; i++) 296 conf->enabledLinks[i] = !!conf->enabledLinks[i]; 297 298 /* Copy config and reset */ 299 bcopy(conf, &priv->conf, sizeof(*conf)); 300 ng_one2many_update_many(priv); 301 break; 302 } 303 case NGM_ONE2MANY_GET_CONFIG: 304 { 305 struct ng_one2many_config *conf; 306 307 NG_MKRESPONSE(resp, msg, sizeof(*conf), M_NOWAIT); 308 if (resp == NULL) { 309 error = ENOMEM; 310 break; 311 } 312 conf = (struct ng_one2many_config *)resp->data; 313 bcopy(&priv->conf, conf, sizeof(priv->conf)); 314 break; 315 } 316 case NGM_ONE2MANY_GET_STATS: 317 case NGM_ONE2MANY_CLR_STATS: 318 case NGM_ONE2MANY_GETCLR_STATS: 319 { 320 struct ng_one2many_link *link; 321 int linkNum; 322 323 /* Get link */ 324 if (msg->header.arglen != sizeof(int32_t)) { 325 error = EINVAL; 326 break; 327 } 328 linkNum = *((int32_t *)msg->data); 329 if (linkNum == NG_ONE2MANY_ONE_LINKNUM) 330 link = &priv->one; 331 else if (linkNum >= 0 332 && linkNum < NG_ONE2MANY_MAX_LINKS) { 333 link = &priv->many[linkNum]; 334 } else { 335 error = EINVAL; 336 break; 337 } 338 339 /* Get/clear stats */ 340 if (msg->header.cmd != NGM_ONE2MANY_CLR_STATS) { 341 NG_MKRESPONSE(resp, msg, 342 sizeof(link->stats), M_NOWAIT); 343 if (resp == NULL) { 344 error = ENOMEM; 345 break; 346 } 347 bcopy(&link->stats, 348 resp->data, sizeof(link->stats)); 349 } 350 if (msg->header.cmd != NGM_ONE2MANY_GET_STATS) 351 bzero(&link->stats, sizeof(link->stats)); 352 break; 353 } 354 default: 355 error = EINVAL; 356 break; 357 } 358 break; 359 /* 360 * One of our downstreams notifies us of link change. If we are 361 * configured to listen to these message, then we remove/add 362 * this hook from array of active hooks. 363 */ 364 case NGM_FLOW_COOKIE: 365 { 366 int linkNum; 367 368 if (priv->conf.failAlg != NG_ONE2MANY_FAIL_NOTIFY) 369 break; 370 371 if (lasthook == NULL) 372 break; 373 374 linkNum = (intptr_t)NG_HOOK_PRIVATE(lasthook); 375 if (linkNum == NG_ONE2MANY_ONE_LINKNUM) 376 break; 377 378 KASSERT((linkNum >= 0 && linkNum < NG_ONE2MANY_MAX_LINKS), 379 ("%s: linkNum=%d", __func__, linkNum)); 380 381 switch (msg->header.cmd) { 382 case NGM_LINK_IS_UP: 383 priv->conf.enabledLinks[linkNum] = 1; 384 ng_one2many_update_many(priv); 385 break; 386 case NGM_LINK_IS_DOWN: 387 priv->conf.enabledLinks[linkNum] = 0; 388 ng_one2many_update_many(priv); 389 break; 390 default: 391 break; 392 } 393 break; 394 } 395 default: 396 error = EINVAL; 397 break; 398 } 399 400 /* Done */ 401 NG_RESPOND_MSG(error, node, item, resp); 402 NG_FREE_MSG(msg); 403 return (error); 404 } 405 406 /* 407 * Receive data on a hook 408 */ 409 static int 410 ng_one2many_rcvdata(hook_p hook, item_p item) 411 { 412 const node_p node = NG_HOOK_NODE(hook); 413 const priv_p priv = NG_NODE_PRIVATE(node); 414 struct ng_one2many_link *src; 415 struct ng_one2many_link *dst = NULL; 416 int error = 0; 417 int linkNum; 418 int i; 419 struct mbuf *m; 420 421 m = NGI_M(item); /* just peaking, mbuf still owned by item */ 422 /* Get link number */ 423 linkNum = (intptr_t)NG_HOOK_PRIVATE(hook); 424 KASSERT(linkNum == NG_ONE2MANY_ONE_LINKNUM 425 || (linkNum >= 0 && linkNum < NG_ONE2MANY_MAX_LINKS), 426 ("%s: linkNum=%d", __func__, linkNum)); 427 428 /* Figure out source link */ 429 src = (linkNum == NG_ONE2MANY_ONE_LINKNUM) ? 430 &priv->one : &priv->many[linkNum]; 431 KASSERT(src->hook != NULL, ("%s: no src%d", __func__, linkNum)); 432 433 /* Update receive stats */ 434 src->stats.recvPackets++; 435 src->stats.recvOctets += m->m_pkthdr.len; 436 437 /* Figure out destination link */ 438 if (linkNum == NG_ONE2MANY_ONE_LINKNUM) { 439 if (priv->numActiveMany == 0) { 440 NG_FREE_ITEM(item); 441 return (ENOTCONN); 442 } 443 switch(priv->conf.xmitAlg) { 444 case NG_ONE2MANY_XMIT_ROUNDROBIN: 445 dst = &priv->many[priv->activeMany[priv->nextMany]]; 446 priv->nextMany = (priv->nextMany + 1) % priv->numActiveMany; 447 break; 448 case NG_ONE2MANY_XMIT_ALL: 449 /* no need to copy data for the 1st one */ 450 dst = &priv->many[priv->activeMany[0]]; 451 452 /* make modifiable copies of data and send for all 453 * links except the first one, which we'll do last 454 */ 455 for (i = 1; i < priv->numActiveMany; i++) { 456 struct mbuf *m2; 457 struct ng_one2many_link *mdst; 458 459 mdst = &priv->many[priv->activeMany[i]]; 460 m2 = m_dup(m, M_NOWAIT); 461 if (m2 == NULL) { 462 mdst->stats.memoryFailures++; 463 NG_FREE_ITEM(item); 464 NG_FREE_M(m); 465 return (ENOBUFS); 466 } 467 /* Update transmit stats */ 468 mdst->stats.xmitPackets++; 469 mdst->stats.xmitOctets += m->m_pkthdr.len; 470 NG_SEND_DATA_ONLY(error, mdst->hook, m2); 471 } 472 break; 473 case NG_ONE2MANY_XMIT_FAILOVER: 474 dst = &priv->many[priv->activeMany[0]]; 475 break; 476 #ifdef INVARIANTS 477 default: 478 panic("%s: invalid xmitAlg", __func__); 479 #endif 480 } 481 } else { 482 dst = &priv->one; 483 } 484 485 /* Update transmit stats */ 486 dst->stats.xmitPackets++; 487 dst->stats.xmitOctets += m->m_pkthdr.len; 488 489 /* Deliver packet */ 490 NG_FWD_ITEM_HOOK(error, item, dst->hook); 491 return (error); 492 } 493 494 /* 495 * Shutdown node 496 */ 497 static int 498 ng_one2many_shutdown(node_p node) 499 { 500 const priv_p priv = NG_NODE_PRIVATE(node); 501 502 KASSERT(priv->numActiveMany == 0, 503 ("%s: numActiveMany=%d", __func__, priv->numActiveMany)); 504 free(priv, M_NETGRAPH); 505 NG_NODE_SET_PRIVATE(node, NULL); 506 NG_NODE_UNREF(node); 507 return (0); 508 } 509 510 /* 511 * Hook disconnection. 512 */ 513 static int 514 ng_one2many_disconnect(hook_p hook) 515 { 516 const priv_p priv = NG_NODE_PRIVATE(NG_HOOK_NODE(hook)); 517 int linkNum; 518 519 /* Get link number */ 520 linkNum = (intptr_t)NG_HOOK_PRIVATE(hook); 521 KASSERT(linkNum == NG_ONE2MANY_ONE_LINKNUM 522 || (linkNum >= 0 && linkNum < NG_ONE2MANY_MAX_LINKS), 523 ("%s: linkNum=%d", __func__, linkNum)); 524 525 /* Nuke the link */ 526 if (linkNum == NG_ONE2MANY_ONE_LINKNUM) 527 priv->one.hook = NULL; 528 else { 529 priv->many[linkNum].hook = NULL; 530 priv->conf.enabledLinks[linkNum] = 0; 531 ng_one2many_update_many(priv); 532 } 533 534 /* If no hooks left, go away */ 535 if ((NG_NODE_NUMHOOKS(NG_HOOK_NODE(hook)) == 0) 536 && (NG_NODE_IS_VALID(NG_HOOK_NODE(hook)))) 537 ng_rmnode_self(NG_HOOK_NODE(hook)); 538 return (0); 539 } 540 541 /****************************************************************** 542 OTHER FUNCTIONS 543 ******************************************************************/ 544 545 /* 546 * Update internal state after the addition or removal of a "many" link 547 */ 548 static void 549 ng_one2many_update_many(priv_p priv) 550 { 551 uint16_t saveActive = priv->numActiveMany; 552 int linkNum; 553 554 /* Update list of which "many" links are up */ 555 priv->numActiveMany = 0; 556 for (linkNum = 0; linkNum < NG_ONE2MANY_MAX_LINKS; linkNum++) { 557 switch (priv->conf.failAlg) { 558 case NG_ONE2MANY_FAIL_MANUAL: 559 case NG_ONE2MANY_FAIL_NOTIFY: 560 if (priv->many[linkNum].hook != NULL 561 && priv->conf.enabledLinks[linkNum]) { 562 priv->activeMany[priv->numActiveMany] = linkNum; 563 priv->numActiveMany++; 564 } 565 break; 566 #ifdef INVARIANTS 567 default: 568 panic("%s: invalid failAlg", __func__); 569 #endif 570 } 571 } 572 573 if (priv->numActiveMany == 0 && saveActive > 0) 574 ng_one2many_notify(priv, NGM_LINK_IS_DOWN); 575 576 if (saveActive == 0 && priv->numActiveMany > 0) 577 ng_one2many_notify(priv, NGM_LINK_IS_UP); 578 579 /* Update transmit algorithm state */ 580 switch (priv->conf.xmitAlg) { 581 case NG_ONE2MANY_XMIT_ROUNDROBIN: 582 if (priv->numActiveMany > 0) 583 priv->nextMany %= priv->numActiveMany; 584 break; 585 case NG_ONE2MANY_XMIT_ALL: 586 case NG_ONE2MANY_XMIT_FAILOVER: 587 break; 588 #ifdef INVARIANTS 589 default: 590 panic("%s: invalid xmitAlg", __func__); 591 #endif 592 } 593 } 594 595 /* 596 * Notify upstream if we are out of links, or we have at least one link. 597 */ 598 static void 599 ng_one2many_notify(priv_p priv, uint32_t cmd) 600 { 601 struct ng_mesg *msg; 602 int dummy_error = 0; 603 604 if (priv->one.hook == NULL) 605 return; 606 607 NG_MKMESSAGE(msg, NGM_FLOW_COOKIE, cmd, 0, M_NOWAIT); 608 if (msg != NULL) 609 NG_SEND_MSG_HOOK(dummy_error, priv->node, msg, priv->one.hook, 0); 610 } 611