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: releng/5.3/sys/netgraph/ng_source.c 136588 2004-10-16 08:43:07Z cvs2svn $");
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 .version = NG_ABI_VERSION,
194 .name = NG_SOURCE_NODE_TYPE,
195 .constructor = ng_source_constructor,
196 .rcvmsg = ng_source_rcvmsg,
197 .shutdown = ng_source_rmnode,
198 .newhook = ng_source_newhook,
199 .rcvdata = ng_source_rcvdata,
200 .disconnect = ng_source_disconnect,
201 .cmdlist = ng_source_cmds,
202 };
203 NETGRAPH_INIT(source, &ng_source_typestruct);
204
205 static int ng_source_set_autosrc(sc_p, u_int32_t);
206
207 /*
208 * Node constructor
209 */
210 static int
211 ng_source_constructor(node_p node)
212 {
213 sc_p sc;
214
215 sc = malloc(sizeof(*sc), M_NETGRAPH, M_NOWAIT | M_ZERO);
216 if (sc == NULL)
217 return (ENOMEM);
218
219 NG_NODE_SET_PRIVATE(node, sc);
220 sc->node = node;
221 sc->snd_queue.ifq_maxlen = 2048; /* XXX not checked */
222 callout_handle_init(&sc->intr_ch); /* XXX fix.. will
223 cause problems. */
224 return (0);
225 }
226
227 /*
228 * Add a hook
229 */
230 static int
231 ng_source_newhook(node_p node, hook_p hook, const char *name)
232 {
233 sc_p sc;
234
235 sc = NG_NODE_PRIVATE(node);
236 KASSERT(sc != NULL, ("%s: null node private", __func__));
237 if (strcmp(name, NG_SOURCE_HOOK_INPUT) == 0) {
238 sc->input.hook = hook;
239 NG_HOOK_SET_PRIVATE(hook, &sc->input);
240 } else if (strcmp(name, NG_SOURCE_HOOK_OUTPUT) == 0) {
241 sc->output.hook = hook;
242 NG_HOOK_SET_PRIVATE(hook, &sc->output);
243 sc->output_ifp = 0;
244 bzero(&sc->stats, sizeof(sc->stats));
245 } else
246 return (EINVAL);
247 return (0);
248 }
249
250 /*
251 * Receive a control message
252 */
253 static int
254 ng_source_rcvmsg(node_p node, item_p item, hook_p lasthook)
255 {
256 sc_p sc;
257 struct ng_mesg *resp = NULL;
258 int error = 0;
259 struct ng_mesg *msg;
260
261 sc = NG_NODE_PRIVATE(node);
262 NGI_GET_MSG(item, msg);
263 KASSERT(sc != NULL, ("%s: null node private", __func__));
264 switch (msg->header.typecookie) {
265 case NGM_SOURCE_COOKIE:
266 if (msg->header.flags & NGF_RESP) {
267 error = EINVAL;
268 break;
269 }
270 switch (msg->header.cmd) {
271 case NGM_SOURCE_GET_STATS:
272 case NGM_SOURCE_CLR_STATS:
273 case NGM_SOURCE_GETCLR_STATS:
274 {
275 struct ng_source_stats *stats;
276
277 if (msg->header.cmd != NGM_SOURCE_CLR_STATS) {
278 NG_MKRESPONSE(resp, msg,
279 sizeof(*stats), M_NOWAIT);
280 if (resp == NULL) {
281 error = ENOMEM;
282 goto done;
283 }
284 sc->stats.queueOctets = sc->queueOctets;
285 sc->stats.queueFrames = sc->snd_queue.ifq_len;
286 if ((sc->node->nd_flags & NG_SOURCE_ACTIVE)
287 && !timevalisset(&sc->stats.endTime)) {
288 getmicrotime(&sc->stats.elapsedTime);
289 timevalsub(&sc->stats.elapsedTime,
290 &sc->stats.startTime);
291 }
292 stats = (struct ng_source_stats *)resp->data;
293 bcopy(&sc->stats, stats, sizeof(* stats));
294 }
295 if (msg->header.cmd != NGM_SOURCE_GET_STATS)
296 bzero(&sc->stats, sizeof(sc->stats));
297 }
298 break;
299 case NGM_SOURCE_START:
300 {
301 u_int64_t packets = *(u_int64_t *)msg->data;
302 if (sc->output.hook == NULL) {
303 printf("%s: start on node with no output hook\n"
304 , __func__);
305 error = EINVAL;
306 break;
307 }
308 /* TODO validation of packets */
309 sc->packets = packets;
310 ng_source_start(sc);
311 }
312 break;
313 case NGM_SOURCE_START_NOW:
314 {
315 u_int64_t packets = *(u_int64_t *)msg->data;
316 if (sc->output.hook == NULL) {
317 printf("%s: start on node with no output hook\n"
318 , __func__);
319 error = EINVAL;
320 break;
321 }
322 if (sc->node->nd_flags & NG_SOURCE_ACTIVE) {
323 error = EBUSY;
324 break;
325 }
326 /* TODO validation of packets */
327 sc->packets = packets;
328 sc->output_ifp = NULL;
329
330 sc->node->nd_flags |= NG_SOURCE_ACTIVE;
331 timevalclear(&sc->stats.elapsedTime);
332 timevalclear(&sc->stats.endTime);
333 getmicrotime(&sc->stats.startTime);
334 sc->intr_ch = ng_timeout(node, NULL, 0,
335 ng_source_intr, sc, 0);
336 }
337 break;
338 case NGM_SOURCE_STOP:
339 ng_source_stop(sc);
340 break;
341 case NGM_SOURCE_CLR_DATA:
342 ng_source_clr_data(sc);
343 break;
344 default:
345 error = EINVAL;
346 break;
347 }
348 break;
349 case NGM_ETHER_COOKIE:
350 if (!(msg->header.flags & NGF_RESP)) {
351 error = EINVAL;
352 break;
353 }
354 switch (msg->header.cmd) {
355 case NGM_ETHER_GET_IFINDEX:
356 if (ng_source_store_output_ifp(sc, msg) == 0) {
357 ng_source_set_autosrc(sc, 0);
358 sc->node->nd_flags |= NG_SOURCE_ACTIVE;
359 timevalclear(&sc->stats.elapsedTime);
360 timevalclear(&sc->stats.endTime);
361 getmicrotime(&sc->stats.startTime);
362 sc->intr_ch = ng_timeout(node, NULL, 0,
363 ng_source_intr, sc, 0);
364 }
365 break;
366 default:
367 error = EINVAL;
368 }
369 break;
370 default:
371 error = EINVAL;
372 break;
373 }
374
375 done:
376 /* Take care of synchronous response, if any */
377 NG_RESPOND_MSG(error, node, item, resp);
378 /* Free the message and return */
379 NG_FREE_MSG(msg);
380 return (error);
381 }
382
383 /*
384 * Receive data on a hook
385 *
386 * If data comes in the input hook, enqueue it on the send queue.
387 * If data comes in the output hook, discard it.
388 */
389 static int
390 ng_source_rcvdata(hook_p hook, item_p item)
391 {
392 sc_p sc;
393 struct source_hookinfo *hinfo;
394 int error = 0;
395 struct mbuf *m;
396
397 sc = NG_NODE_PRIVATE(NG_HOOK_NODE(hook));
398 NGI_GET_M(item, m);
399 NG_FREE_ITEM(item);
400 hinfo = NG_HOOK_PRIVATE(hook);
401 KASSERT(sc != NULL, ("%s: null node private", __func__));
402 KASSERT(hinfo != NULL, ("%s: null hook info", __func__));
403
404 /* Which hook? */
405 if (hinfo == &sc->output) {
406 /* discard */
407 NG_FREE_M(m);
408 return (error);
409 }
410 KASSERT(hinfo == &sc->input, ("%s: no hook!", __func__));
411
412 if ((m->m_flags & M_PKTHDR) == 0) {
413 printf("%s: mbuf without PKTHDR\n", __func__);
414 NG_FREE_M(m);
415 return (EINVAL);
416 }
417
418 /* enque packet */
419 /* XXX should we check IF_QFULL() ? */
420 _IF_ENQUEUE(&sc->snd_queue, m);
421 sc->queueOctets += m->m_pkthdr.len;
422
423 return (0);
424 }
425
426 /*
427 * Shutdown processing
428 */
429 static int
430 ng_source_rmnode(node_p node)
431 {
432 sc_p sc;
433
434 sc = NG_NODE_PRIVATE(node);
435 KASSERT(sc != NULL, ("%s: null node private", __func__));
436 ng_source_stop(sc);
437 ng_source_clr_data(sc);
438 NG_NODE_SET_PRIVATE(node, NULL);
439 NG_NODE_UNREF(node);
440 free(sc, M_NETGRAPH);
441 return (0);
442 }
443
444 /*
445 * Hook disconnection
446 */
447 static int
448 ng_source_disconnect(hook_p hook)
449 {
450 struct source_hookinfo *hinfo;
451 sc_p sc;
452
453 hinfo = NG_HOOK_PRIVATE(hook);
454 sc = NG_NODE_PRIVATE(NG_HOOK_NODE(hook));
455 KASSERT(sc != NULL, ("%s: null node private", __func__));
456 hinfo->hook = NULL;
457 if (NG_NODE_NUMHOOKS(NG_HOOK_NODE(hook)) == 0 || hinfo == &sc->output)
458 ng_rmnode_self(NG_HOOK_NODE(hook));
459 return (0);
460 }
461
462 /*
463 *
464 * Ask out neighbour on the output hook side to send us it's interface
465 * information.
466 */
467 static int
468 ng_source_request_output_ifp(sc_p sc)
469 {
470 struct ng_mesg *msg;
471 int error = 0;
472
473 sc->output_ifp = NULL;
474
475 /* Ask the attached node for the connected interface's index */
476 NG_MKMESSAGE(msg, NGM_ETHER_COOKIE, NGM_ETHER_GET_IFINDEX, 0, M_NOWAIT);
477 if (msg == NULL)
478 return (ENOBUFS);
479
480 NG_SEND_MSG_HOOK(error, sc->node, msg, sc->output.hook, 0);
481 return (error);
482 }
483
484 /*
485 * Set sc->output_ifp to point to the the struct ifnet of the interface
486 * reached via our output hook.
487 */
488 static int
489 ng_source_store_output_ifp(sc_p sc, struct ng_mesg *msg)
490 {
491 struct ifnet *ifp;
492 u_int32_t if_index;
493 int s;
494
495 if (msg->header.arglen < sizeof(u_int32_t))
496 return (EINVAL);
497
498 if_index = *(u_int32_t *)msg->data;
499 /* Could use ifindex2ifnet[if_index] except that we have no
500 * way of verifying if_index is valid since if_indexlim is
501 * local to if_attach()
502 */
503 IFNET_RLOCK();
504 TAILQ_FOREACH(ifp, &ifnet, if_link) {
505 if (ifp->if_index == if_index)
506 break;
507 }
508 IFNET_RUNLOCK();
509
510 if (ifp == NULL) {
511 printf("%s: can't find interface %d\n", __func__, if_index);
512 return (EINVAL);
513 }
514 sc->output_ifp = ifp;
515
516 #if 1
517 /* XXX mucking with a drivers ifqueue size is ugly but we need it
518 * to queue a lot of packets to get close to line rate on a gigabit
519 * interface with small packets.
520 * XXX we should restore the original value at stop or disconnect
521 */
522 s = splimp(); /* XXX is this required? */
523 if (ifp->if_snd.ifq_maxlen < NG_SOURCE_DRIVER_IFQ_MAXLEN) {
524 printf("ng_source: changing ifq_maxlen from %d to %d\n",
525 ifp->if_snd.ifq_maxlen, NG_SOURCE_DRIVER_IFQ_MAXLEN);
526 ifp->if_snd.ifq_maxlen = NG_SOURCE_DRIVER_IFQ_MAXLEN;
527 }
528 splx(s);
529 #endif
530 return (0);
531 }
532
533 /*
534 * Set the attached ethernet node's ethernet source address override flag.
535 */
536 static int
537 ng_source_set_autosrc(sc_p sc, u_int32_t flag)
538 {
539 struct ng_mesg *msg;
540 int error = 0;
541
542 NG_MKMESSAGE(msg, NGM_ETHER_COOKIE, NGM_ETHER_SET_AUTOSRC,
543 sizeof (u_int32_t), M_NOWAIT);
544 if (msg == NULL)
545 return(ENOBUFS);
546
547 *(u_int32_t *)msg->data = flag;
548 NG_SEND_MSG_HOOK(error, sc->node, msg, sc->output.hook, 0);
549 return (error);
550 }
551
552 /*
553 * Clear out the data we've queued
554 */
555 static void
556 ng_source_clr_data (sc_p sc)
557 {
558 struct mbuf *m;
559
560 for (;;) {
561 _IF_DEQUEUE(&sc->snd_queue, m);
562 if (m == NULL)
563 break;
564 NG_FREE_M(m);
565 }
566 sc->queueOctets = 0;
567 }
568
569 /*
570 * Start sending queued data out the output hook
571 */
572 static void
573 ng_source_start (sc_p sc)
574 {
575 KASSERT(sc->output.hook != NULL,
576 ("%s: output hook unconnected", __func__));
577 if (((sc->node->nd_flags & NG_SOURCE_ACTIVE) == 0) &&
578 (sc->output_ifp == NULL))
579 ng_source_request_output_ifp(sc);
580 }
581
582 /*
583 * Stop sending queued data out the output hook
584 */
585 static void
586 ng_source_stop (sc_p sc)
587 {
588 if (sc->node->nd_flags & NG_SOURCE_ACTIVE) {
589 ng_untimeout(sc->intr_ch, sc->node);
590 sc->node->nd_flags &= ~NG_SOURCE_ACTIVE;
591 getmicrotime(&sc->stats.endTime);
592 sc->stats.elapsedTime = sc->stats.endTime;
593 timevalsub(&sc->stats.elapsedTime, &sc->stats.startTime);
594 /* XXX should set this to the initial value instead */
595 ng_source_set_autosrc(sc, 1);
596 }
597 }
598
599 /*
600 * While active called every NG_SOURCE_INTR_TICKS ticks.
601 * Sends as many packets as the interface connected to our
602 * output hook is able to enqueue.
603 */
604 static void
605 ng_source_intr(node_p node, hook_p hook, void *arg1, int arg2)
606 {
607 sc_p sc = (sc_p)arg1;
608 struct ifqueue *ifq;
609 int packets;
610
611 KASSERT(sc != NULL, ("%s: null node private", __func__));
612
613 callout_handle_init(&sc->intr_ch);
614 if (sc->packets == 0 || sc->output.hook == NULL
615 || (sc->node->nd_flags & NG_SOURCE_ACTIVE) == 0) {
616 ng_source_stop(sc);
617 return;
618 }
619
620 if (sc->output_ifp != NULL) {
621 ifq = &sc->output_ifp->if_snd;
622 packets = ifq->ifq_maxlen - ifq->ifq_len;
623 } else
624 packets = sc->snd_queue.ifq_len;
625
626 ng_source_send(sc, packets, NULL);
627 if (sc->packets == 0)
628 ng_source_stop(sc);
629 else
630 sc->intr_ch = ng_timeout(node, NULL, 0,
631 ng_source_intr, sc, NG_SOURCE_INTR_TICKS);
632 }
633
634 /*
635 * Send packets out our output hook
636 */
637 static int
638 ng_source_send (sc_p sc, int tosend, int *sent_p)
639 {
640 struct ifqueue tmp_queue;
641 struct mbuf *m, *m2;
642 int sent = 0;
643 int error = 0;
644
645 KASSERT(sc != NULL, ("%s: null node private", __func__));
646 KASSERT(tosend >= 0, ("%s: negative tosend param", __func__));
647 KASSERT(sc->node->nd_flags & NG_SOURCE_ACTIVE,
648 ("%s: inactive node", __func__));
649
650 if ((u_int64_t)tosend > sc->packets)
651 tosend = sc->packets;
652
653 /* Copy the required number of packets to a temporary queue */
654 bzero (&tmp_queue, sizeof (tmp_queue));
655 for (sent = 0; error == 0 && sent < tosend; ++sent) {
656 _IF_DEQUEUE(&sc->snd_queue, m);
657 if (m == NULL)
658 break;
659
660 /* duplicate the packet */
661 m2 = m_copypacket(m, M_DONTWAIT);
662 if (m2 == NULL) {
663 _IF_PREPEND(&sc->snd_queue, m);
664 error = ENOBUFS;
665 break;
666 }
667
668 /* re-enqueue the original packet for us */
669 _IF_ENQUEUE(&sc->snd_queue, m);
670
671 /* queue the copy for sending at smplimp */
672 _IF_ENQUEUE(&tmp_queue, m2);
673 }
674
675 sent = 0;
676 for (;;) {
677 _IF_DEQUEUE(&tmp_queue, m2);
678 if (m2 == NULL)
679 break;
680 if (error == 0) {
681 ++sent;
682 sc->stats.outFrames++;
683 sc->stats.outOctets += m2->m_pkthdr.len;
684 NG_SEND_DATA_ONLY(error, sc->output.hook, m2);
685 if (error)
686 printf("%s: error=%d\n", __func__, error);
687 } else {
688 NG_FREE_M(m2);
689 }
690 }
691
692 sc->packets -= sent;
693 if (sent_p != NULL)
694 *sent_p = sent;
695 return (error);
696 }
Cache object: f98f3e556cacd90dda550b9a2c932a20
|