Add the netgraph 'source' module.
This is NOT YET CONVERTED TO -current. This node is a source for preprogrammed packets at a known rate for testing. I will convert it to -current "in place" but will MFC teh original pre-conversion variant as that is what is originally submitted. Man page my me, info from Dave's README. Submitted by: Dave Chapeskie <dchapeskie@SANDVINE.com> Obtained from: Sandvine inc. MFC after: 1 week
This commit is contained in:
parent
24b50116ed
commit
585ff168dc
Notes:
svn2git
2020-12-20 02:59:44 +00:00
svn path=/head/; revision=106266
264
share/man/man4/ng_source.4
Normal file
264
share/man/man4/ng_source.4
Normal file
@ -0,0 +1,264 @@
|
||||
.\" ng_source.4
|
||||
.\" Copyright 2002 Sandvine Inc.
|
||||
.\" All rights reserved.
|
||||
.\"
|
||||
.\" Subject to the following obligations and disclaimer of warranty, use and
|
||||
.\" redistribution of this software, in source or object code forms, with or
|
||||
.\" without modifications are expressly permitted by Sandvine Inc.; provided,
|
||||
.\" however, that:
|
||||
.\" 1. Any and all reproductions of the source or object code must include the
|
||||
.\" copyright notice above and the following disclaimer of warranties; and
|
||||
.\" 2. No rights are granted, in any manner or form, to use Sandvine Inc.
|
||||
.\" trademarks, including the mark "SANDVINE" on advertising, endorsements,
|
||||
.\" or otherwise except as such appears in the above copyright notice or in
|
||||
.\" the software.
|
||||
.\"
|
||||
.\" THIS SOFTWARE IS BEING PROVIDED BY SANDVINE "AS IS", AND TO THE MAXIMUM
|
||||
.\" EXTENT PERMITTED BY LAW, SANDVINE MAKES NO REPRESENTATIONS OR WARRANTIES,
|
||||
.\" EXPRESS OR IMPLIED, REGARDING THIS SOFTWARE, INCLUDING WITHOUT LIMITATION,
|
||||
.\" ANY AND ALL IMPLIED WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR
|
||||
.\" PURPOSE, OR NON-INFRINGEMENT. SANDVINE DOES NOT WARRANT, GUARANTEE, OR
|
||||
.\" MAKE ANY REPRESENTATIONS REGARDING THE USE OF, OR THE RESULTS OF THE
|
||||
.\" USE OF THIS SOFTWARE IN TERMS OF ITS CORRECTNESS, ACCURACY, RELIABILITY
|
||||
.\" OR OTHERWISE. IN NO EVENT SHALL SANDVINE BE LIABLE FOR ANY DAMAGES
|
||||
.\" RESULTING FROM OR ARISING OUT OF ANY USE OF THIS SOFTWARE, INCLUDING
|
||||
.\" WITHOUT LIMITATION, ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY,
|
||||
.\" PUNITIVE, OR CONSEQUENTIAL DAMAGES, PROCUREMENT OF SUBSTITUTE GOODS OR
|
||||
.\" SERVICES, LOSS OF USE, DATA OR PROFITS, HOWEVER CAUSED AND UNDER ANY
|
||||
.\" THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||
.\" (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
|
||||
.\" THIS SOFTWARE, EVEN IF SANDVINE IS ADVISED OF THE POSSIBILITY OF SUCH
|
||||
.\" DAMAGE.
|
||||
.\"
|
||||
.\" Author: Dave Chapeskie <dchapeskie@sandvine.com>
|
||||
.\" $FreeBSD$
|
||||
.\"
|
||||
.Dd November 1, 2002
|
||||
.Dt NG_SOURCE 4
|
||||
.Os
|
||||
.Sh NAME
|
||||
.Nm ng_source
|
||||
.Nd netgraph discard node type
|
||||
.Sh SYNOPSIS
|
||||
.In netgraph/ng_source.h
|
||||
.Sh DESCRIPTION
|
||||
The
|
||||
.Nm source
|
||||
node acts as a source of packets according to the parameters set up
|
||||
using control messages and input packets.
|
||||
The 'output' hook must also be connected to a node that responds to the
|
||||
.Em NGM_ETHER_COOKIE
|
||||
/
|
||||
.Em NGM_ETHER_GET_IFINDEX
|
||||
message (e.g. an
|
||||
ng_ether
|
||||
node).
|
||||
node type silently discards all data and control messages it receives.
|
||||
This type is used for testing and debugging.
|
||||
.Pp
|
||||
The operation of the node is as follows:
|
||||
.Pp
|
||||
.Bl -bullet -compact -offset 2n
|
||||
.It
|
||||
Packets received on the 'input' hook are queued internally.
|
||||
.It
|
||||
On recpetion of a NGM_SOURCE_START message the node starts sending
|
||||
the queued packets out the 'output' hook on every clock tick as fast
|
||||
as the connect interface will take them.
|
||||
.It
|
||||
While active, on every clock tick the node checks the available space
|
||||
in the ifqueue of the interface connected to the output hook and sends
|
||||
that many packets out it's output hook.
|
||||
.It
|
||||
Once the number of packets indicated in the start message have been
|
||||
sent, or on reception of a stop message, the node stops sending data.
|
||||
.El
|
||||
.Sh HOOKS
|
||||
The
|
||||
.Nm source
|
||||
node has two hooks: 'input' and 'output'. The 'output'
|
||||
hook must remain connected, its disconnection will shutdown the node.
|
||||
.Sh CONTROL MESSAGES
|
||||
This node type supports the generic control messages as well as the following,
|
||||
which must be sent with the
|
||||
.Em NGM_SOURCE_COOKIE
|
||||
attached.
|
||||
.Bl -tag -width NGM_SOURCE_GETCLR_STATS
|
||||
.It NGM_SOURCE_GET_STATS
|
||||
"getstats":
|
||||
Returns a structure containing the following fields:
|
||||
.\".Bl -bullet -compact -offset 2n
|
||||
.Bl -tag -width queueFrames:
|
||||
.It outOctets:
|
||||
The number of octets/bytes sent out the 'output' hook.
|
||||
.It outFrames:
|
||||
The number of frames/packets sent out the 'output' hook.
|
||||
.It queueOctets:
|
||||
The number of octets queued from the 'input' hook.
|
||||
.It queueFrames:
|
||||
The number of frames queued from the 'input' hook.
|
||||
.It startTime:
|
||||
The time the last start message was recieved.
|
||||
.It endTime:
|
||||
The time the last end message was recieved or
|
||||
the output packet count was reached.
|
||||
.It elapsedTime:
|
||||
Either endTime-startTime or current time - startTime.
|
||||
.El
|
||||
.It NGM_SOURCE_CLR_STATS
|
||||
"clrstats":
|
||||
Clears and resets the statistics returned by getstats (except
|
||||
queueOctects and queueFrames).
|
||||
.It NGM_SOURCE_GETCLR_STATS
|
||||
"getclrstats":
|
||||
As getstats but clears the statistics at the same time.
|
||||
.It NGM_SOURCE_START
|
||||
"start":
|
||||
.Bl -bullet -compact -offset 2n
|
||||
.It
|
||||
Takes a single u_int64_t parameter which is the number of packets to
|
||||
send before stopping.
|
||||
.It
|
||||
Starts sending the queued packets out the output hook.
|
||||
.It
|
||||
The output hook must be connected or EINVAL is returned.
|
||||
.It
|
||||
The node connected to the output hook must respond to
|
||||
.Em NGM_ETHER_GET_IFINDEX
|
||||
which is used to get the ifqueue of the attached
|
||||
interface.
|
||||
.It
|
||||
.Em NGM_ETHER_SET_AUTOSRC
|
||||
is sent to the node connected to the output hook
|
||||
to turn off automatic ethernet source address overwriting (any errors
|
||||
from this message are ignored).
|
||||
.El
|
||||
.It NGM_SOURCE_STOP
|
||||
"stop":
|
||||
Stops the node if it is active.
|
||||
.It NGM_SOURCE_CLR_DATA
|
||||
"clrdata":
|
||||
Clears the packets queued from the 'input' hook.
|
||||
.El
|
||||
.Sh SHUTDOWN
|
||||
This node shuts down upon receipt of a
|
||||
.Dv NGM_SHUTDOWN
|
||||
control message, or when the
|
||||
.Em output
|
||||
hook has been disconnected.
|
||||
.Sh EXAMPLE
|
||||
Build and install the node to /modules (or load it anually).
|
||||
.Bd -literal -offset 0n
|
||||
$ make obj
|
||||
$ make depend
|
||||
$ make
|
||||
$ make install
|
||||
.Ed
|
||||
.Pp
|
||||
Attach the node to an ng_ether node for an interface. If ng_ether is
|
||||
not already loaded you'll need to do so. For example these commands
|
||||
load the ng_ether module and attach the output hook of a new source node
|
||||
to orphans hook of the bge0: ng_ether node.
|
||||
.Bd -literal -offset 0n
|
||||
$ kldload ng_ether
|
||||
$ ngctl mkpeer bge0: source orphans output
|
||||
.Ed
|
||||
.Pp
|
||||
At this point the new node can be refered to as "bge0:orphans". The
|
||||
node can be given it's own name like this:
|
||||
.Bd -literal -offset 0n
|
||||
$ ngctl name bge0:orphans src0
|
||||
.Ed
|
||||
.Pp
|
||||
After which it can be refered to as "src0:".
|
||||
.Pp
|
||||
Once created packets need to be sent to the node, the TCL net package
|
||||
can be used to generate these packets:
|
||||
.Pp
|
||||
[Sandvine specific TCL code example omitted]
|
||||
.Pp
|
||||
To feed the output of the above TCL script to the ng_source node's input
|
||||
hook via nghook:
|
||||
.Bd -literal -offset 0n
|
||||
$ tcl genPacket | nghook bge0:orphans input
|
||||
.Ed
|
||||
.Pp
|
||||
To check that the node has queued these packets you can get the node
|
||||
statistics:
|
||||
.Bd -literal -offset 0n
|
||||
$ ngctl msg bge0:orphans getstats
|
||||
Args: { queueOctets=64 queueFrames=1 }
|
||||
.Ed
|
||||
.Pp
|
||||
Send as many packets as required out the output hook:
|
||||
.Bd -literal -offset 0n
|
||||
$ ngctl msg bge0:orphans start 16
|
||||
.Ed
|
||||
.Pp
|
||||
Either wait for them to be sent (periodicly fetching stats if desired)
|
||||
or send the stop message:
|
||||
.Bd -literal -offset 0n
|
||||
$ ngctl msg bge0:orphans stop
|
||||
.Ed
|
||||
.Pp
|
||||
Check the statistics (here we use getclrstats to also clear the
|
||||
statistics):
|
||||
.Bd -literal -offset 0n
|
||||
$ ngctl msg bge0:orphans getclrstats
|
||||
Args: { outOctets=1024 outFrames=16 queueOctets=64 queueFrames=1
|
||||
startTime={ tv_sec=1035305880 tv_usec=758036 } endTime={ tv_sec=1035305880
|
||||
tv_usec=759041 } elapsedTime={ tv_usec=1005 } }
|
||||
.Ed
|
||||
.Pp
|
||||
The times are from "struct timeval"s, the tv_sec field is seconds since
|
||||
the epoch and can be converted into a date string via TCL's [clock
|
||||
format] or via the UNIX date command:
|
||||
.Bd -literal -offset 0n
|
||||
$ date -r 1035305880
|
||||
Tue Oct 22 12:58:00 EDT 2002
|
||||
.Ed
|
||||
.Pp
|
||||
.Sh IMPLEMENTATION NOTES
|
||||
(FreeBSD 4.4 version)
|
||||
.Pp
|
||||
The use of splimp around the NG_SEND_DATA loop is important. Without
|
||||
it the time taken by a single invocation of ng_source_intr becomes too
|
||||
large and the packet rate drops. Probably due to the NIC starting to
|
||||
send the packets right away.
|
||||
.Pp
|
||||
Copying all the packets in one loop and sending them in another inside
|
||||
of ng_source_send is done to limit how long we're at splimp and gave
|
||||
minor packet rate increases (~5% at 256 byte packets). However note
|
||||
that if there are errors in the send loop the remaining copied packets
|
||||
are simply freed and discarded thus we skip those packets and ordering
|
||||
of the input queue to the output is not maintained.
|
||||
.Pp
|
||||
Calling timeout(9) at the end of ng_source_intr instead of near the
|
||||
begining is done to help avoid CPU starvaion if ng_source_intr takes a
|
||||
long time to run.
|
||||
.Pp
|
||||
The use of splnet may be sub-optimal. It's used for syncronization
|
||||
within the node (e.g. data recieved on the input hook while
|
||||
ng_source_send is active) but we don't want to hold it too long and risk
|
||||
starving the NIC.
|
||||
.Pp
|
||||
For clarity and simplicity debugging messages and instrumentation code
|
||||
has been removed. On i386 one can include machine/cpufunc.h to have
|
||||
access to the rdtsc() function to read the instruction counter at the
|
||||
start and end of ng_source_intr. Also useful is the packet count
|
||||
returned by ng_source_send. Do not try to report such things from
|
||||
within ng_source_intr, instead include the values in sc->stats.
|
||||
.Sh SEE ALSO
|
||||
.Xr netgraph 4 ,
|
||||
.Xr ng_echo 4 ,
|
||||
.Xr ng_hole 4 ,
|
||||
.Xr ng_tee 4 ,
|
||||
.Xr ngctl 8
|
||||
.Xr nghook 8
|
||||
.Sh HISTORY
|
||||
The
|
||||
.Nm
|
||||
node type was implemented in
|
||||
.Fx 4.8 .
|
||||
.Sh AUTHORS
|
||||
.An Dave Chapeskie Aq dchapeskie@SANDVINE.com
|
8
sys/modules/netgraph/source/Makefile
Normal file
8
sys/modules/netgraph/source/Makefile
Normal file
@ -0,0 +1,8 @@
|
||||
# $FreeBSD$
|
||||
|
||||
KMOD= ng_source
|
||||
SRCS= ng_source.c
|
||||
# 4.x only
|
||||
#KMODDEPS= netgraph
|
||||
|
||||
.include <bsd.kmod.mk>
|
682
sys/netgraph/ng_source.c
Normal file
682
sys/netgraph/ng_source.c
Normal file
@ -0,0 +1,682 @@
|
||||
/*
|
||||
* ng_source.c
|
||||
*
|
||||
* Copyright 2002 Sandvine Inc.
|
||||
* All rights reserved.
|
||||
*
|
||||
* Subject to the following obligations and disclaimer of warranty, use and
|
||||
* redistribution of this software, in source or object code forms, with or
|
||||
* without modifications are expressly permitted by Sandvine Inc.;
|
||||
provided,
|
||||
* however, that:
|
||||
* 1. Any and all reproductions of the source or object code must include
|
||||
the
|
||||
* copyright notice above and the following disclaimer of warranties;
|
||||
and
|
||||
* 2. No rights are granted, in any manner or form, to use Sandvine Inc.
|
||||
* trademarks, including the mark "SANDVINE" on advertising,
|
||||
endorsements,
|
||||
* or otherwise except as such appears in the above copyright notice or
|
||||
in
|
||||
* the software.
|
||||
*
|
||||
* THIS SOFTWARE IS BEING PROVIDED BY SANDVINE "AS IS", AND TO THE MAXIMUM
|
||||
* EXTENT PERMITTED BY LAW, SANDVINE MAKES NO REPRESENTATIONS OR
|
||||
WARRANTIES,
|
||||
* EXPRESS OR IMPLIED, REGARDING THIS SOFTWARE, INCLUDING WITHOUT
|
||||
LIMITATION,
|
||||
* ANY AND ALL IMPLIED WARRANTIES OF MERCHANTABILITY, FITNESS FOR A
|
||||
PARTICULAR
|
||||
* PURPOSE, OR NON-INFRINGEMENT. SANDVINE DOES NOT WARRANT, GUARANTEE, OR
|
||||
* MAKE ANY REPRESENTATIONS REGARDING THE USE OF, OR THE RESULTS OF THE
|
||||
* USE OF THIS SOFTWARE IN TERMS OF ITS CORRECTNESS, ACCURACY, RELIABILITY
|
||||
* OR OTHERWISE. IN NO EVENT SHALL SANDVINE BE LIABLE FOR ANY DAMAGES
|
||||
* RESULTING FROM OR ARISING OUT OF ANY USE OF THIS SOFTWARE, INCLUDING
|
||||
* WITHOUT LIMITATION, ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
|
||||
EXEMPLARY,
|
||||
* PUNITIVE, OR CONSEQUENTIAL DAMAGES, PROCUREMENT OF SUBSTITUTE GOODS OR
|
||||
* SERVICES, LOSS OF USE, DATA OR PROFITS, HOWEVER CAUSED AND UNDER ANY
|
||||
* THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
|
||||
* THIS SOFTWARE, EVEN IF SANDVINE IS ADVISED OF THE POSSIBILITY OF SUCH
|
||||
* DAMAGE.
|
||||
*
|
||||
* Author: Dave Chapeskie <dchapeskie@sandvine.com>
|
||||
*
|
||||
* $FreeBSD$
|
||||
*/
|
||||
|
||||
/*
|
||||
* This node is used for high speed packet geneneration. It queues
|
||||
* all data recieved on it's 'input' hook and when told to start via
|
||||
* a control message it sends the packets out it's 'output' hook. In
|
||||
* this way this node can be preloaded with a packet stream which is
|
||||
* continuously sent.
|
||||
*
|
||||
* Currently it just copies the mbufs as required. It could do various
|
||||
* tricks to try and avoid this. Probably the best performance would
|
||||
* be achieved by modifying the appropriate drivers to be told to
|
||||
* self-re-enqueue packets (e.g. the if_bge driver could reuse the same
|
||||
* transmit descriptors) under control of this node; perhaps via some
|
||||
* flag in the mbuf or some such. The node would peak at an appropriate
|
||||
* ifnet flag to see if such support is available for the connected
|
||||
* interface.
|
||||
*/
|
||||
|
||||
#include <sys/param.h>
|
||||
#include <sys/systm.h>
|
||||
#include <sys/errno.h>
|
||||
#include <sys/kernel.h>
|
||||
#include <sys/malloc.h>
|
||||
#include <sys/mbuf.h>
|
||||
#include <sys/socket.h>
|
||||
#include <net/if.h>
|
||||
#include <net/if_var.h>
|
||||
#include <netgraph/ng_message.h>
|
||||
#include <netgraph/netgraph.h>
|
||||
#include <netgraph/ng_parse.h>
|
||||
#include <netgraph/ng_ether.h>
|
||||
#include <netgraph/ng_source.h>
|
||||
|
||||
#define NG_SOURCE_INTR_TICKS 1
|
||||
#define NG_SOURCE_DRIVER_IFQ_MAXLEN (4*1024)
|
||||
|
||||
|
||||
/* Per hook info */
|
||||
struct source_hookinfo {
|
||||
hook_p hook;
|
||||
};
|
||||
|
||||
/* Per node info */
|
||||
struct privdata {
|
||||
node_p node;
|
||||
struct source_hookinfo input;
|
||||
struct source_hookinfo output;
|
||||
struct ng_source_stats stats;
|
||||
struct ifqueue snd_queue; /* packets to send
|
||||
*/
|
||||
struct ifnet *output_ifp;
|
||||
struct callout_handle intr_ch;
|
||||
u_int64_t packets; /* packets to send
|
||||
*/
|
||||
u_int32_t queueOctets;
|
||||
};
|
||||
typedef struct privdata *sc_p;
|
||||
|
||||
/* Node flags */
|
||||
#define NG_SOURCE_ACTIVE (NGF_TYPE1)
|
||||
|
||||
/* XXX */
|
||||
#if 1
|
||||
#undef KASSERT
|
||||
#define KASSERT(expr,msg) do { \
|
||||
if (!(expr)) { \
|
||||
printf msg ; \
|
||||
panic("Assertion"); \
|
||||
} \
|
||||
} while(0)
|
||||
#endif
|
||||
|
||||
/* Netgraph methods */
|
||||
static ng_constructor_t ng_source_constructor;
|
||||
static ng_rcvmsg_t ng_source_rcvmsg;
|
||||
static ng_shutdown_t ng_source_rmnode;
|
||||
static ng_newhook_t ng_source_newhook;
|
||||
static ng_rcvdata_t ng_source_rcvdata;
|
||||
static ng_disconnect_t ng_source_disconnect;
|
||||
|
||||
/* Other functions */
|
||||
static timeout_t ng_source_intr;
|
||||
static int ng_source_get_output_ifp (sc_p);
|
||||
static void ng_source_clr_data (sc_p);
|
||||
static void ng_source_start (sc_p);
|
||||
static void ng_source_stop (sc_p);
|
||||
static int ng_source_send (sc_p, int, int *);
|
||||
|
||||
|
||||
/* Parse type for timeval */
|
||||
static const struct ng_parse_struct_field ng_source_timeval_type_fields[] =
|
||||
{
|
||||
{ "tv_sec", &ng_parse_int32_type },
|
||||
{ "tv_usec", &ng_parse_int32_type },
|
||||
{ NULL }
|
||||
};
|
||||
const struct ng_parse_type ng_source_timeval_type = {
|
||||
&ng_parse_struct_type,
|
||||
&ng_source_timeval_type_fields
|
||||
};
|
||||
|
||||
/* Parse type for struct ng_source_stats */
|
||||
static const struct ng_parse_struct_field ng_source_stats_type_fields[]
|
||||
= NG_SOURCE_STATS_TYPE_INFO;
|
||||
static const struct ng_parse_type ng_source_stats_type = {
|
||||
&ng_parse_struct_type,
|
||||
&ng_source_stats_type_fields
|
||||
};
|
||||
|
||||
/* List of commands and how to convert arguments to/from ASCII */
|
||||
static const struct ng_cmdlist ng_source_cmds[] = {
|
||||
{
|
||||
NGM_SOURCE_COOKIE,
|
||||
NGM_SOURCE_GET_STATS,
|
||||
"getstats",
|
||||
NULL,
|
||||
&ng_source_stats_type
|
||||
},
|
||||
{
|
||||
NGM_SOURCE_COOKIE,
|
||||
NGM_SOURCE_CLR_STATS,
|
||||
"clrstats",
|
||||
NULL,
|
||||
NULL
|
||||
},
|
||||
{
|
||||
NGM_SOURCE_COOKIE,
|
||||
NGM_SOURCE_GETCLR_STATS,
|
||||
"getclrstats",
|
||||
NULL,
|
||||
&ng_source_stats_type
|
||||
},
|
||||
{
|
||||
NGM_SOURCE_COOKIE,
|
||||
NGM_SOURCE_START,
|
||||
"start",
|
||||
&ng_parse_uint64_type,
|
||||
NULL
|
||||
},
|
||||
{
|
||||
NGM_SOURCE_COOKIE,
|
||||
NGM_SOURCE_STOP,
|
||||
"stop",
|
||||
NULL,
|
||||
NULL
|
||||
},
|
||||
{
|
||||
NGM_SOURCE_COOKIE,
|
||||
NGM_SOURCE_CLR_DATA,
|
||||
"clrdata",
|
||||
NULL,
|
||||
NULL
|
||||
},
|
||||
{ 0 }
|
||||
};
|
||||
|
||||
/* Netgraph type descriptor */
|
||||
static struct ng_type ng_source_typestruct = {
|
||||
NG_VERSION,
|
||||
NG_SOURCE_NODE_TYPE,
|
||||
NULL, /* module event handler */
|
||||
ng_source_constructor,
|
||||
ng_source_rcvmsg,
|
||||
ng_source_rmnode,
|
||||
ng_source_newhook,
|
||||
NULL, /* findhook */
|
||||
NULL,
|
||||
ng_source_rcvdata, /* rcvdata */
|
||||
ng_source_rcvdata, /* rcvdataq */
|
||||
ng_source_disconnect,
|
||||
ng_source_cmds
|
||||
};
|
||||
NETGRAPH_INIT(source, &ng_source_typestruct);
|
||||
|
||||
/*
|
||||
* Node constructor
|
||||
*/
|
||||
static int
|
||||
ng_source_constructor(node_p *nodep)
|
||||
{
|
||||
sc_p sc;
|
||||
int error = 0;
|
||||
|
||||
MALLOC(sc, sc_p, sizeof(*sc), M_NETGRAPH, M_NOWAIT);
|
||||
if (sc == NULL)
|
||||
return (ENOMEM);
|
||||
bzero(sc, sizeof(*sc));
|
||||
|
||||
if ((error = ng_make_node_common(&ng_source_typestruct, nodep))) {
|
||||
FREE(sc, M_NETGRAPH);
|
||||
return (error);
|
||||
}
|
||||
(*nodep)->private = sc;
|
||||
sc->node = *nodep;
|
||||
sc->snd_queue.ifq_maxlen = 2048; /* XXX not checked */
|
||||
callout_handle_init(&sc->intr_ch);
|
||||
return (0);
|
||||
}
|
||||
|
||||
/*
|
||||
* Add a hook
|
||||
*/
|
||||
static int
|
||||
ng_source_newhook(node_p node, hook_p hook, const char *name)
|
||||
{
|
||||
const sc_p sc = node->private;
|
||||
|
||||
KASSERT(sc != NULL, ("%s: null node private", __FUNCTION__));
|
||||
if (strcmp(name, NG_SOURCE_HOOK_INPUT) == 0) {
|
||||
sc->input.hook = hook;
|
||||
hook->private = &sc->input;
|
||||
} else if (strcmp(name, NG_SOURCE_HOOK_OUTPUT) == 0) {
|
||||
sc->output.hook = hook;
|
||||
hook->private = &sc->output;
|
||||
sc->output_ifp = 0;
|
||||
bzero(&sc->stats, sizeof(sc->stats));
|
||||
} else
|
||||
return (EINVAL);
|
||||
return (0);
|
||||
}
|
||||
|
||||
/*
|
||||
* Receive a control message
|
||||
*/
|
||||
static int
|
||||
ng_source_rcvmsg(node_p node, struct ng_mesg *msg, const char *retaddr,
|
||||
struct ng_mesg **rptr)
|
||||
{
|
||||
const sc_p sc = node->private;
|
||||
struct ng_mesg *resp = NULL;
|
||||
int error = 0;
|
||||
|
||||
KASSERT(sc != NULL, ("%s: null node private", __FUNCTION__));
|
||||
switch (msg->header.typecookie) {
|
||||
case NGM_SOURCE_COOKIE:
|
||||
switch (msg->header.cmd) {
|
||||
case NGM_SOURCE_GET_STATS:
|
||||
case NGM_SOURCE_CLR_STATS:
|
||||
case NGM_SOURCE_GETCLR_STATS:
|
||||
{
|
||||
struct ng_source_stats *stats;
|
||||
|
||||
if (msg->header.cmd != NGM_SOURCE_CLR_STATS) {
|
||||
NG_MKRESPONSE(resp, msg,
|
||||
sizeof(*stats), M_NOWAIT);
|
||||
if (resp == NULL) {
|
||||
error = ENOMEM;
|
||||
goto done;
|
||||
}
|
||||
sc->stats.queueOctets = sc->queueOctets;
|
||||
sc->stats.queueFrames =
|
||||
sc->snd_queue.ifq_len;
|
||||
if ((sc->node->flags & NG_SOURCE_ACTIVE)
|
||||
&& !timevalisset(&sc->stats.endTime)) {
|
||||
|
||||
getmicrotime(&sc->stats.elapsedTime);
|
||||
timevalsub(&sc->stats.elapsedTime,
|
||||
|
||||
&sc->stats.startTime);
|
||||
}
|
||||
stats = (struct ng_source_stats
|
||||
*)resp->data;
|
||||
bcopy(&sc->stats, stats, sizeof(* stats));
|
||||
}
|
||||
if (msg->header.cmd != NGM_SOURCE_GET_STATS)
|
||||
bzero(&sc->stats, sizeof(sc->stats));
|
||||
}
|
||||
break;
|
||||
case NGM_SOURCE_START:
|
||||
{
|
||||
u_int64_t packets = *(u_int64_t *)msg->data;
|
||||
if (sc->output.hook == NULL) {
|
||||
printf("%s: start on node with no output
|
||||
hook\n", __FUNCTION__);
|
||||
error = EINVAL;
|
||||
break;
|
||||
}
|
||||
/* TODO validation of packets */
|
||||
sc->packets = packets;
|
||||
ng_source_start(sc);
|
||||
}
|
||||
break;
|
||||
case NGM_SOURCE_STOP:
|
||||
ng_source_stop(sc);
|
||||
break;
|
||||
case NGM_SOURCE_CLR_DATA:
|
||||
ng_source_clr_data(sc);
|
||||
break;
|
||||
default:
|
||||
error = EINVAL;
|
||||
break;
|
||||
}
|
||||
break;
|
||||
default:
|
||||
error = EINVAL;
|
||||
break;
|
||||
}
|
||||
if (rptr)
|
||||
*rptr = resp;
|
||||
else if (resp)
|
||||
FREE(resp, M_NETGRAPH);
|
||||
|
||||
done:
|
||||
FREE(msg, M_NETGRAPH);
|
||||
return (error);
|
||||
}
|
||||
|
||||
/*
|
||||
* Receive data on a hook
|
||||
*
|
||||
* If data comes in the input hook, enqueue it on the send queue.
|
||||
* If data comes in the output hook, discard it.
|
||||
*/
|
||||
static int
|
||||
ng_source_rcvdata(hook_p hook, struct mbuf *m, meta_p meta)
|
||||
{
|
||||
const sc_p sc = hook->node->private;
|
||||
struct source_hookinfo *const hinfo = (struct source_hookinfo *)
|
||||
hook->private;
|
||||
int error = 0;
|
||||
|
||||
KASSERT(sc != NULL, ("%s: null node private", __FUNCTION__));
|
||||
KASSERT(hinfo != NULL, ("%s: null hook info", __FUNCTION__));
|
||||
|
||||
/* Which hook? */
|
||||
if (hinfo == &sc->output) {
|
||||
/* discard */
|
||||
NG_FREE_DATA(m, meta);
|
||||
return (error);
|
||||
}
|
||||
KASSERT(hinfo == &sc->input, ("%s: no hook!", __FUNCTION__));
|
||||
|
||||
if ((m->m_flags & M_PKTHDR) == 0) {
|
||||
printf("%s: mbuf without PKTHDR\n", __FUNCTION__);
|
||||
NG_FREE_DATA(m, meta);
|
||||
return (EINVAL);
|
||||
}
|
||||
|
||||
/* XXX we discard the meta data for now */
|
||||
NG_FREE_META(meta);
|
||||
|
||||
/* enque packet */
|
||||
/* XXX should we check IF_QFULL() ? */
|
||||
IF_ENQUEUE(&sc->snd_queue, m);
|
||||
sc->queueOctets += m->m_pkthdr.len;
|
||||
|
||||
return (0);
|
||||
}
|
||||
|
||||
/*
|
||||
* Shutdown processing
|
||||
*/
|
||||
static int
|
||||
ng_source_rmnode(node_p node)
|
||||
{
|
||||
const sc_p sc = node->private;
|
||||
|
||||
KASSERT(sc != NULL, ("%s: null node private", __FUNCTION__));
|
||||
node->flags |= NG_INVALID;
|
||||
ng_source_stop(sc);
|
||||
ng_cutlinks(node);
|
||||
ng_source_clr_data(sc);
|
||||
ng_unname(node);
|
||||
node->private = NULL;
|
||||
ng_unref(sc->node);
|
||||
FREE(sc, M_NETGRAPH);
|
||||
return (0);
|
||||
}
|
||||
|
||||
/*
|
||||
* Hook disconnection
|
||||
*/
|
||||
static int
|
||||
ng_source_disconnect(hook_p hook)
|
||||
{
|
||||
struct source_hookinfo *const hinfo = (struct source_hookinfo *)
|
||||
hook->private;
|
||||
sc_p sc = (sc_p) hinfo->hook->node->private;
|
||||
|
||||
KASSERT(sc != NULL, ("%s: null node private", __FUNCTION__));
|
||||
hinfo->hook = NULL;
|
||||
if (hook->node->numhooks == 0 || hinfo == &sc->output)
|
||||
ng_rmnode(hook->node);
|
||||
return (0);
|
||||
}
|
||||
|
||||
/*
|
||||
* Set sc->output_ifp to point to the the struct ifnet of the interface
|
||||
* reached via our output hook.
|
||||
*/
|
||||
static int
|
||||
ng_source_get_output_ifp(sc_p sc)
|
||||
{
|
||||
struct ng_mesg *msg, *rsp;
|
||||
struct ifnet *ifp;
|
||||
u_int32_t if_index;
|
||||
int error = 0;
|
||||
int s;
|
||||
|
||||
sc->output_ifp = NULL;
|
||||
|
||||
/* Ask the attached node for the connected interface's index */
|
||||
NG_MKMESSAGE(msg, NGM_ETHER_COOKIE, NGM_ETHER_GET_IFINDEX, 0,
|
||||
M_NOWAIT);
|
||||
if (msg == NULL)
|
||||
return (ENOBUFS);
|
||||
|
||||
error = ng_send_msg(sc->node, msg, NG_SOURCE_HOOK_OUTPUT, &rsp);
|
||||
if (error != 0)
|
||||
return (error);
|
||||
|
||||
if (rsp == NULL)
|
||||
return (EINVAL);
|
||||
|
||||
if (rsp->header.arglen < sizeof(u_int32_t))
|
||||
return (EINVAL);
|
||||
|
||||
if_index = *(u_int32_t *)rsp->data;
|
||||
/* Could use ifindex2ifnet[if_index] except that we have no
|
||||
* way of verifying if_index is valid since if_indexlim is
|
||||
* local to if_attach()
|
||||
*/
|
||||
TAILQ_FOREACH(ifp, &ifnet, if_link) {
|
||||
if (ifp->if_index == if_index)
|
||||
break;
|
||||
}
|
||||
|
||||
if (ifp == NULL) {
|
||||
printf("%s: can't find interface %d\n", __FUNCTION__,
|
||||
if_index);
|
||||
return (EINVAL);
|
||||
}
|
||||
sc->output_ifp = ifp;
|
||||
|
||||
#if 1
|
||||
/* XXX mucking with a drivers ifqueue size is ugly but we need it
|
||||
* to queue a lot of packets to get close to line rate on a gigabit
|
||||
* interface with small packets.
|
||||
* XXX we should restore the original value at stop or disconnect
|
||||
*/
|
||||
s = splimp(); /* XXX is this required? */
|
||||
if (ifp->if_snd.ifq_maxlen < NG_SOURCE_DRIVER_IFQ_MAXLEN)
|
||||
{
|
||||
printf("ng_source: changing ifq_maxlen from %d to %d\n",
|
||||
ifp->if_snd.ifq_maxlen,
|
||||
NG_SOURCE_DRIVER_IFQ_MAXLEN);
|
||||
ifp->if_snd.ifq_maxlen = NG_SOURCE_DRIVER_IFQ_MAXLEN;
|
||||
}
|
||||
splx(s);
|
||||
#endif
|
||||
return (0);
|
||||
}
|
||||
|
||||
/*
|
||||
* Set the attached ethernet node's ethernet source address override flag.
|
||||
*/
|
||||
static int
|
||||
ng_source_set_autosrc(sc_p sc, u_int32_t flag)
|
||||
{
|
||||
struct ng_mesg *msg;
|
||||
int error = 0;
|
||||
|
||||
NG_MKMESSAGE(msg, NGM_ETHER_COOKIE, NGM_ETHER_SET_AUTOSRC,
|
||||
sizeof (u_int32_t), M_NOWAIT);
|
||||
if (msg == NULL)
|
||||
return(ENOBUFS);
|
||||
|
||||
*(u_int32_t *)msg->data = flag;
|
||||
error = ng_send_msg(sc->node, msg, NG_SOURCE_HOOK_OUTPUT, NULL);
|
||||
return (error);
|
||||
}
|
||||
|
||||
/*
|
||||
* Clear out the data we've queued
|
||||
*/
|
||||
static void
|
||||
ng_source_clr_data (sc_p sc)
|
||||
{
|
||||
struct mbuf *m;
|
||||
|
||||
SPLASSERT(net, __FUNCTION__);
|
||||
for (;;) {
|
||||
IF_DEQUEUE(&sc->snd_queue, m);
|
||||
if (m == NULL)
|
||||
break;
|
||||
NG_FREE_M(m);
|
||||
}
|
||||
sc->queueOctets = 0;
|
||||
}
|
||||
|
||||
/*
|
||||
* Start sending queued data out the output hook
|
||||
*/
|
||||
static void
|
||||
ng_source_start (sc_p sc)
|
||||
{
|
||||
SPLASSERT(net, __FUNCTION__);
|
||||
KASSERT(sc->output.hook != NULL,
|
||||
("%s: output hook unconnected", __FUNCTION__));
|
||||
if ((sc->node->flags & NG_SOURCE_ACTIVE) == 0) {
|
||||
if (sc->output_ifp == NULL && ng_source_get_output_ifp(sc)
|
||||
!= 0)
|
||||
return;
|
||||
ng_source_set_autosrc(sc, 0);
|
||||
sc->node->flags |= NG_SOURCE_ACTIVE;
|
||||
timevalclear(&sc->stats.elapsedTime);
|
||||
timevalclear(&sc->stats.endTime);
|
||||
getmicrotime(&sc->stats.startTime);
|
||||
sc->intr_ch = timeout(ng_source_intr, sc, 0);
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* Stop sending queued data out the output hook
|
||||
*/
|
||||
static void
|
||||
ng_source_stop (sc_p sc)
|
||||
{
|
||||
SPLASSERT(net, __FUNCTION__);
|
||||
if (sc->node->flags & NG_SOURCE_ACTIVE) {
|
||||
untimeout(ng_source_intr, sc, sc->intr_ch);
|
||||
sc->node->flags &= ~NG_SOURCE_ACTIVE;
|
||||
getmicrotime(&sc->stats.endTime);
|
||||
sc->stats.elapsedTime = sc->stats.endTime;
|
||||
timevalsub(&sc->stats.elapsedTime, &sc->stats.startTime);
|
||||
/* XXX should set this to the initial value instead */
|
||||
ng_source_set_autosrc(sc, 1);
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* While active called every NG_SOURCE_INTR_TICKS ticks.
|
||||
* Sends as many packets as the interface connected to our
|
||||
* output hook is able to enqueue.
|
||||
*/
|
||||
static void
|
||||
ng_source_intr (void *arg)
|
||||
{
|
||||
const sc_p sc = (sc_p) arg;
|
||||
struct ifqueue *ifq;
|
||||
int packets;
|
||||
|
||||
KASSERT(sc != NULL, ("%s: null node private", __FUNCTION__));
|
||||
|
||||
callout_handle_init(&sc->intr_ch);
|
||||
if (sc->packets == 0 || sc->output.hook == NULL
|
||||
|| (sc->node->flags & NG_SOURCE_ACTIVE) == 0) {
|
||||
ng_source_stop(sc);
|
||||
return;
|
||||
}
|
||||
|
||||
ifq = &sc->output_ifp->if_snd;
|
||||
packets = ifq->ifq_maxlen - ifq->ifq_len;
|
||||
ng_source_send(sc, packets, NULL);
|
||||
if (sc->packets == 0) {
|
||||
int s = splnet();
|
||||
ng_source_stop(sc);
|
||||
splx(s);
|
||||
} else
|
||||
sc->intr_ch = timeout(ng_source_intr, sc,
|
||||
NG_SOURCE_INTR_TICKS);
|
||||
}
|
||||
|
||||
/*
|
||||
* Send packets out our output hook
|
||||
*/
|
||||
static int
|
||||
ng_source_send (sc_p sc, int tosend, int *sent_p)
|
||||
{
|
||||
struct ifqueue tmp_queue;
|
||||
struct mbuf *m, *m2;
|
||||
int sent = 0;
|
||||
int error = 0;
|
||||
int s, s2;
|
||||
|
||||
KASSERT(sc != NULL, ("%s: null node private", __FUNCTION__));
|
||||
KASSERT(tosend >= 0, ("%s: negative tosend param", __FUNCTION__));
|
||||
KASSERT(sc->node->flags & NG_SOURCE_ACTIVE,
|
||||
("%s: inactive node", __FUNCTION__));
|
||||
|
||||
if ((u_int64_t)tosend > sc->packets)
|
||||
tosend = sc->packets;
|
||||
|
||||
/* Copy the required number of packets to a temporary queue */
|
||||
bzero (&tmp_queue, sizeof (tmp_queue));
|
||||
for (sent = 0; error == 0 && sent < tosend; ++sent) {
|
||||
s = splnet();
|
||||
IF_DEQUEUE(&sc->snd_queue, m);
|
||||
splx(s);
|
||||
if (m == NULL)
|
||||
break;
|
||||
|
||||
/* duplicate the packet */
|
||||
m2 = m_copypacket(m, M_NOWAIT);
|
||||
if (m2 == NULL) {
|
||||
s = splnet();
|
||||
IF_PREPEND(&sc->snd_queue, m);
|
||||
splx(s);
|
||||
error = ENOBUFS;
|
||||
break;
|
||||
}
|
||||
|
||||
/* re-enqueue the original packet for us */
|
||||
s = splnet();
|
||||
IF_ENQUEUE(&sc->snd_queue, m);
|
||||
splx(s);
|
||||
|
||||
/* queue the copy for sending at smplimp */
|
||||
IF_ENQUEUE(&tmp_queue, m2);
|
||||
}
|
||||
|
||||
sent = 0;
|
||||
s = splimp();
|
||||
for (;;) {
|
||||
IF_DEQUEUE(&tmp_queue, m2);
|
||||
if (m2 == NULL)
|
||||
break;
|
||||
if (error == 0) {
|
||||
++sent;
|
||||
sc->stats.outFrames++;
|
||||
sc->stats.outOctets += m2->m_pkthdr.len;
|
||||
s2 = splnet();
|
||||
NG_SEND_DATA_ONLY(error, sc->output.hook, m2);
|
||||
splx(s2);
|
||||
} else {
|
||||
NG_FREE_M(m2);
|
||||
}
|
||||
}
|
||||
splx(s);
|
||||
|
||||
sc->packets -= sent;
|
||||
if (sent_p != NULL)
|
||||
*sent_p = sent;
|
||||
return (error);
|
||||
}
|
94
sys/netgraph/ng_source.h
Normal file
94
sys/netgraph/ng_source.h
Normal file
@ -0,0 +1,94 @@
|
||||
/*
|
||||
* ng_source.h
|
||||
*
|
||||
* Copyright 2002 Sandvine Inc.
|
||||
* All rights reserved.
|
||||
*
|
||||
* Subject to the following obligations and disclaimer of warranty, use and
|
||||
* redistribution of this software, in source or object code forms, with or
|
||||
* without modifications are expressly permitted by Sandvine Inc.;
|
||||
provided,
|
||||
* however, that:
|
||||
* 1. Any and all reproductions of the source or object code must include
|
||||
the
|
||||
* copyright notice above and the following disclaimer of warranties;
|
||||
and
|
||||
* 2. No rights are granted, in any manner or form, to use Sandvine Inc.
|
||||
* trademarks, including the mark "SANDVINE" on advertising,
|
||||
endorsements,
|
||||
* or otherwise except as such appears in the above copyright notice or
|
||||
in
|
||||
* the software.
|
||||
*
|
||||
* THIS SOFTWARE IS BEING PROVIDED BY SANDVINE "AS IS", AND TO THE MAXIMUM
|
||||
* EXTENT PERMITTED BY LAW, SANDVINE MAKES NO REPRESENTATIONS OR
|
||||
WARRANTIES,
|
||||
* EXPRESS OR IMPLIED, REGARDING THIS SOFTWARE, INCLUDING WITHOUT
|
||||
LIMITATION,
|
||||
* ANY AND ALL IMPLIED WARRANTIES OF MERCHANTABILITY, FITNESS FOR A
|
||||
PARTICULAR
|
||||
* PURPOSE, OR NON-INFRINGEMENT. SANDVINE DOES NOT WARRANT, GUARANTEE, OR
|
||||
* MAKE ANY REPRESENTATIONS REGARDING THE USE OF, OR THE RESULTS OF THE
|
||||
* USE OF THIS SOFTWARE IN TERMS OF ITS CORRECTNESS, ACCURACY, RELIABILITY
|
||||
* OR OTHERWISE. IN NO EVENT SHALL SANDVINE BE LIABLE FOR ANY DAMAGES
|
||||
* RESULTING FROM OR ARISING OUT OF ANY USE OF THIS SOFTWARE, INCLUDING
|
||||
* WITHOUT LIMITATION, ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
|
||||
EXEMPLARY,
|
||||
* PUNITIVE, OR CONSEQUENTIAL DAMAGES, PROCUREMENT OF SUBSTITUTE GOODS OR
|
||||
* SERVICES, LOSS OF USE, DATA OR PROFITS, HOWEVER CAUSED AND UNDER ANY
|
||||
* THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
|
||||
* THIS SOFTWARE, EVEN IF SANDVINE IS ADVISED OF THE POSSIBILITY OF SUCH
|
||||
* DAMAGE.
|
||||
*
|
||||
* Author: Dave Chapeskie <dchapeskie@sandvine.com>
|
||||
*
|
||||
* $FreeBSD$
|
||||
*/
|
||||
|
||||
#ifndef _NETGRAPH_SOURCE_H_
|
||||
#define _NETGRAPH_SOURCE_H_
|
||||
|
||||
/* Node type name and magic cookie */
|
||||
#define NG_SOURCE_NODE_TYPE "source"
|
||||
#define NGM_SOURCE_COOKIE 1034346805
|
||||
|
||||
/* Hook names */
|
||||
#define NG_SOURCE_HOOK_INPUT "input"
|
||||
#define NG_SOURCE_HOOK_OUTPUT "output"
|
||||
|
||||
/* Statistics structure returned by NGM_SOURCE_GET_STATS */
|
||||
struct ng_source_stats {
|
||||
u_int64_t outOctets;
|
||||
u_int64_t outFrames;
|
||||
u_int32_t queueOctets;
|
||||
u_int32_t queueFrames;
|
||||
struct timeval startTime;
|
||||
struct timeval endTime;
|
||||
struct timeval elapsedTime;
|
||||
};
|
||||
|
||||
extern const struct ng_parse_type ng_source_timeval_type;
|
||||
/* Keep this in sync with the above structure definition */
|
||||
#define NG_SOURCE_STATS_TYPE_INFO { \
|
||||
{ "outOctets", &ng_parse_uint64_type }, \
|
||||
{ "outFrames", &ng_parse_uint64_type }, \
|
||||
{ "queueOctets", &ng_parse_uint32_type }, \
|
||||
{ "queueFrames", &ng_parse_uint32_type }, \
|
||||
{ "startTime", &ng_source_timeval_type }, \
|
||||
{ "endTime", &ng_source_timeval_type }, \
|
||||
{ "elapsedTime", &ng_source_timeval_type }, \
|
||||
{ NULL } \
|
||||
}
|
||||
|
||||
/* Netgraph commands */
|
||||
enum {
|
||||
NGM_SOURCE_GET_STATS = 1, /* get stats */
|
||||
NGM_SOURCE_CLR_STATS, /* clear stats */
|
||||
NGM_SOURCE_GETCLR_STATS, /* atomically get and clear stats */
|
||||
NGM_SOURCE_START, /* start sending queued data */
|
||||
NGM_SOURCE_STOP, /* stop sending queued data */
|
||||
NGM_SOURCE_CLR_DATA, /* clear the queued data */
|
||||
};
|
||||
|
||||
#endif /* _NETGRAPH_SOURCE_H_ */
|
Loading…
Reference in New Issue
Block a user