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