1 2 /* 3 * ng_ether.c 4 * 5 * Copyright (c) 1996-2000 Whistle Communications, Inc. 6 * All rights reserved. 7 * 8 * Subject to the following obligations and disclaimer of warranty, use and 9 * redistribution of this software, in source or object code forms, with or 10 * without modifications are expressly permitted by Whistle Communications; 11 * provided, however, that: 12 * 1. Any and all reproductions of the source or object code must include the 13 * copyright notice above and the following disclaimer of warranties; and 14 * 2. No rights are granted, in any manner or form, to use Whistle 15 * Communications, Inc. trademarks, including the mark "WHISTLE 16 * COMMUNICATIONS" on advertising, endorsements, or otherwise except as 17 * such appears in the above copyright notice or in the software. 18 * 19 * THIS SOFTWARE IS BEING PROVIDED BY WHISTLE COMMUNICATIONS "AS IS", AND 20 * TO THE MAXIMUM EXTENT PERMITTED BY LAW, WHISTLE COMMUNICATIONS MAKES NO 21 * REPRESENTATIONS OR WARRANTIES, EXPRESS OR IMPLIED, REGARDING THIS SOFTWARE, 22 * INCLUDING WITHOUT LIMITATION, ANY AND ALL IMPLIED WARRANTIES OF 23 * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE, OR NON-INFRINGEMENT. 24 * WHISTLE COMMUNICATIONS DOES NOT WARRANT, GUARANTEE, OR MAKE ANY 25 * REPRESENTATIONS REGARDING THE USE OF, OR THE RESULTS OF THE USE OF THIS 26 * SOFTWARE IN TERMS OF ITS CORRECTNESS, ACCURACY, RELIABILITY OR OTHERWISE. 27 * IN NO EVENT SHALL WHISTLE COMMUNICATIONS BE LIABLE FOR ANY DAMAGES 28 * RESULTING FROM OR ARISING OUT OF ANY USE OF THIS SOFTWARE, INCLUDING 29 * WITHOUT LIMITATION, ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, 30 * PUNITIVE, OR CONSEQUENTIAL DAMAGES, PROCUREMENT OF SUBSTITUTE GOODS OR 31 * SERVICES, LOSS OF USE, DATA OR PROFITS, HOWEVER CAUSED AND UNDER ANY 32 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 33 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF 34 * THIS SOFTWARE, EVEN IF WHISTLE COMMUNICATIONS IS ADVISED OF THE POSSIBILITY 35 * OF SUCH DAMAGE. 36 * 37 * Authors: Archie Cobbs <archie@freebsd.org> 38 * Julian Elischer <julian@freebsd.org> 39 * 40 * $FreeBSD: src/sys/netgraph/ng_ether.c,v 1.2.2.13 2002/07/02 20:10:25 archie Exp $ 41 */ 42 43 /* 44 * ng_ether(4) netgraph node type 45 */ 46 47 #include <sys/param.h> 48 #include <sys/systm.h> 49 #include <sys/kernel.h> 50 #include <sys/malloc.h> 51 #include <sys/mbuf.h> 52 #include <sys/errno.h> 53 #include <sys/syslog.h> 54 #include <sys/socket.h> 55 #include <sys/thread2.h> 56 57 #include <net/if.h> 58 #include <net/if_types.h> 59 #include <net/if_arp.h> 60 #include <net/if_var.h> 61 #include <net/ethernet.h> 62 63 #include <netgraph/ng_message.h> 64 #include <netgraph/netgraph.h> 65 #include <netgraph/ng_parse.h> 66 #include "ng_ether.h" 67 68 #define IFP2AC(IFP) ((struct arpcom *)IFP) 69 #define IFP2NG(ifp) (IFP2AC((ifp))->ac_netgraph) 70 71 /* Per-node private data */ 72 struct private { 73 struct ifnet *ifp; /* associated interface */ 74 hook_p upper; /* upper hook connection */ 75 hook_p lower; /* lower OR orphan hook connection */ 76 u_char lowerOrphan; /* whether lower is lower or orphan */ 77 u_char autoSrcAddr; /* always overwrite source address */ 78 u_char promisc; /* promiscuous mode enabled */ 79 u_long hwassist; /* hardware checksum capabilities */ 80 }; 81 typedef struct private *priv_p; 82 83 /* Functional hooks called from if_ethersubr.c */ 84 static void ng_ether_input(struct ifnet *ifp, struct mbuf **mp); 85 static void ng_ether_input_orphan(struct ifnet *ifp, struct mbuf *m); 86 static int ng_ether_output(struct ifnet *ifp, struct mbuf **mp); 87 static void ng_ether_attach(struct ifnet *ifp); 88 static void ng_ether_detach(struct ifnet *ifp); 89 90 /* Other functions */ 91 static void ng_ether_input2(node_p node, struct mbuf **mp); 92 static int ng_ether_rcv_lower(node_p node, struct mbuf *m, meta_p meta); 93 static int ng_ether_rcv_upper(node_p node, struct mbuf *m, meta_p meta); 94 95 /* Netgraph node methods */ 96 static ng_constructor_t ng_ether_constructor; 97 static ng_rcvmsg_t ng_ether_rcvmsg; 98 static ng_shutdown_t ng_ether_rmnode; 99 static ng_newhook_t ng_ether_newhook; 100 static ng_rcvdata_t ng_ether_rcvdata; 101 static ng_disconnect_t ng_ether_disconnect; 102 static int ng_ether_mod_event(module_t mod, int event, void *data); 103 104 /* Parse type for an Ethernet address */ 105 static ng_parse_t ng_enaddr_parse; 106 static ng_unparse_t ng_enaddr_unparse; 107 const struct ng_parse_type ng_ether_enaddr_type = { 108 NULL, 109 NULL, 110 NULL, 111 ng_enaddr_parse, 112 ng_enaddr_unparse, 113 NULL, /* no such thing as a "default" EN address */ 114 0 115 }; 116 117 /* List of commands and how to convert arguments to/from ASCII */ 118 static const struct ng_cmdlist ng_ether_cmdlist[] = { 119 { 120 NGM_ETHER_COOKIE, 121 NGM_ETHER_GET_IFNAME, 122 "getifname", 123 NULL, 124 &ng_parse_string_type 125 }, 126 { 127 NGM_ETHER_COOKIE, 128 NGM_ETHER_GET_IFINDEX, 129 "getifindex", 130 NULL, 131 &ng_parse_int32_type 132 }, 133 { 134 NGM_ETHER_COOKIE, 135 NGM_ETHER_GET_ENADDR, 136 "getenaddr", 137 NULL, 138 &ng_ether_enaddr_type 139 }, 140 { 141 NGM_ETHER_COOKIE, 142 NGM_ETHER_SET_ENADDR, 143 "setenaddr", 144 &ng_ether_enaddr_type, 145 NULL 146 }, 147 { 148 NGM_ETHER_COOKIE, 149 NGM_ETHER_GET_PROMISC, 150 "getpromisc", 151 NULL, 152 &ng_parse_int32_type 153 }, 154 { 155 NGM_ETHER_COOKIE, 156 NGM_ETHER_SET_PROMISC, 157 "setpromisc", 158 &ng_parse_int32_type, 159 NULL 160 }, 161 { 162 NGM_ETHER_COOKIE, 163 NGM_ETHER_GET_AUTOSRC, 164 "getautosrc", 165 NULL, 166 &ng_parse_int32_type 167 }, 168 { 169 NGM_ETHER_COOKIE, 170 NGM_ETHER_SET_AUTOSRC, 171 "setautosrc", 172 &ng_parse_int32_type, 173 NULL 174 }, 175 { 0 } 176 }; 177 178 static struct ng_type ng_ether_typestruct = { 179 NG_VERSION, 180 NG_ETHER_NODE_TYPE, 181 ng_ether_mod_event, 182 ng_ether_constructor, 183 ng_ether_rcvmsg, 184 ng_ether_rmnode, 185 ng_ether_newhook, 186 NULL, 187 NULL, 188 ng_ether_rcvdata, 189 ng_ether_rcvdata, 190 ng_ether_disconnect, 191 ng_ether_cmdlist, 192 }; 193 NETGRAPH_INIT(ether, &ng_ether_typestruct); 194 195 /****************************************************************** 196 ETHERNET FUNCTION HOOKS 197 ******************************************************************/ 198 199 /* 200 * Handle a packet that has come in on an interface. We get to 201 * look at it here before any upper layer protocols do. 202 */ 203 static void 204 ng_ether_input(struct ifnet *ifp, struct mbuf **mp) 205 { 206 const node_p node = IFP2NG(ifp); 207 const priv_p priv = node->private; 208 209 /* If "lower" hook not connected, let packet continue */ 210 if (priv->lower == NULL || priv->lowerOrphan) 211 return; 212 ng_ether_input2(node, mp); 213 } 214 215 /* 216 * Handle a packet that has come in on an interface, and which 217 * does not match any of our known protocols (an ``orphan''). 218 */ 219 static void 220 ng_ether_input_orphan(struct ifnet *ifp, struct mbuf *m) 221 { 222 const node_p node = IFP2NG(ifp); 223 const priv_p priv = node->private; 224 225 /* If "orphan" hook not connected, let packet continue */ 226 if (priv->lower == NULL || !priv->lowerOrphan) { 227 m_freem(m); 228 return; 229 } 230 ng_ether_input2(node, &m); 231 if (m != NULL) 232 m_freem(m); 233 } 234 235 /* 236 * Handle a packet that has come in on an interface. 237 * The Ethernet header has already been detached from the mbuf, 238 * so we have to put it back. 239 * 240 * NOTE: this function will get called at splimp() 241 */ 242 static void 243 ng_ether_input2(node_p node, struct mbuf **mp) 244 { 245 const priv_p priv = node->private; 246 meta_p meta = NULL; 247 int error; 248 249 /* Send out lower/orphan hook */ 250 ng_queue_data(priv->lower, *mp, meta); 251 *mp = NULL; 252 } 253 254 /* 255 * Handle a packet that is going out on an interface. 256 * The Ethernet header is already attached to the mbuf. 257 */ 258 static int 259 ng_ether_output(struct ifnet *ifp, struct mbuf **mp) 260 { 261 const node_p node = IFP2NG(ifp); 262 const priv_p priv = node->private; 263 meta_p meta = NULL; 264 int error = 0; 265 266 /* If "upper" hook not connected, let packet continue */ 267 if (priv->upper == NULL) 268 return (0); 269 270 /* Send it out "upper" hook */ 271 NG_SEND_DATA(error, priv->upper, *mp, meta); 272 *mp = NULL; 273 return (error); 274 } 275 276 /* 277 * A new Ethernet interface has been attached. 278 * Create a new node for it, etc. 279 */ 280 static void 281 ng_ether_attach(struct ifnet *ifp) 282 { 283 char name[IFNAMSIZ + 1]; 284 priv_p priv; 285 node_p node; 286 287 /* Create node */ 288 KASSERT(!IFP2NG(ifp), ("%s: node already exists?", __func__)); 289 strlcpy(name, ifp->if_xname, sizeof(name)); 290 if (ng_make_node_common(&ng_ether_typestruct, &node) != 0) { 291 log(LOG_ERR, "%s: can't %s for %s\n", 292 __func__, "create node", name); 293 return; 294 } 295 296 /* Allocate private data */ 297 priv = kmalloc(sizeof(*priv), M_NETGRAPH, M_NOWAIT | M_ZERO); 298 if (priv == NULL) { 299 log(LOG_ERR, "%s: can't %s for %s\n", 300 __func__, "allocate memory", name); 301 ng_unref(node); 302 return; 303 } 304 node->private = priv; 305 priv->ifp = ifp; 306 IFP2NG(ifp) = node; 307 priv->autoSrcAddr = 1; 308 priv->hwassist = ifp->if_hwassist; 309 310 /* Try to give the node the same name as the interface */ 311 if (ng_name_node(node, name) != 0) { 312 log(LOG_WARNING, "%s: can't name node %s\n", 313 __func__, name); 314 } 315 } 316 317 /* 318 * An Ethernet interface is being detached. 319 * Destroy its node. 320 */ 321 static void 322 ng_ether_detach(struct ifnet *ifp) 323 { 324 const node_p node = IFP2NG(ifp); 325 priv_p priv; 326 327 if (node == NULL) /* no node (why not?), ignore */ 328 return; 329 ng_rmnode(node); /* break all links to other nodes */ 330 node->flags |= NG_INVALID; 331 ng_unname(node); /* free name (and its reference) */ 332 IFP2NG(ifp) = NULL; /* detach node from interface */ 333 priv = node->private; /* free node private info */ 334 bzero(priv, sizeof(*priv)); 335 kfree(priv, M_NETGRAPH); 336 node->private = NULL; 337 ng_unref(node); /* free node itself */ 338 } 339 340 /****************************************************************** 341 NETGRAPH NODE METHODS 342 ******************************************************************/ 343 344 /* 345 * It is not possible or allowable to create a node of this type. 346 * Nodes get created when the interface is attached (or, when 347 * this node type's KLD is loaded). 348 */ 349 static int 350 ng_ether_constructor(node_p *nodep) 351 { 352 return (EINVAL); 353 } 354 355 /* 356 * Check for attaching a new hook. 357 */ 358 static int 359 ng_ether_newhook(node_p node, hook_p hook, const char *name) 360 { 361 const priv_p priv = node->private; 362 u_char orphan = priv->lowerOrphan; 363 hook_p *hookptr; 364 365 /* Divert hook is an alias for lower */ 366 if (strcmp(name, NG_ETHER_HOOK_DIVERT) == 0) 367 name = NG_ETHER_HOOK_LOWER; 368 369 /* Which hook? */ 370 if (strcmp(name, NG_ETHER_HOOK_UPPER) == 0) 371 hookptr = &priv->upper; 372 else if (strcmp(name, NG_ETHER_HOOK_LOWER) == 0) { 373 hookptr = &priv->lower; 374 orphan = 0; 375 } else if (strcmp(name, NG_ETHER_HOOK_ORPHAN) == 0) { 376 hookptr = &priv->lower; 377 orphan = 1; 378 } else 379 return (EINVAL); 380 381 /* Check if already connected (shouldn't be, but doesn't hurt) */ 382 if (*hookptr != NULL) 383 return (EISCONN); 384 385 /* Disable hardware checksums while 'upper' hook is connected */ 386 if (hookptr == &priv->upper) 387 priv->ifp->if_hwassist = 0; 388 389 /* OK */ 390 *hookptr = hook; 391 priv->lowerOrphan = orphan; 392 return (0); 393 } 394 395 /* 396 * Receive an incoming control message. 397 */ 398 static int 399 ng_ether_rcvmsg(node_p node, struct ng_mesg *msg, 400 const char *retaddr, struct ng_mesg **rptr) 401 { 402 const priv_p priv = node->private; 403 struct ng_mesg *resp = NULL; 404 int error = 0; 405 406 switch (msg->header.typecookie) { 407 case NGM_ETHER_COOKIE: 408 switch (msg->header.cmd) { 409 case NGM_ETHER_GET_IFNAME: 410 NG_MKRESPONSE(resp, msg, IFNAMSIZ + 1, M_NOWAIT); 411 if (resp == NULL) { 412 error = ENOMEM; 413 break; 414 } 415 strlcpy(resp->data, priv->ifp->if_xname, IFNAMSIZ); 416 break; 417 case NGM_ETHER_GET_IFINDEX: 418 NG_MKRESPONSE(resp, msg, sizeof(u_int32_t), M_NOWAIT); 419 if (resp == NULL) { 420 error = ENOMEM; 421 break; 422 } 423 *((u_int32_t *)resp->data) = priv->ifp->if_index; 424 break; 425 case NGM_ETHER_GET_ENADDR: 426 NG_MKRESPONSE(resp, msg, ETHER_ADDR_LEN, M_NOWAIT); 427 if (resp == NULL) { 428 error = ENOMEM; 429 break; 430 } 431 bcopy((IFP2AC(priv->ifp))->ac_enaddr, 432 resp->data, ETHER_ADDR_LEN); 433 break; 434 case NGM_ETHER_SET_ENADDR: 435 { 436 if (msg->header.arglen != ETHER_ADDR_LEN) { 437 error = EINVAL; 438 break; 439 } 440 error = if_setlladdr(priv->ifp, 441 (u_char *)msg->data, ETHER_ADDR_LEN); 442 break; 443 } 444 case NGM_ETHER_GET_PROMISC: 445 NG_MKRESPONSE(resp, msg, sizeof(u_int32_t), M_NOWAIT); 446 if (resp == NULL) { 447 error = ENOMEM; 448 break; 449 } 450 *((u_int32_t *)resp->data) = priv->promisc; 451 break; 452 case NGM_ETHER_SET_PROMISC: 453 { 454 u_char want; 455 456 if (msg->header.arglen != sizeof(u_int32_t)) { 457 error = EINVAL; 458 break; 459 } 460 want = !!*((u_int32_t *)msg->data); 461 if (want ^ priv->promisc) { 462 if ((error = ifpromisc(priv->ifp, want)) != 0) 463 break; 464 priv->promisc = want; 465 } 466 break; 467 } 468 case NGM_ETHER_GET_AUTOSRC: 469 NG_MKRESPONSE(resp, msg, sizeof(u_int32_t), M_NOWAIT); 470 if (resp == NULL) { 471 error = ENOMEM; 472 break; 473 } 474 *((u_int32_t *)resp->data) = priv->autoSrcAddr; 475 break; 476 case NGM_ETHER_SET_AUTOSRC: 477 if (msg->header.arglen != sizeof(u_int32_t)) { 478 error = EINVAL; 479 break; 480 } 481 priv->autoSrcAddr = !!*((u_int32_t *)msg->data); 482 break; 483 default: 484 error = EINVAL; 485 break; 486 } 487 break; 488 default: 489 error = EINVAL; 490 break; 491 } 492 if (rptr) 493 *rptr = resp; 494 else if (resp != NULL) 495 kfree(resp, M_NETGRAPH); 496 kfree(msg, M_NETGRAPH); 497 return (error); 498 } 499 500 /* 501 * Receive data on a hook. 502 */ 503 static int 504 ng_ether_rcvdata(hook_p hook, struct mbuf *m, meta_p meta) 505 { 506 const node_p node = hook->node; 507 const priv_p priv = node->private; 508 509 if (hook == priv->lower) 510 return ng_ether_rcv_lower(node, m, meta); 511 if (hook == priv->upper) 512 return ng_ether_rcv_upper(node, m, meta); 513 panic("%s: weird hook", __func__); 514 } 515 516 /* 517 * Handle an mbuf received on the "lower" hook. 518 */ 519 static int 520 ng_ether_rcv_lower(node_p node, struct mbuf *m, meta_p meta) 521 { 522 const priv_p priv = node->private; 523 struct ifnet *const ifp = priv->ifp; 524 int error; 525 526 /* Discard meta info */ 527 NG_FREE_META(meta); 528 529 /* Check whether interface is ready for packets */ 530 if ((ifp->if_flags & (IFF_UP|IFF_RUNNING)) != (IFF_UP|IFF_RUNNING)) { 531 m_freem(m); 532 return (ENETDOWN); 533 } 534 535 /* Make sure header is fully pulled up */ 536 if (m->m_pkthdr.len < sizeof(struct ether_header)) { 537 m_freem(m); 538 return (EINVAL); 539 } 540 if (m->m_len < sizeof(struct ether_header) 541 && (m = m_pullup(m, sizeof(struct ether_header))) == NULL) 542 return (ENOBUFS); 543 544 /* Drop in the MAC address if desired */ 545 if (priv->autoSrcAddr) { 546 547 /* Make the mbuf writable if it's not already */ 548 if (!M_WRITABLE(m) 549 && (m = m_pullup(m, sizeof(struct ether_header))) == NULL) 550 return (ENOBUFS); 551 552 /* Overwrite source MAC address */ 553 bcopy((IFP2AC(ifp))->ac_enaddr, 554 mtod(m, struct ether_header *)->ether_shost, 555 ETHER_ADDR_LEN); 556 } 557 558 /* Send it on its way */ 559 error = ether_output_frame(ifp, m); 560 return (error); 561 } 562 563 /* 564 * Handle an mbuf received on the "upper" hook. 565 */ 566 static int 567 ng_ether_rcv_upper(node_p node, struct mbuf *m, meta_p meta) 568 { 569 const priv_p priv = node->private; 570 571 /* Discard meta info */ 572 NG_FREE_META(meta); 573 574 /* Check length and pull off header */ 575 if (m->m_pkthdr.len < ETHER_HDR_LEN) { 576 m_freem(m); 577 return (EINVAL); 578 } 579 if (m->m_len < ETHER_HDR_LEN && 580 (m = m_pullup(m, ETHER_HDR_LEN)) == NULL) 581 return (ENOBUFS); 582 583 m->m_pkthdr.rcvif = priv->ifp; 584 585 /* Route packet back in */ 586 ether_demux(m); 587 return (0); 588 } 589 590 /* 591 * Shutdown node. This resets the node but does not remove it. 592 */ 593 static int 594 ng_ether_rmnode(node_p node) 595 { 596 const priv_p priv = node->private; 597 598 ng_cutlinks(node); 599 node->flags &= ~NG_INVALID; /* bounce back to life */ 600 if (priv->promisc) { /* disable promiscuous mode */ 601 ifpromisc(priv->ifp, 0); 602 priv->promisc = 0; 603 } 604 priv->autoSrcAddr = 1; /* reset auto-src-addr flag */ 605 return (0); 606 } 607 608 /* 609 * Hook disconnection. 610 */ 611 static int 612 ng_ether_disconnect(hook_p hook) 613 { 614 const priv_p priv = hook->node->private; 615 616 if (hook == priv->upper) { 617 priv->upper = NULL; 618 priv->ifp->if_hwassist = priv->hwassist; /* restore h/w csum */ 619 } else if (hook == priv->lower) { 620 priv->lower = NULL; 621 priv->lowerOrphan = 0; 622 } else 623 panic("%s: weird hook", __func__); 624 if (hook->node->numhooks == 0) 625 ng_rmnode(hook->node); /* reset node */ 626 return (0); 627 } 628 629 static int 630 ng_enaddr_parse(const struct ng_parse_type *type, 631 const char *s, int *const off, const u_char *const start, 632 u_char *const buf, int *const buflen) 633 { 634 char *eptr; 635 u_long val; 636 int i; 637 638 if (*buflen < ETHER_ADDR_LEN) 639 return (ERANGE); 640 for (i = 0; i < ETHER_ADDR_LEN; i++) { 641 val = strtoul(s + *off, &eptr, 16); 642 if (val > 0xff || eptr == s + *off) 643 return (EINVAL); 644 buf[i] = (u_char)val; 645 *off = (eptr - s); 646 if (i < ETHER_ADDR_LEN - 1) { 647 if (*eptr != ':') 648 return (EINVAL); 649 (*off)++; 650 } 651 } 652 *buflen = ETHER_ADDR_LEN; 653 return (0); 654 } 655 656 static int 657 ng_enaddr_unparse(const struct ng_parse_type *type, 658 const u_char *data, int *off, char *cbuf, int cbuflen) 659 { 660 int len; 661 662 len = ksnprintf(cbuf, cbuflen, "%02x:%02x:%02x:%02x:%02x:%02x", 663 data[*off], data[*off + 1], data[*off + 2], 664 data[*off + 3], data[*off + 4], data[*off + 5]); 665 if (len >= cbuflen) 666 return (ERANGE); 667 *off += ETHER_ADDR_LEN; 668 return (0); 669 } 670 671 /****************************************************************** 672 INITIALIZATION 673 ******************************************************************/ 674 675 /* 676 * Handle loading and unloading for this node type. 677 */ 678 static int 679 ng_ether_mod_event(module_t mod, int event, void *data) 680 { 681 struct ifnet *ifp; 682 int error = 0; 683 684 crit_enter(); 685 switch (event) { 686 case MOD_LOAD: 687 688 /* Register function hooks */ 689 if (ng_ether_attach_p != NULL) { 690 error = EEXIST; 691 break; 692 } 693 ng_ether_attach_p = ng_ether_attach; 694 ng_ether_detach_p = ng_ether_detach; 695 ng_ether_output_p = ng_ether_output; 696 ng_ether_input_p = ng_ether_input; 697 ng_ether_input_orphan_p = ng_ether_input_orphan; 698 699 /* Create nodes for any already-existing Ethernet interfaces */ 700 TAILQ_FOREACH(ifp, &ifnet, if_link) { 701 if (ifp->if_type == IFT_ETHER || 702 ifp->if_type == IFT_L2VLAN) 703 ng_ether_attach(ifp); 704 } 705 break; 706 707 case MOD_UNLOAD: 708 709 /* 710 * Note that the base code won't try to unload us until 711 * all nodes have been removed, and that can't happen 712 * until all Ethernet interfaces are removed. In any 713 * case, we know there are no nodes left if the action 714 * is MOD_UNLOAD, so there's no need to detach any nodes. 715 */ 716 717 /* Unregister function hooks */ 718 ng_ether_attach_p = NULL; 719 ng_ether_detach_p = NULL; 720 ng_ether_output_p = NULL; 721 ng_ether_input_p = NULL; 722 ng_ether_input_orphan_p = NULL; 723 break; 724 725 default: 726 error = EOPNOTSUPP; 727 break; 728 } 729 crit_exit(); 730 return (error); 731 } 732 733