Deleted Added
sdiff udiff text old ( 63543 ) new ( 64358 )
full compact
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: head/sys/netgraph/ng_ether.c 64358 2000-08-07 18:52:26Z archie $
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 IFP2AC(IFP) ((struct arpcom *)IFP)
68#define IFP2NG(ifp) ((struct ng_node *)((struct arpcom *)(ifp))->ac_netgraph)
69
70/* Per-node private data */
71struct private {
72 struct ifnet *ifp; /* associated interface */
73 hook_p upper; /* upper hook connection */
74 hook_p lower; /* lower OR orphan hook connection */
75 u_char lowerOrphan; /* whether lower is lower or orphan */
76 u_char autoSrcAddr; /* always overwrite source address */
77 u_char promisc; /* promiscuous mode enabled */
78};
79typedef struct private *priv_p;
80
81/* Functional hooks called from if_ethersubr.c */
82static void ng_ether_input(struct ifnet *ifp,
83 struct mbuf **mp, struct ether_header *eh);
84static void ng_ether_input_orphan(struct ifnet *ifp,
85 struct mbuf *m, struct ether_header *eh);
86static int ng_ether_output(struct ifnet *ifp, struct mbuf **mp);
87static void ng_ether_attach(struct ifnet *ifp);
88static void ng_ether_detach(struct ifnet *ifp);
89
90/* Other functions */
91static void ng_ether_input2(node_p node,
92 struct mbuf **mp, struct ether_header *eh);
93static int ng_ether_glueback_header(struct mbuf **mp,
94 struct ether_header *eh);
95static int ng_ether_rcv_lower(node_p node, struct mbuf *m, meta_p meta);
96static int ng_ether_rcv_upper(node_p node, struct mbuf *m, meta_p meta);
97
98/* Netgraph node methods */
99static ng_constructor_t ng_ether_constructor;
100static ng_rcvmsg_t ng_ether_rcvmsg;
101static ng_shutdown_t ng_ether_rmnode;
102static ng_newhook_t ng_ether_newhook;
103static ng_rcvdata_t ng_ether_rcvdata;
104static ng_disconnect_t ng_ether_disconnect;
105static int ng_ether_mod_event(module_t mod, int event, void *data);
106
107/* Parse type for an Ethernet address. Slightly better than an array of
108 six int8's would be the more common colon-separated hex byte format. */
109static const struct ng_parse_fixedarray_info ng_ether_enaddr_type_info = {
110 &ng_parse_int8_type,
111 ETHER_ADDR_LEN
112};
113static const struct ng_parse_type ng_ether_enaddr_type = {
114 &ng_parse_fixedarray_type,
115 &ng_ether_enaddr_type_info
116};
117
118/* List of commands and how to convert arguments to/from ASCII */
119static const struct ng_cmdlist ng_ether_cmdlist[] = {
120 {
121 NGM_ETHER_COOKIE,
122 NGM_ETHER_GET_IFNAME,
123 "getifname",
124 NULL,
125 &ng_parse_string_type
126 },
127 {
128 NGM_ETHER_COOKIE,
129 NGM_ETHER_GET_IFINDEX,
130 "getifindex",
131 NULL,
132 &ng_parse_int32_type
133 },
134 {
135 NGM_ETHER_COOKIE,
136 NGM_ETHER_GET_ENADDR,
137 "getenaddr",
138 NULL,
139 &ng_ether_enaddr_type
140 },
141 {
142 NGM_ETHER_COOKIE,
143 NGM_ETHER_SET_PROMISC,
144 "setpromisc",
145 &ng_parse_int32_type,
146 NULL
147 },
148 {
149 NGM_ETHER_COOKIE,
150 NGM_ETHER_SET_AUTOSRC,
151 "setautosrc",
152 &ng_parse_int32_type,
153 NULL
154 },
155 { 0 }
156};
157
158static struct ng_type ng_ether_typestruct = {
159 NG_VERSION,
160 NG_ETHER_NODE_TYPE,
161 ng_ether_mod_event,
162 ng_ether_constructor,
163 ng_ether_rcvmsg,
164 ng_ether_rmnode,
165 ng_ether_newhook,
166 NULL,
167 NULL,
168 ng_ether_rcvdata,
169 ng_ether_rcvdata,
170 ng_ether_disconnect,
171 ng_ether_cmdlist,
172};
173NETGRAPH_INIT(ether, &ng_ether_typestruct);
174
175/******************************************************************
176 ETHERNET FUNCTION HOOKS
177******************************************************************/
178
179/*
180 * Handle a packet that has come in on an interface. We get to
181 * look at it here before any upper layer protocols do.
182 *
183 * NOTE: this function will get called at splimp()
184 */
185static void
186ng_ether_input(struct ifnet *ifp,
187 struct mbuf **mp, struct ether_header *eh)
188{
189 const node_p node = IFP2NG(ifp);
190 const priv_p priv = node->private;
191
192 /* If "lower" hook not connected, let packet continue */
193 if (priv->lower == NULL || priv->lowerOrphan)
194 return;
195 ng_ether_input2(node, mp, eh);
196}
197
198/*
199 * Handle a packet that has come in on an interface, and which
200 * does not match any of our known protocols (an ``orphan'').
201 *
202 * NOTE: this function will get called at splimp()
203 */
204static void
205ng_ether_input_orphan(struct ifnet *ifp,
206 struct mbuf *m, struct ether_header *eh)
207{
208 const node_p node = IFP2NG(ifp);
209 const priv_p priv = node->private;
210
211 /* If "orphan" hook not connected, let packet continue */
212 if (priv->lower == NULL || !priv->lowerOrphan) {
213 m_freem(m);
214 return;
215 }
216 ng_ether_input2(node, &m, eh);
217 if (m != NULL)
218 m_freem(m);
219}
220
221/*
222 * Handle a packet that has come in on an interface.
223 * The Ethernet header has already been detached from the mbuf,
224 * so we have to put it back.
225 *
226 * NOTE: this function will get called at splimp()
227 */
228static void
229ng_ether_input2(node_p node, struct mbuf **mp, struct ether_header *eh)
230{
231 const priv_p priv = node->private;
232 meta_p meta = NULL;
233 int error;
234
235 /* Glue Ethernet header back on */
236 if ((error = ng_ether_glueback_header(mp, eh)) != 0)
237 return;
238
239 /* Send out lower/orphan hook */
240 (void)ng_queue_data(priv->lower, *mp, meta);
241 *mp = NULL;
242}
243
244/*
245 * Handle a packet that is going out on an interface.
246 * The Ethernet header is already attached to the mbuf.
247 */
248static int
249ng_ether_output(struct ifnet *ifp, struct mbuf **mp)
250{
251 const node_p node = IFP2NG(ifp);
252 const priv_p priv = node->private;
253 meta_p meta = NULL;
254 int error = 0;
255
256 /* If "upper" hook not connected, let packet continue */
257 if (priv->upper == NULL)
258 return (0);
259
260 /* Send it out "upper" hook */
261 NG_SEND_DATA_RET(error, priv->upper, *mp, meta);
262
263 /* If we got a reflected packet back, handle it */
264 if (error == 0 && *mp != NULL) {
265 error = ng_ether_rcv_upper(node, *mp, meta);
266 *mp = NULL;
267 }
268 return (error);
269}
270
271/*
272 * A new Ethernet interface has been attached.
273 * Create a new node for it, etc.
274 */
275static void
276ng_ether_attach(struct ifnet *ifp)
277{
278 char name[IFNAMSIZ + 1];
279 priv_p priv;
280 node_p node;
281
282 /* Create node */
283 KASSERT(!IFP2NG(ifp), ("%s: node already exists?", __FUNCTION__));
284 snprintf(name, sizeof(name), "%s%d", ifp->if_name, ifp->if_unit);
285 if (ng_make_node_common(&ng_ether_typestruct, &node) != 0) {
286 log(LOG_ERR, "%s: can't %s for %s\n",
287 __FUNCTION__, "create node", name);
288 return;
289 }
290
291 /* Allocate private data */
292 MALLOC(priv, priv_p, sizeof(*priv), M_NETGRAPH, M_NOWAIT);
293 if (priv == NULL) {
294 log(LOG_ERR, "%s: can't %s for %s\n",
295 __FUNCTION__, "allocate memory", name);
296 ng_unref(node);
297 return;
298 }
299 bzero(priv, sizeof(*priv));
300 node->private = priv;
301 priv->ifp = ifp;
302 IFP2NG(ifp) = node;
303 priv->autoSrcAddr = 1;
304
305 /* Try to give the node the same name as the interface */
306 if (ng_name_node(node, name) != 0) {
307 log(LOG_WARNING, "%s: can't name node %s\n",
308 __FUNCTION__, name);
309 }
310}
311
312/*
313 * An Ethernet interface is being detached.
314 * Destroy its node.
315 */
316static void
317ng_ether_detach(struct ifnet *ifp)
318{
319 const node_p node = IFP2NG(ifp);
320 priv_p priv;
321
322 if (node == NULL) /* no node (why not?), ignore */
323 return;
324 ng_rmnode(node); /* break all links to other nodes */
325 node->flags |= NG_INVALID;
326 ng_unname(node); /* free name (and its reference) */
327 IFP2NG(ifp) = NULL; /* detach node from interface */
328 priv = node->private; /* free node private info */
329 bzero(priv, sizeof(*priv));
330 FREE(priv, M_NETGRAPH);
331 node->private = NULL;
332 ng_unref(node); /* free node itself */
333}
334
335/*
336 * Optimization for gluing the Ethernet header back onto
337 * the front of an incoming packet.
338 */
339static int
340ng_ether_glueback_header(struct mbuf **mp, struct ether_header *eh)
341{
342 struct mbuf *m = *mp;
343 uintfptr_t room;
344 int error = 0;
345
346 /*
347 * Possibly the header is already on the front.
348 * If this is the case so just move the markers back
349 * to re-include it. We lucked out.
350 * This allows us to avoid a yucky m_pullup
351 * in later nodes if it works.
352 */
353 if (eh == mtod(m, struct ether_header *) - 1) {
354 m->m_len += sizeof(*eh);
355 m->m_data -= sizeof(*eh);
356 m->m_pkthdr.len += sizeof(*eh);
357 goto done;
358 }
359
360 /*
361 * Alternatively there may be room even though
362 * it is stored somewhere else. If so, copy it in.
363 * This only safe because we KNOW that this packet has
364 * just been generated by an ethernet card, so there are
365 * no aliases to the buffer (not so for outgoing packets).
366 * Nearly all ethernet cards will end up producing mbufs
367 * that fall into these cases. So we are not optimizing
368 * contorted cases.
369 */
370 if ((m->m_flags & M_EXT) != 0) {
371 room = mtod(m, caddr_t) - m->m_ext.ext_buf;
372 if (room > m->m_ext.ext_size) /* garbage, fail immediately */
373 room = 0;
374 } else
375 room = mtod(m, caddr_t) - m->m_pktdat;
376
377 /*
378 * If we have room, just copy it and adjust
379 */
380 if (room >= sizeof(*eh)) {
381 m->m_len += sizeof(*eh);
382 m->m_data -= sizeof(*eh);
383 m->m_pkthdr.len += sizeof(*eh);
384 goto copy;
385 }
386
387 /*
388 * Doing anything more is likely to get more
389 * expensive than it's worth..
390 * it's probable that everything else is in one
391 * big lump. The next node will do an m_pullup()
392 * for exactly the amount of data it needs and
393 * hopefully everything after that will not
394 * need one. So let's just use M_PREPEND.
395 */
396 M_PREPEND(m, sizeof (*eh), M_DONTWAIT);
397 if (m == NULL) {
398 error = ENOBUFS;
399 goto done;
400 }
401
402copy:
403 /* Copy header and return (possibly new) mbuf */
404 bcopy((caddr_t)eh, mtod(m, struct ether_header *), sizeof(*eh));
405done:
406 *mp = m;
407 return error;
408}
409
410/******************************************************************
411 NETGRAPH NODE METHODS
412******************************************************************/
413
414/*
415 * It is not possible or allowable to create a node of this type.
416 * Nodes get created when the interface is attached (or, when
417 * this node type's KLD is loaded).
418 */
419static int
420ng_ether_constructor(node_p *nodep)
421{
422 return (EINVAL);
423}
424
425/*
426 * Check for attaching a new hook.
427 */
428static int
429ng_ether_newhook(node_p node, hook_p hook, const char *name)
430{
431 const priv_p priv = node->private;
432 u_char orphan = priv->lowerOrphan;
433 hook_p *hookptr;
434
435 /* Divert hook is an alias for lower */
436 if (strcmp(name, NG_ETHER_HOOK_DIVERT) == 0)
437 name = NG_ETHER_HOOK_LOWER;
438
439 /* Which hook? */
440 if (strcmp(name, NG_ETHER_HOOK_UPPER) == 0)
441 hookptr = &priv->upper;
442 else if (strcmp(name, NG_ETHER_HOOK_LOWER) == 0) {
443 hookptr = &priv->lower;
444 orphan = 0;
445 } else if (strcmp(name, NG_ETHER_HOOK_ORPHAN) == 0) {
446 hookptr = &priv->lower;
447 orphan = 1;
448 } else
449 return (EINVAL);
450
451 /* Check if already connected (shouldn't be, but doesn't hurt) */
452 if (*hookptr != NULL)
453 return (EISCONN);
454
455 /* OK */
456 *hookptr = hook;
457 priv->lowerOrphan = orphan;
458 return (0);
459}
460
461/*
462 * Receive an incoming control message.
463 */
464static int
465ng_ether_rcvmsg(node_p node, struct ng_mesg *msg, const char *retaddr,
466 struct ng_mesg **rptr, hook_p lasthook)
467{
468 const priv_p priv = node->private;
469 struct ng_mesg *resp = NULL;
470 int error = 0;
471
472 switch (msg->header.typecookie) {
473 case NGM_ETHER_COOKIE:
474 switch (msg->header.cmd) {
475 case NGM_ETHER_GET_IFNAME:
476 NG_MKRESPONSE(resp, msg, IFNAMSIZ + 1, M_NOWAIT);
477 if (resp == NULL) {
478 error = ENOMEM;
479 break;
480 }
481 snprintf(resp->data, IFNAMSIZ + 1,
482 "%s%d", priv->ifp->if_name, priv->ifp->if_unit);
483 break;
484 case NGM_ETHER_GET_IFINDEX:
485 NG_MKRESPONSE(resp, msg, sizeof(u_int32_t), M_NOWAIT);
486 if (resp == NULL) {
487 error = ENOMEM;
488 break;
489 }
490 *((u_int32_t *)resp->data) = priv->ifp->if_index;
491 break;
492 case NGM_ETHER_GET_ENADDR:
493 NG_MKRESPONSE(resp, msg, ETHER_ADDR_LEN, M_NOWAIT);
494 if (resp == NULL) {
495 error = ENOMEM;
496 break;
497 }
498 bcopy((IFP2AC(priv->ifp))->ac_enaddr,
499 resp->data, ETHER_ADDR_LEN);
500 break;
501 case NGM_ETHER_SET_PROMISC:
502 {
503 u_char want;
504
505 if (msg->header.arglen != sizeof(u_int32_t)) {
506 error = EINVAL;
507 break;
508 }
509 want = !!*((u_int32_t *)msg->data);
510 if (want ^ priv->promisc) {
511 if ((error = ifpromisc(priv->ifp, want)) != 0)
512 break;
513 priv->promisc = want;
514 }
515 break;
516 }
517 case NGM_ETHER_SET_AUTOSRC:
518 if (msg->header.arglen != sizeof(u_int32_t)) {
519 error = EINVAL;
520 break;
521 }
522 priv->autoSrcAddr = !!*((u_int32_t *)msg->data);
523 break;
524 default:
525 error = EINVAL;
526 break;
527 }
528 break;
529 default:
530 error = EINVAL;
531 break;
532 }
533 if (rptr)
534 *rptr = resp;
535 else if (resp != NULL)
536 FREE(resp, M_NETGRAPH);
537 FREE(msg, M_NETGRAPH);
538 return (error);
539}
540
541/*
542 * Receive data on a hook.
543 */
544static int
545ng_ether_rcvdata(hook_p hook, struct mbuf *m, meta_p meta,
546 struct mbuf **ret_m, meta_p *ret_meta)
547{
548 const node_p node = hook->node;
549 const priv_p priv = node->private;
550
551 if (hook == priv->lower)
552 return ng_ether_rcv_lower(node, m, meta);
553 if (hook == priv->upper)
554 return ng_ether_rcv_upper(node, m, meta);
555 panic("%s: weird hook", __FUNCTION__);
556}
557
558/*
559 * Handle an mbuf received on the "lower" hook.
560 */
561static int
562ng_ether_rcv_lower(node_p node, struct mbuf *m, meta_p meta)
563{
564 const priv_p priv = node->private;
565
566 /* Make sure header is fully pulled up */
567 if (m->m_pkthdr.len < sizeof(struct ether_header)) {
568 NG_FREE_DATA(m, meta);
569 return (EINVAL);
570 }
571 if (m->m_len < sizeof(struct ether_header)
572 && (m = m_pullup(m, sizeof(struct ether_header))) == NULL) {
573 NG_FREE_META(meta);
574 return (ENOBUFS);
575 }
576
577 /* Drop in the MAC address if desired */
578 if (priv->autoSrcAddr) {
579 bcopy((IFP2AC(priv->ifp))->ac_enaddr,
580 mtod(m, struct ether_header *)->ether_shost,
581 ETHER_ADDR_LEN);
582 }
583
584 /* Send it on its way */
585 NG_FREE_META(meta);
586 return ether_output_frame(priv->ifp, m);
587}
588
589/*
590 * Handle an mbuf received on the "upper" hook.
591 */
592static int
593ng_ether_rcv_upper(node_p node, struct mbuf *m, meta_p meta)
594{
595 const priv_p priv = node->private;
596 struct ether_header *eh;
597
598 /* Check length and pull off header */
599 if (m->m_pkthdr.len < sizeof(*eh)) {
600 NG_FREE_DATA(m, meta);
601 return (EINVAL);
602 }
603 if (m->m_len < sizeof(*eh) && (m = m_pullup(m, sizeof(*eh))) == NULL) {
604 NG_FREE_META(meta);
605 return (ENOBUFS);
606 }
607 eh = mtod(m, struct ether_header *);
608 m->m_data += sizeof(*eh);
609 m->m_len -= sizeof(*eh);
610 m->m_pkthdr.len -= sizeof(*eh);
611
612 /* Route packet back in */
613 NG_FREE_META(meta);
614 ether_demux(priv->ifp, eh, m);
615 return (0);
616}
617
618/*
619 * Shutdown node. This resets the node but does not remove it.
620 */
621static int
622ng_ether_rmnode(node_p node)
623{
624 const priv_p priv = node->private;
625
626 ng_cutlinks(node);
627 node->flags &= ~NG_INVALID; /* bounce back to life */
628 if (priv->promisc) { /* disable promiscuous mode */
629 (void)ifpromisc(priv->ifp, 0);
630 priv->promisc = 0;
631 }
632 priv->autoSrcAddr = 1; /* reset auto-src-addr flag */
633 return (0);
634}
635
636/*
637 * Hook disconnection.
638 */
639static int
640ng_ether_disconnect(hook_p hook)
641{
642 const priv_p priv = hook->node->private;
643
644 if (hook == priv->upper)
645 priv->upper = NULL;
646 else if (hook == priv->lower) {
647 priv->lower = NULL;
648 priv->lowerOrphan = 0;
649 } else
650 panic("%s: weird hook", __FUNCTION__);
651 return (0);
652}
653
654/******************************************************************
655 INITIALIZATION
656******************************************************************/
657
658/*
659 * Handle loading and unloading for this node type.
660 */
661static int
662ng_ether_mod_event(module_t mod, int event, void *data)
663{
664 struct ifnet *ifp;
665 int error = 0;
666 int s;
667
668 s = splnet();
669 switch (event) {
670 case MOD_LOAD:
671
672 /* Register function hooks */
673 if (ng_ether_attach_p != NULL) {
674 error = EEXIST;
675 break;
676 }
677 ng_ether_attach_p = ng_ether_attach;
678 ng_ether_detach_p = ng_ether_detach;
679 ng_ether_output_p = ng_ether_output;
680 ng_ether_input_p = ng_ether_input;
681 ng_ether_input_orphan_p = ng_ether_input_orphan;
682
683 /* Create nodes for any already-existing Ethernet interfaces */
684 TAILQ_FOREACH(ifp, &ifnet, if_link) {
685 if (ifp->if_type == IFT_ETHER)
686 ng_ether_attach(ifp);
687 }
688 break;
689
690 case MOD_UNLOAD:
691
692 /*
693 * Note that the base code won't try to unload us until
694 * all nodes have been removed, and that can't happen
695 * until all Ethernet interfaces are removed. In any
696 * case, we know there are no nodes left if the action
697 * is MOD_UNLOAD, so there's no need to detach any nodes.
698 */
699
700 /* Unregister function hooks */
701 ng_ether_attach_p = NULL;
702 ng_ether_detach_p = NULL;
703 ng_ether_output_p = NULL;
704 ng_ether_input_p = NULL;
705 ng_ether_input_orphan_p = NULL;
706 break;
707
708 default:
709 error = EOPNOTSUPP;
710 break;
711 }
712 splx(s);
713 return (error);
714}
715