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