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