xref: /freebsd/sys/netgraph/ng_source.c (revision 6780ab54325a71e7e70112b11657973edde8655e)
1 /*
2  * ng_source.c
3  *
4  * Copyright 2002 Sandvine Inc.
5  * All rights reserved.
6  *
7  * Subject to the following obligations and disclaimer of warranty, use and
8  * redistribution of this software, in source or object code forms, with or
9  * without modifications are expressly permitted by Sandvine Inc.; provided,
10  * however, that:
11  * 1. Any and all reproductions of the source or object code must include the
12  *    copyright notice above and the following disclaimer of warranties; and
13  * 2. No rights are granted, in any manner or form, to use Sandvine Inc.
14  *    trademarks, including the mark "SANDVINE" on advertising, endorsements,
15  *    or otherwise except as such appears in the above copyright notice or in
16  *    the software.
17  *
18  * THIS SOFTWARE IS BEING PROVIDED BY SANDVINE "AS IS", AND TO THE MAXIMUM
19  * EXTENT PERMITTED BY LAW, SANDVINE MAKES NO REPRESENTATIONS OR WARRANTIES,
20  * EXPRESS OR IMPLIED, REGARDING THIS SOFTWARE, INCLUDING WITHOUT LIMITATION,
21  * ANY AND ALL IMPLIED WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR
22  * PURPOSE, OR NON-INFRINGEMENT.  SANDVINE DOES NOT WARRANT, GUARANTEE, OR
23  * MAKE ANY REPRESENTATIONS REGARDING THE USE OF, OR THE RESULTS OF THE
24  * USE OF THIS SOFTWARE IN TERMS OF ITS CORRECTNESS, ACCURACY, RELIABILITY
25  * OR OTHERWISE.  IN NO EVENT SHALL SANDVINE BE LIABLE FOR ANY DAMAGES
26  * RESULTING FROM OR ARISING OUT OF ANY USE OF THIS SOFTWARE, INCLUDING
27  * WITHOUT LIMITATION, ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY,
28  * PUNITIVE, OR CONSEQUENTIAL DAMAGES, PROCUREMENT OF SUBSTITUTE GOODS OR
29  * SERVICES, LOSS OF USE, DATA OR PROFITS, HOWEVER CAUSED AND UNDER ANY
30  * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
31  * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
32  * THIS SOFTWARE, EVEN IF SANDVINE IS ADVISED OF THE POSSIBILITY OF SUCH
33  * DAMAGE.
34  *
35  * Author: Dave Chapeskie <dchapeskie@sandvine.com>
36  *
37  * $FreeBSD$
38  */
39 
40 /*
41  * This node is used for high speed packet geneneration.  It queues
42  * all data recieved on it's 'input' hook and when told to start via
43  * a control message it sends the packets out it's 'output' hook.  In
44  * this way this node can be preloaded with a packet stream which is
45  * continuously sent.
46  *
47  * Currently it just copies the mbufs as required.  It could do various
48  * tricks to try and avoid this.  Probably the best performance would
49  * be achieved by modifying the appropriate drivers to be told to
50  * self-re-enqueue packets (e.g. the if_bge driver could reuse the same
51  * transmit descriptors) under control of this node; perhaps via some
52  * flag in the mbuf or some such.  The node would peak at an appropriate
53  * ifnet flag to see if such support is available for the connected
54  * interface.
55  */
56 
57 #include <sys/param.h>
58 #include <sys/systm.h>
59 #include <sys/errno.h>
60 #include <sys/kernel.h>
61 #include <sys/malloc.h>
62 #include <sys/mbuf.h>
63 #include <sys/socket.h>
64 #include <net/if.h>
65 #include <net/if_var.h>
66 #include <netgraph/ng_message.h>
67 #include <netgraph/netgraph.h>
68 #include <netgraph/ng_parse.h>
69 #include <netgraph/ng_ether.h>
70 #include <netgraph/ng_source.h>
71 
72 #define NG_SOURCE_INTR_TICKS		1
73 #define NG_SOURCE_DRIVER_IFQ_MAXLEN	(4*1024)
74 
75 
76 /* Per hook info */
77 struct source_hookinfo {
78 	hook_p				hook;
79 };
80 
81 /* Per node info */
82 struct privdata {
83 	node_p				node;
84 	struct source_hookinfo		input;
85 	struct source_hookinfo		output;
86 	struct ng_source_stats		stats;
87 	struct ifqueue			snd_queue;	/* packets to send */
88 	struct ifnet			*output_ifp;
89 	struct callout_handle		intr_ch;
90 	u_int64_t			packets;	/* packets to send */
91 	u_int32_t			queueOctets;
92 };
93 typedef struct privdata *sc_p;
94 
95 /* Node flags */
96 #define NG_SOURCE_ACTIVE	(NGF_TYPE1)
97 
98 /* XXX */
99 #if 1
100 #undef KASSERT
101 #define KASSERT(expr,msg) do {			\
102 		if (!(expr)) {			\
103 			printf msg ;		\
104 			panic("Assertion");	\
105 		}				\
106 	} while(0)
107 #endif
108 
109 /* Netgraph methods */
110 static ng_constructor_t	ng_source_constructor;
111 static ng_rcvmsg_t	ng_source_rcvmsg;
112 static ng_shutdown_t	ng_source_rmnode;
113 static ng_newhook_t	ng_source_newhook;
114 static ng_rcvdata_t	ng_source_rcvdata;
115 static ng_disconnect_t	ng_source_disconnect;
116 
117 /* Other functions */
118 static timeout_t	ng_source_intr;
119 static void		ng_source_request_output_ifp (sc_p);
120 static void		ng_source_clr_data (sc_p);
121 static void		ng_source_start (sc_p);
122 static void		ng_source_stop (sc_p);
123 static int		ng_source_send (sc_p, int, int *);
124 static int		ng_source_store_output_ifp(sc_p sc,
125 			    struct ng_mesg *msg);
126 
127 
128 /* Parse type for timeval */
129 static const struct ng_parse_struct_field ng_source_timeval_type_fields[] =
130 {
131 	{ "tv_sec",		&ng_parse_int32_type	},
132 	{ "tv_usec",		&ng_parse_int32_type	},
133 	{ NULL }
134 };
135 const struct ng_parse_type ng_source_timeval_type = {
136 	&ng_parse_struct_type,
137 	&ng_source_timeval_type_fields
138 };
139 
140 /* Parse type for struct ng_source_stats */
141 static const struct ng_parse_struct_field ng_source_stats_type_fields[]
142 	= NG_SOURCE_STATS_TYPE_INFO;
143 static const struct ng_parse_type ng_source_stats_type = {
144 	&ng_parse_struct_type,
145 	&ng_source_stats_type_fields
146 };
147 
148 /* List of commands and how to convert arguments to/from ASCII */
149 static const struct ng_cmdlist ng_source_cmds[] = {
150 	{
151 	  NGM_SOURCE_COOKIE,
152 	  NGM_SOURCE_GET_STATS,
153 	  "getstats",
154 	  NULL,
155 	  &ng_source_stats_type
156 	},
157 	{
158 	  NGM_SOURCE_COOKIE,
159 	  NGM_SOURCE_CLR_STATS,
160 	  "clrstats",
161 	  NULL,
162 	  NULL
163 	},
164 	{
165 	  NGM_SOURCE_COOKIE,
166 	  NGM_SOURCE_GETCLR_STATS,
167 	  "getclrstats",
168 	  NULL,
169 	  &ng_source_stats_type
170 	},
171 	{
172 	  NGM_SOURCE_COOKIE,
173 	  NGM_SOURCE_START,
174 	  "start",
175 	  &ng_parse_uint64_type,
176 	  NULL
177 	},
178 	{
179 	  NGM_SOURCE_COOKIE,
180 	  NGM_SOURCE_STOP,
181 	  "stop",
182 	  NULL,
183 	  NULL
184 	},
185 	{
186 	  NGM_SOURCE_COOKIE,
187 	  NGM_SOURCE_CLR_DATA,
188 	  "clrdata",
189 	  NULL,
190 	  NULL
191 	},
192 	{ 0 }
193 };
194 
195 /* Netgraph type descriptor */
196 static struct ng_type ng_source_typestruct = {
197 	NG_VERSION,
198 	NG_SOURCE_NODE_TYPE,
199 	NULL,					/* module event handler */
200 	ng_source_constructor,
201 	ng_source_rcvmsg,
202 	ng_source_rmnode,
203 	ng_source_newhook,
204 	NULL,					/* findhook */
205 	NULL,
206 	ng_source_rcvdata,			/* rcvdata */
207 	ng_source_disconnect,
208 	ng_source_cmds
209 };
210 NETGRAPH_INIT(source, &ng_source_typestruct);
211 
212 /*
213  * Node constructor
214  */
215 static int
216 ng_source_constructor(node_p node)
217 {
218 	sc_p sc;
219 
220 	MALLOC(sc, sc_p, sizeof(*sc), M_NETGRAPH, M_NOWAIT);
221 	if (sc == NULL)
222 		return (ENOMEM);
223 	bzero(sc, sizeof(*sc));
224 
225 	NG_NODE_SET_PRIVATE(node, sc);
226 	sc->node = node;
227 	sc->snd_queue.ifq_maxlen = 2048;	/* XXX not checked */
228 	callout_handle_init(&sc->intr_ch);   /* XXX fix.. will
229 						cause problems. */
230 	return (0);
231 }
232 
233 /*
234  * Add a hook
235  */
236 static int
237 ng_source_newhook(node_p node, hook_p hook, const char *name)
238 {
239 	sc_p sc;
240 
241 	sc = NG_NODE_PRIVATE(node);
242 	KASSERT(sc != NULL, ("%s: null node private", __FUNCTION__));
243 	if (strcmp(name, NG_SOURCE_HOOK_INPUT) == 0) {
244 		sc->input.hook = hook;
245 		NG_HOOK_SET_PRIVATE(hook, &sc->input);
246 	} else if (strcmp(name, NG_SOURCE_HOOK_OUTPUT) == 0) {
247 		sc->output.hook = hook;
248 		NG_HOOK_SET_PRIVATE(hook, &sc->output);
249 		sc->output_ifp = 0;
250 		bzero(&sc->stats, sizeof(sc->stats));
251 	} else
252 		return (EINVAL);
253 	return (0);
254 }
255 
256 /*
257  * Receive a control message
258  */
259 static int
260 ng_source_rcvmsg(node_p node, item_p item, hook_p lasthook)
261 {
262 	sc_p sc;
263 	struct ng_mesg *resp = NULL;
264 	int error = 0;
265 	struct ng_mesg *msg;
266 
267 	sc = NG_NODE_PRIVATE(node);
268 	NGI_GET_MSG(item, msg);
269 	KASSERT(sc != NULL, ("%s: null node private", __FUNCTION__));
270 	switch (msg->header.typecookie) {
271 	case NGM_SOURCE_COOKIE:
272 		if (msg->header.flags & NGF_RESP) {
273 			error = EINVAL;
274 			break;
275 		}
276 		switch (msg->header.cmd) {
277 		case NGM_SOURCE_GET_STATS:
278 		case NGM_SOURCE_CLR_STATS:
279 		case NGM_SOURCE_GETCLR_STATS:
280                     {
281 			struct ng_source_stats *stats;
282 
283                         if (msg->header.cmd != NGM_SOURCE_CLR_STATS) {
284                                 NG_MKRESPONSE(resp, msg,
285                                     sizeof(*stats), M_NOWAIT);
286 				if (resp == NULL) {
287 					error = ENOMEM;
288 					goto done;
289 				}
290 				sc->stats.queueOctets = sc->queueOctets;
291 				sc->stats.queueFrames = sc->snd_queue.ifq_len;
292 				if ((sc->node->nd_flags & NG_SOURCE_ACTIVE)
293 				    && !timevalisset(&sc->stats.endTime)) {
294 					getmicrotime(&sc->stats.elapsedTime);
295 					timevalsub(&sc->stats.elapsedTime,
296 					    &sc->stats.startTime);
297 				}
298 				stats = (struct ng_source_stats *)resp->data;
299 				bcopy(&sc->stats, stats, sizeof(* stats));
300                         }
301                         if (msg->header.cmd != NGM_SOURCE_GET_STATS)
302 				bzero(&sc->stats, sizeof(sc->stats));
303 		    }
304 		    break;
305 		case NGM_SOURCE_START:
306 		    {
307 			u_int64_t packets = *(u_int64_t *)msg->data;
308 			if (sc->output.hook == NULL) {
309 				printf("%s: start on node with no output hook\n"
310 				    , __FUNCTION__);
311 				error = EINVAL;
312 				break;
313 			}
314 			/* TODO validation of packets */
315 			sc->packets = packets;
316 			ng_source_start(sc);
317 		    }
318 		    break;
319 		case NGM_SOURCE_STOP:
320 			ng_source_stop(sc);
321 			break;
322 		case NGM_SOURCE_CLR_DATA:
323 			ng_source_clr_data(sc);
324 			break;
325 		default:
326 			error = EINVAL;
327 			break;
328 		}
329 		break;
330 	case NGM_ETHER_COOKIE:
331 		if (!(msg->header.flags & NGF_RESP)) {
332 			error = EINVAL;
333 			break;
334 		}
335 		switch (msg->header.cmd) {
336 		case NGM_ETHER_GET_IFINDEX:
337 			if (ng_source_store_output_ifp(sc, msg) == 0) {
338 				ng_source_set_autosrc(sc, 0);
339 				sc->node->nd_flags |= NG_SOURCE_ACTIVE;
340 				timevalclear(&sc->stats.elapsedTime);
341 				timevalclear(&sc->stats.endTime);
342 				getmicrotime(&sc->stats.startTime);
343 				sc->intr_ch = timeout(ng_source_intr, sc, 0);
344 			}
345 			break;
346 		default:
347 			error = EINVAL;
348 		}
349 		break;
350 	default:
351 		error = EINVAL;
352 		break;
353 	}
354 
355 done:
356 	/* Take care of synchronous response, if any */
357 	NG_RESPOND_MSG(error, node, item, resp);
358 	/* Free the message and return */
359 	NG_FREE_MSG(msg);
360 	return (error);
361 }
362 
363 /*
364  * Receive data on a hook
365  *
366  * If data comes in the input hook, enqueue it on the send queue.
367  * If data comes in the output hook, discard it.
368  */
369 static int
370 ng_source_rcvdata(hook_p hook, item_p item)
371 {
372 	sc_p sc;
373 	struct source_hookinfo *hinfo;
374 	int error = 0;
375 	struct mbuf *m;
376 
377 	sc = NG_NODE_PRIVATE(NG_HOOK_NODE(hook));
378 	NGI_GET_M(item, m);
379 	NG_FREE_ITEM(item);
380 	hinfo = NG_HOOK_PRIVATE(hook);
381 	KASSERT(sc != NULL, ("%s: null node private", __FUNCTION__));
382 	KASSERT(hinfo != NULL, ("%s: null hook info", __FUNCTION__));
383 
384 	/* Which hook? */
385 	if (hinfo == &sc->output) {
386 		/* discard */
387 		NG_FREE_M(m);
388 		return (error);
389 	}
390 	KASSERT(hinfo == &sc->input, ("%s: no hook!", __FUNCTION__));
391 
392 	if ((m->m_flags & M_PKTHDR) == 0) {
393 		printf("%s: mbuf without PKTHDR\n", __FUNCTION__);
394 		NG_FREE_M(m);
395 		return (EINVAL);
396 	}
397 
398 	/* enque packet */
399 	/* XXX should we check IF_QFULL() ? */
400 	IF_ENQUEUE(&sc->snd_queue, m);
401 	sc->queueOctets += m->m_pkthdr.len;
402 
403 	return (0);
404 }
405 
406 /*
407  * Shutdown processing
408  */
409 static int
410 ng_source_rmnode(node_p node)
411 {
412 	sc_p sc;
413 
414 	sc = NG_NODE_PRIVATE(node);
415 	KASSERT(sc != NULL, ("%s: null node private", __FUNCTION__));
416 	node->nd_flags |= NG_INVALID;
417 	ng_source_stop(sc);
418 	ng_source_clr_data(sc);
419 	NG_NODE_SET_PRIVATE(node, NULL);
420 	NG_NODE_UNREF(node);
421 	FREE(sc, M_NETGRAPH);
422 	return (0);
423 }
424 
425 /*
426  * Hook disconnection
427  */
428 static int
429 ng_source_disconnect(hook_p hook)
430 {
431 	struct source_hookinfo *hinfo;
432 	sc_p sc;
433 
434 	hinfo = NG_HOOK_PRIVATE(hook);
435 	sc = NG_NODE_PRIVATE(NG_HOOK_NODE(hook));
436 	KASSERT(sc != NULL, ("%s: null node private", __FUNCTION__));
437 	hinfo->hook = NULL;
438 	if (NG_NODE_NUMHOOKS(NG_HOOK_NODE(hook)) == 0 || hinfo == &sc->output)
439 		ng_rmnode_self(NG_HOOK_NODE(hook));
440 	return (0);
441 }
442 
443 /*
444  *
445  * Ask out neighbour on the output hook side to send us it's interface
446  * information.
447  */
448 static void
449 ng_source_request_output_ifp(sc_p sc)
450 {
451 	struct ng_mesg *msg;
452 	int error = 0;
453 
454 	sc->output_ifp = NULL;
455 
456 	/* Ask the attached node for the connected interface's index */
457 	NG_MKMESSAGE(msg, NGM_ETHER_COOKIE, NGM_ETHER_GET_IFINDEX, 0, M_NOWAIT);
458 	if (msg == NULL)
459 		return (ENOBUFS);
460 
461 	NG_SEND_MSG_HOOK(error, sc->node, msg, sc->output.hook, NULL);
462 	return (error);
463 }
464 
465 /*
466  * Set sc->output_ifp to point to the the struct ifnet of the interface
467  * reached via our output hook.
468  */
469 static int
470 ng_source_store_output_ifp(sc_p sc, struct ng_mesg *msg)
471 {
472 	struct ifnet *ifp;
473 	u_int32_t if_index;
474 	int s;
475 
476 	if (msg->header.arglen < sizeof(u_int32_t))
477 		return (EINVAL);
478 
479 	if_index = *(u_int32_t *)msg->data;
480 	/* Could use ifindex2ifnet[if_index] except that we have no
481 	 * way of verifying if_index is valid since if_indexlim is
482 	 * local to if_attach()
483 	 */
484 	IFNET_RLOCK();
485 	TAILQ_FOREACH(ifp, &ifnet, if_link) {
486 		if (ifp->if_index == if_index)
487 			break;
488 	}
489 	IFNET_RUNLOCK();
490 
491 	if (ifp == NULL) {
492 		printf("%s: can't find interface %d\n", __FUNCTION__, if_index);
493 		return (EINVAL);
494 	}
495 	sc->output_ifp = ifp;
496 
497 #if 1
498 	/* XXX mucking with a drivers ifqueue size is ugly but we need it
499 	 * to queue a lot of packets to get close to line rate on a gigabit
500 	 * interface with small packets.
501 	 * XXX we should restore the original value at stop or disconnect
502 	 */
503 	s = splimp();		/* XXX is this required? */
504 	if (ifp->if_snd.ifq_maxlen < NG_SOURCE_DRIVER_IFQ_MAXLEN)
505 	{
506 		printf("ng_source: changing ifq_maxlen from %d to %d\n",
507 		    ifp->if_snd.ifq_maxlen, NG_SOURCE_DRIVER_IFQ_MAXLEN);
508 		ifp->if_snd.ifq_maxlen = NG_SOURCE_DRIVER_IFQ_MAXLEN;
509 	}
510 	splx(s);
511 #endif
512 	return (0);
513 }
514 
515 /*
516  * Set the attached ethernet node's ethernet source address override flag.
517  */
518 static int
519 ng_source_set_autosrc(sc_p sc, u_int32_t flag)
520 {
521 	struct ng_mesg *msg;
522 	int error = 0;
523 
524 	NG_MKMESSAGE(msg, NGM_ETHER_COOKIE, NGM_ETHER_SET_AUTOSRC,
525 			sizeof (u_int32_t), M_NOWAIT);
526 	if (msg == NULL)
527 		return(ENOBUFS);
528 
529 	*(u_int32_t *)msg->data = flag;
530 	NG_SEND_MSG_HOOK(error, sc->node, msg, sc->output.hook, NULL);
531 	return (error);
532 }
533 
534 /*
535  * Clear out the data we've queued
536  */
537 static void
538 ng_source_clr_data (sc_p sc)
539 {
540 	struct mbuf *m;
541 
542 	for (;;) {
543 		IF_DEQUEUE(&sc->snd_queue, m);
544 		if (m == NULL)
545 			break;
546 		NG_FREE_M(m);
547 	}
548 	sc->queueOctets = 0;
549 }
550 
551 /*
552  * Start sending queued data out the output hook
553  */
554 static void
555 ng_source_start (sc_p sc)
556 {
557 	KASSERT(sc->output.hook != NULL,
558 			("%s: output hook unconnected", __FUNCTION__));
559 	if (((sc->node->nd_flags & NG_SOURCE_ACTIVE) == 0) &&
560 	    (sc->output_ifp == NULL))
561 		ng_source_request_output_ifp(sc);
562 }
563 
564 /*
565  * Stop sending queued data out the output hook
566  */
567 static void
568 ng_source_stop (sc_p sc)
569 {
570 	if (sc->node->nd_flags & NG_SOURCE_ACTIVE) {
571 		untimeout(ng_source_intr, sc, sc->intr_ch);
572 		sc->node->nd_flags &= ~NG_SOURCE_ACTIVE;
573 		getmicrotime(&sc->stats.endTime);
574 		sc->stats.elapsedTime = sc->stats.endTime;
575 		timevalsub(&sc->stats.elapsedTime, &sc->stats.startTime);
576 		/* XXX should set this to the initial value instead */
577 		ng_source_set_autosrc(sc, 1);
578 	}
579 }
580 
581 /*
582  * While active called every NG_SOURCE_INTR_TICKS ticks.
583  * Sends as many packets as the interface connected to our
584  * output hook is able to enqueue.
585  */
586 static void
587 ng_source_intr (void *arg)
588 {
589 	sc_p sc = (sc_p) arg;
590 	struct ifqueue *ifq;
591 	int packets;
592 
593 	KASSERT(sc != NULL, ("%s: null node private", __FUNCTION__));
594 
595 	callout_handle_init(&sc->intr_ch);
596 	if (sc->packets == 0 || sc->output.hook == NULL
597 	    || (sc->node->nd_flags & NG_SOURCE_ACTIVE) == 0) {
598 		ng_source_stop(sc);
599 		return;
600 	}
601 
602 	ifq = &sc->output_ifp->if_snd;
603 	packets = ifq->ifq_maxlen - ifq->ifq_len;
604 	ng_source_send(sc, packets, NULL);
605 	if (sc->packets == 0) {
606 		int s = splnet();
607 		ng_source_stop(sc);
608 		splx(s);
609 	} else
610 		sc->intr_ch = timeout(ng_source_intr, sc, NG_SOURCE_INTR_TICKS);
611 }
612 
613 /*
614  * Send packets out our output hook
615  */
616 static int
617 ng_source_send (sc_p sc, int tosend, int *sent_p)
618 {
619 	struct ifqueue tmp_queue;
620 	struct mbuf *m, *m2;
621 	int sent = 0;
622 	int error = 0;
623 	int s, s2;
624 
625 	KASSERT(sc != NULL, ("%s: null node private", __FUNCTION__));
626 	KASSERT(tosend >= 0, ("%s: negative tosend param", __FUNCTION__));
627 	KASSERT(sc->node->nd_flags & NG_SOURCE_ACTIVE,
628 			("%s: inactive node", __FUNCTION__));
629 
630 	if ((u_int64_t)tosend > sc->packets)
631 		tosend = sc->packets;
632 
633 	/* Copy the required number of packets to a temporary queue */
634 	bzero (&tmp_queue, sizeof (tmp_queue));
635 	for (sent = 0; error == 0 && sent < tosend; ++sent) {
636 		s = splnet();
637 		IF_DEQUEUE(&sc->snd_queue, m);
638 		splx(s);
639 		if (m == NULL)
640 			break;
641 
642 		/* duplicate the packet */
643 		m2 = m_copypacket(m, M_NOWAIT);
644 		if (m2 == NULL) {
645 			s = splnet();
646 			IF_PREPEND(&sc->snd_queue, m);
647 			splx(s);
648 			error = ENOBUFS;
649 			break;
650 		}
651 
652 		/* re-enqueue the original packet for us */
653 		s = splnet();
654 		IF_ENQUEUE(&sc->snd_queue, m);
655 		splx(s);
656 
657 		/* queue the copy for sending at smplimp */
658 		IF_ENQUEUE(&tmp_queue, m2);
659 	}
660 
661 	sent = 0;
662 	s = splimp();
663 	for (;;) {
664 		IF_DEQUEUE(&tmp_queue, m2);
665 		if (m2 == NULL)
666 			break;
667 		if (error == 0) {
668 			++sent;
669 			sc->stats.outFrames++;
670 			sc->stats.outOctets += m2->m_pkthdr.len;
671 			s2 = splnet();
672 			NG_SEND_DATA_ONLY(error, sc->output.hook, m2);
673 			splx(s2);
674 		} else {
675 			NG_FREE_M(m2);
676 		}
677 	}
678 	splx(s);
679 
680 	sc->packets -= sent;
681 	if (sent_p != NULL)
682 		*sent_p = sent;
683 	return (error);
684 }
685