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$ 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 56 #include <net/if.h> 57 #include <net/if_types.h> 58 #include <net/if_arp.h> 59 #include <net/if_var.h> 60 #include <net/ethernet.h> 61 62 #include <netgraph/ng_message.h> 63 #include <netgraph/netgraph.h> 64 #include <netgraph/ng_parse.h> 65 #include <netgraph/ng_ether.h> 66 67 #define IFP2NG(ifp) ((struct ng_node *)((struct arpcom *)(ifp))->ac_netgraph) 68 69 /* Per-node private data */ 70 struct private { 71 struct ifnet *ifp; /* associated interface */ 72 hook_p upper; /* upper hook connection */ 73 hook_p lower; /* lower hook connection */ 74 hook_p orphan; /* orphan hook connection */ 75 u_char autoSrcAddr; /* always overwrite source address */ 76 u_char promisc; /* promiscuous mode enabled */ 77 u_long hwassist; /* hardware checksum capabilities */ 78 u_int flags; /* flags e.g. really die */ 79 }; 80 typedef struct private *priv_p; 81 82 /* Hook pointers used by if_ethersubr.c to callback to netgraph */ 83 extern void (*ng_ether_input_p)(struct ifnet *ifp, struct mbuf **mp); 84 extern void (*ng_ether_input_orphan_p)(struct ifnet *ifp, struct mbuf *m); 85 extern int (*ng_ether_output_p)(struct ifnet *ifp, struct mbuf **mp); 86 extern void (*ng_ether_attach_p)(struct ifnet *ifp); 87 extern void (*ng_ether_detach_p)(struct ifnet *ifp); 88 89 /* Functional hooks called from if_ethersubr.c */ 90 static void ng_ether_input(struct ifnet *ifp, struct mbuf **mp); 91 static void ng_ether_input_orphan(struct ifnet *ifp, struct mbuf *m); 92 static int ng_ether_output(struct ifnet *ifp, struct mbuf **mp); 93 static void ng_ether_attach(struct ifnet *ifp); 94 static void ng_ether_detach(struct ifnet *ifp); 95 96 /* Other functions */ 97 static int ng_ether_rcv_lower(node_p node, struct mbuf *m); 98 static int ng_ether_rcv_upper(node_p node, struct mbuf *m); 99 100 /* Netgraph node methods */ 101 static ng_constructor_t ng_ether_constructor; 102 static ng_rcvmsg_t ng_ether_rcvmsg; 103 static ng_shutdown_t ng_ether_shutdown; 104 static ng_newhook_t ng_ether_newhook; 105 static ng_connect_t ng_ether_connect; 106 static ng_rcvdata_t ng_ether_rcvdata; 107 static ng_disconnect_t ng_ether_disconnect; 108 static int ng_ether_mod_event(module_t mod, int event, void *data); 109 110 /* List of commands and how to convert arguments to/from ASCII */ 111 static const struct ng_cmdlist ng_ether_cmdlist[] = { 112 { 113 NGM_ETHER_COOKIE, 114 NGM_ETHER_GET_IFNAME, 115 "getifname", 116 NULL, 117 &ng_parse_string_type 118 }, 119 { 120 NGM_ETHER_COOKIE, 121 NGM_ETHER_GET_IFINDEX, 122 "getifindex", 123 NULL, 124 &ng_parse_int32_type 125 }, 126 { 127 NGM_ETHER_COOKIE, 128 NGM_ETHER_GET_ENADDR, 129 "getenaddr", 130 NULL, 131 &ng_parse_enaddr_type 132 }, 133 { 134 NGM_ETHER_COOKIE, 135 NGM_ETHER_SET_ENADDR, 136 "setenaddr", 137 &ng_parse_enaddr_type, 138 NULL 139 }, 140 { 141 NGM_ETHER_COOKIE, 142 NGM_ETHER_GET_PROMISC, 143 "getpromisc", 144 NULL, 145 &ng_parse_int32_type 146 }, 147 { 148 NGM_ETHER_COOKIE, 149 NGM_ETHER_SET_PROMISC, 150 "setpromisc", 151 &ng_parse_int32_type, 152 NULL 153 }, 154 { 155 NGM_ETHER_COOKIE, 156 NGM_ETHER_GET_AUTOSRC, 157 "getautosrc", 158 NULL, 159 &ng_parse_int32_type 160 }, 161 { 162 NGM_ETHER_COOKIE, 163 NGM_ETHER_SET_AUTOSRC, 164 "setautosrc", 165 &ng_parse_int32_type, 166 NULL 167 }, 168 { 0 } 169 }; 170 171 static struct ng_type ng_ether_typestruct = { 172 .version = NG_ABI_VERSION, 173 .name = NG_ETHER_NODE_TYPE, 174 .mod_event = ng_ether_mod_event, 175 .constructor = ng_ether_constructor, 176 .rcvmsg = ng_ether_rcvmsg, 177 .shutdown = ng_ether_shutdown, 178 .newhook = ng_ether_newhook, 179 .connect = ng_ether_connect, 180 .rcvdata = ng_ether_rcvdata, 181 .disconnect = ng_ether_disconnect, 182 .cmdlist = ng_ether_cmdlist, 183 }; 184 MODULE_VERSION(ng_ether, 1); 185 NETGRAPH_INIT(ether, &ng_ether_typestruct); 186 187 /****************************************************************** 188 ETHERNET FUNCTION HOOKS 189 ******************************************************************/ 190 191 /* 192 * Handle a packet that has come in on an interface. We get to 193 * look at it here before any upper layer protocols do. 194 * 195 * NOTE: this function will get called at splimp() 196 */ 197 static void 198 ng_ether_input(struct ifnet *ifp, struct mbuf **mp) 199 { 200 const node_p node = IFP2NG(ifp); 201 const priv_p priv = NG_NODE_PRIVATE(node); 202 int error; 203 204 /* If "lower" hook not connected, let packet continue */ 205 if (priv->lower == NULL) 206 return; 207 NG_SEND_DATA_ONLY(error, priv->lower, *mp); /* sets *mp = NULL */ 208 } 209 210 /* 211 * Handle a packet that has come in on an interface, and which 212 * does not match any of our known protocols (an ``orphan''). 213 * 214 * NOTE: this function will get called at splimp() 215 */ 216 static void 217 ng_ether_input_orphan(struct ifnet *ifp, struct mbuf *m) 218 { 219 const node_p node = IFP2NG(ifp); 220 const priv_p priv = NG_NODE_PRIVATE(node); 221 int error; 222 223 /* If "orphan" hook not connected, discard packet */ 224 if (priv->orphan == NULL) { 225 m_freem(m); 226 return; 227 } 228 NG_SEND_DATA_ONLY(error, priv->orphan, m); 229 } 230 231 /* 232 * Handle a packet that is going out on an interface. 233 * The Ethernet header is already attached to the mbuf. 234 */ 235 static int 236 ng_ether_output(struct ifnet *ifp, struct mbuf **mp) 237 { 238 const node_p node = IFP2NG(ifp); 239 const priv_p priv = NG_NODE_PRIVATE(node); 240 int error = 0; 241 242 /* If "upper" hook not connected, let packet continue */ 243 if (priv->upper == NULL) 244 return (0); 245 246 /* Send it out "upper" hook */ 247 NG_SEND_DATA_ONLY(error, priv->upper, *mp); 248 return (error); 249 } 250 251 /* 252 * A new Ethernet interface has been attached. 253 * Create a new node for it, etc. 254 */ 255 static void 256 ng_ether_attach(struct ifnet *ifp) 257 { 258 priv_p priv; 259 node_p node; 260 261 /* Create node */ 262 KASSERT(!IFP2NG(ifp), ("%s: node already exists?", __func__)); 263 if (ng_make_node_common(&ng_ether_typestruct, &node) != 0) { 264 log(LOG_ERR, "%s: can't %s for %s\n", 265 __func__, "create node", ifp->if_xname); 266 return; 267 } 268 269 /* Allocate private data */ 270 MALLOC(priv, priv_p, sizeof(*priv), M_NETGRAPH, M_NOWAIT | M_ZERO); 271 if (priv == NULL) { 272 log(LOG_ERR, "%s: can't %s for %s\n", 273 __func__, "allocate memory", ifp->if_xname); 274 NG_NODE_UNREF(node); 275 return; 276 } 277 NG_NODE_SET_PRIVATE(node, priv); 278 priv->ifp = ifp; 279 IFP2NG(ifp) = node; 280 priv->autoSrcAddr = 1; 281 priv->hwassist = ifp->if_hwassist; 282 283 /* Try to give the node the same name as the interface */ 284 if (ng_name_node(node, ifp->if_xname) != 0) { 285 log(LOG_WARNING, "%s: can't name node %s\n", 286 __func__, ifp->if_xname); 287 } 288 } 289 290 /* 291 * An Ethernet interface is being detached. 292 * REALLY Destroy its node. 293 */ 294 static void 295 ng_ether_detach(struct ifnet *ifp) 296 { 297 const node_p node = IFP2NG(ifp); 298 const priv_p priv = NG_NODE_PRIVATE(node); 299 300 if (node == NULL) /* no node (why not?), ignore */ 301 return; 302 NG_NODE_REALLY_DIE(node); /* Force real removal of node */ 303 /* 304 * We can't assume the ifnet is still around when we run shutdown 305 * So zap it now. XXX We HOPE that anything running at this time 306 * handles it (as it should in the non netgraph case). 307 */ 308 IFP2NG(ifp) = NULL; 309 priv->ifp = NULL; /* XXX race if interrupted an output packet */ 310 ng_rmnode_self(node); /* remove all netgraph parts */ 311 } 312 313 /****************************************************************** 314 NETGRAPH NODE METHODS 315 ******************************************************************/ 316 317 /* 318 * It is not possible or allowable to create a node of this type. 319 * Nodes get created when the interface is attached (or, when 320 * this node type's KLD is loaded). 321 */ 322 static int 323 ng_ether_constructor(node_p node) 324 { 325 return (EINVAL); 326 } 327 328 /* 329 * Check for attaching a new hook. 330 */ 331 static int 332 ng_ether_newhook(node_p node, hook_p hook, const char *name) 333 { 334 const priv_p priv = NG_NODE_PRIVATE(node); 335 hook_p *hookptr; 336 337 /* Divert hook is an alias for lower */ 338 if (strcmp(name, NG_ETHER_HOOK_DIVERT) == 0) 339 name = NG_ETHER_HOOK_LOWER; 340 341 /* Which hook? */ 342 if (strcmp(name, NG_ETHER_HOOK_UPPER) == 0) 343 hookptr = &priv->upper; 344 else if (strcmp(name, NG_ETHER_HOOK_LOWER) == 0) 345 hookptr = &priv->lower; 346 else if (strcmp(name, NG_ETHER_HOOK_ORPHAN) == 0) 347 hookptr = &priv->orphan; 348 else 349 return (EINVAL); 350 351 /* Check if already connected (shouldn't be, but doesn't hurt) */ 352 if (*hookptr != NULL) 353 return (EISCONN); 354 355 /* Disable hardware checksums while 'upper' hook is connected */ 356 if (hookptr == &priv->upper) 357 priv->ifp->if_hwassist = 0; 358 359 /* OK */ 360 *hookptr = hook; 361 return (0); 362 } 363 364 /* 365 * Hooks are attached, adjust to force queueing. 366 * We don't really care which hook it is. 367 * they should all be queuing for outgoing data. 368 */ 369 static int 370 ng_ether_connect(hook_p hook) 371 { 372 NG_HOOK_FORCE_QUEUE(NG_HOOK_PEER(hook)); 373 return (0); 374 } 375 376 /* 377 * Receive an incoming control message. 378 */ 379 static int 380 ng_ether_rcvmsg(node_p node, item_p item, hook_p lasthook) 381 { 382 const priv_p priv = NG_NODE_PRIVATE(node); 383 struct ng_mesg *resp = NULL; 384 int error = 0; 385 struct ng_mesg *msg; 386 387 NGI_GET_MSG(item, msg); 388 switch (msg->header.typecookie) { 389 case NGM_ETHER_COOKIE: 390 switch (msg->header.cmd) { 391 case NGM_ETHER_GET_IFNAME: 392 NG_MKRESPONSE(resp, msg, IFNAMSIZ + 1, M_NOWAIT); 393 if (resp == NULL) { 394 error = ENOMEM; 395 break; 396 } 397 strlcpy(resp->data, priv->ifp->if_xname, IFNAMSIZ + 1); 398 break; 399 case NGM_ETHER_GET_IFINDEX: 400 NG_MKRESPONSE(resp, msg, sizeof(u_int32_t), M_NOWAIT); 401 if (resp == NULL) { 402 error = ENOMEM; 403 break; 404 } 405 *((u_int32_t *)resp->data) = priv->ifp->if_index; 406 break; 407 case NGM_ETHER_GET_ENADDR: 408 NG_MKRESPONSE(resp, msg, ETHER_ADDR_LEN, M_NOWAIT); 409 if (resp == NULL) { 410 error = ENOMEM; 411 break; 412 } 413 bcopy((IFP2AC(priv->ifp))->ac_enaddr, 414 resp->data, ETHER_ADDR_LEN); 415 break; 416 case NGM_ETHER_SET_ENADDR: 417 { 418 if (msg->header.arglen != ETHER_ADDR_LEN) { 419 error = EINVAL; 420 break; 421 } 422 error = if_setlladdr(priv->ifp, 423 (u_char *)msg->data, ETHER_ADDR_LEN); 424 break; 425 } 426 case NGM_ETHER_GET_PROMISC: 427 NG_MKRESPONSE(resp, msg, sizeof(u_int32_t), M_NOWAIT); 428 if (resp == NULL) { 429 error = ENOMEM; 430 break; 431 } 432 *((u_int32_t *)resp->data) = priv->promisc; 433 break; 434 case NGM_ETHER_SET_PROMISC: 435 { 436 u_char want; 437 438 if (msg->header.arglen != sizeof(u_int32_t)) { 439 error = EINVAL; 440 break; 441 } 442 want = !!*((u_int32_t *)msg->data); 443 if (want ^ priv->promisc) { 444 if ((error = ifpromisc(priv->ifp, want)) != 0) 445 break; 446 priv->promisc = want; 447 } 448 break; 449 } 450 case NGM_ETHER_GET_AUTOSRC: 451 NG_MKRESPONSE(resp, msg, sizeof(u_int32_t), M_NOWAIT); 452 if (resp == NULL) { 453 error = ENOMEM; 454 break; 455 } 456 *((u_int32_t *)resp->data) = priv->autoSrcAddr; 457 break; 458 case NGM_ETHER_SET_AUTOSRC: 459 if (msg->header.arglen != sizeof(u_int32_t)) { 460 error = EINVAL; 461 break; 462 } 463 priv->autoSrcAddr = !!*((u_int32_t *)msg->data); 464 break; 465 default: 466 error = EINVAL; 467 break; 468 } 469 break; 470 default: 471 error = EINVAL; 472 break; 473 } 474 NG_RESPOND_MSG(error, node, item, resp); 475 NG_FREE_MSG(msg); 476 return (error); 477 } 478 479 /* 480 * Receive data on a hook. 481 */ 482 static int 483 ng_ether_rcvdata(hook_p hook, item_p item) 484 { 485 const node_p node = NG_HOOK_NODE(hook); 486 const priv_p priv = NG_NODE_PRIVATE(node); 487 struct mbuf *m; 488 489 NGI_GET_M(item, m); 490 NG_FREE_ITEM(item); 491 492 if (hook == priv->lower || hook == priv->orphan) 493 return ng_ether_rcv_lower(node, m); 494 if (hook == priv->upper) 495 return ng_ether_rcv_upper(node, m); 496 panic("%s: weird hook", __func__); 497 #ifdef RESTARTABLE_PANICS /* so we don't get an error msg in LINT */ 498 return NULL; 499 #endif 500 } 501 502 /* 503 * Handle an mbuf received on the "lower" or "orphan" hook. 504 */ 505 static int 506 ng_ether_rcv_lower(node_p node, struct mbuf *m) 507 { 508 const priv_p priv = NG_NODE_PRIVATE(node); 509 struct ifnet *const ifp = priv->ifp; 510 511 /* Check whether interface is ready for packets */ 512 if ((ifp->if_flags & (IFF_UP|IFF_RUNNING)) != (IFF_UP|IFF_RUNNING)) { 513 NG_FREE_M(m); 514 return (ENETDOWN); 515 } 516 517 /* Make sure header is fully pulled up */ 518 if (m->m_pkthdr.len < sizeof(struct ether_header)) { 519 NG_FREE_M(m); 520 return (EINVAL); 521 } 522 if (m->m_len < sizeof(struct ether_header) 523 && (m = m_pullup(m, sizeof(struct ether_header))) == NULL) 524 return (ENOBUFS); 525 526 /* Drop in the MAC address if desired */ 527 if (priv->autoSrcAddr) { 528 529 /* Make the mbuf writable if it's not already */ 530 if (!M_WRITABLE(m) 531 && (m = m_pullup(m, sizeof(struct ether_header))) == NULL) 532 return (ENOBUFS); 533 534 /* Overwrite source MAC address */ 535 bcopy((IFP2AC(ifp))->ac_enaddr, 536 mtod(m, struct ether_header *)->ether_shost, 537 ETHER_ADDR_LEN); 538 } 539 540 /* Send it on its way */ 541 return ether_output_frame(ifp, m); 542 } 543 544 /* 545 * Handle an mbuf received on the "upper" hook. 546 */ 547 static int 548 ng_ether_rcv_upper(node_p node, struct mbuf *m) 549 { 550 const priv_p priv = NG_NODE_PRIVATE(node); 551 552 m->m_pkthdr.rcvif = priv->ifp; 553 554 /* Route packet back in */ 555 ether_demux(priv->ifp, m); 556 return (0); 557 } 558 559 /* 560 * Shutdown node. This resets the node but does not remove it 561 * unless the REALLY_DIE flag is set. 562 */ 563 static int 564 ng_ether_shutdown(node_p node) 565 { 566 const priv_p priv = NG_NODE_PRIVATE(node); 567 568 if (node->nd_flags & NG_REALLY_DIE) { 569 /* 570 * WE came here because the ethernet card is being unloaded, 571 * so stop being persistant. 572 * Actually undo all the things we did on creation. 573 * Assume the ifp has already been freed. 574 */ 575 NG_NODE_SET_PRIVATE(node, NULL); 576 FREE(priv, M_NETGRAPH); 577 NG_NODE_UNREF(node); /* free node itself */ 578 return (0); 579 } 580 if (priv->promisc) { /* disable promiscuous mode */ 581 (void)ifpromisc(priv->ifp, 0); 582 priv->promisc = 0; 583 } 584 priv->autoSrcAddr = 1; /* reset auto-src-addr flag */ 585 node->nd_flags &= ~NG_INVALID; /* Signal ng_rmnode we are persisant */ 586 return (0); 587 } 588 589 /* 590 * Hook disconnection. 591 */ 592 static int 593 ng_ether_disconnect(hook_p hook) 594 { 595 const priv_p priv = NG_NODE_PRIVATE(NG_HOOK_NODE(hook)); 596 597 if (hook == priv->upper) { 598 priv->upper = NULL; 599 if (priv->ifp != NULL) /* restore h/w csum */ 600 priv->ifp->if_hwassist = priv->hwassist; 601 } else if (hook == priv->lower) 602 priv->lower = NULL; 603 else if (hook == priv->orphan) 604 priv->orphan = NULL; 605 else 606 panic("%s: weird hook", __func__); 607 if ((NG_NODE_NUMHOOKS(NG_HOOK_NODE(hook)) == 0) 608 && (NG_NODE_IS_VALID(NG_HOOK_NODE(hook)))) 609 ng_rmnode_self(NG_HOOK_NODE(hook)); /* reset node */ 610 return (0); 611 } 612 613 /****************************************************************** 614 INITIALIZATION 615 ******************************************************************/ 616 617 /* 618 * Handle loading and unloading for this node type. 619 */ 620 static int 621 ng_ether_mod_event(module_t mod, int event, void *data) 622 { 623 struct ifnet *ifp; 624 int error = 0; 625 int s; 626 627 s = splnet(); 628 switch (event) { 629 case MOD_LOAD: 630 631 /* Register function hooks */ 632 if (ng_ether_attach_p != NULL) { 633 error = EEXIST; 634 break; 635 } 636 ng_ether_attach_p = ng_ether_attach; 637 ng_ether_detach_p = ng_ether_detach; 638 ng_ether_output_p = ng_ether_output; 639 ng_ether_input_p = ng_ether_input; 640 ng_ether_input_orphan_p = ng_ether_input_orphan; 641 642 /* Create nodes for any already-existing Ethernet interfaces */ 643 IFNET_RLOCK(); 644 TAILQ_FOREACH(ifp, &ifnet, if_link) { 645 if (ifp->if_type == IFT_ETHER 646 || ifp->if_type == IFT_L2VLAN) 647 ng_ether_attach(ifp); 648 } 649 IFNET_RUNLOCK(); 650 break; 651 652 case MOD_UNLOAD: 653 654 /* 655 * Note that the base code won't try to unload us until 656 * all nodes have been removed, and that can't happen 657 * until all Ethernet interfaces are removed. In any 658 * case, we know there are no nodes left if the action 659 * is MOD_UNLOAD, so there's no need to detach any nodes. 660 */ 661 662 /* Unregister function hooks */ 663 ng_ether_attach_p = NULL; 664 ng_ether_detach_p = NULL; 665 ng_ether_output_p = NULL; 666 ng_ether_input_p = NULL; 667 ng_ether_input_orphan_p = NULL; 668 break; 669 670 default: 671 error = EOPNOTSUPP; 672 break; 673 } 674 splx(s); 675 return (error); 676 } 677 678