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