Vde switch plugins
VDE switches support plugins.
Packet processing like dumping or filtering can be implemented by plugins.
The API to create VDE plugins is described in the file vdeplugin.h, the source code hierarchy provide some examples of plugins in the direrectory src/vde_switch/plugins/.
A plugin is a dynamic library. Its constructor must initialize plugin's features. A destructor must be provided for cleaning up plugin's data structures. A vde plugin must define a struct plugin variable named vde_plugin_data.
struct plugin vde_plugin_data={ .name="test", .help="a simple plugin for vde", };
VDE plugins use the event driven paradigm. Functions can be activated by commands or by switch related events.
A plugin can add its own management commands. All the plugin commands (or menu definition) must be defined in a struct comlist array, loaded by the macro ADDCL and unloaded by DELCL. The following example is a simple (useless) plugin. It manages an integer value, the user can store a new value or print the current value.
#define _GNU_SOURCE #include <stdio.h> #include <stdlib.h> #include <vdeplugin.h> int testglobal; struct plugin vde_plugin_data={ .name="test", .help="a simple plugin for vde", }; static int testvalue(int val) { testglobal=val; return 0; } static int testprint(FILE *f) { fprintf(f,"Test Plugin value %d\n",testglobal); return 0; } static struct comlist cl[]={ {"test","============","test plugin",NULL,NOARG}, {"test/change","N","change value",testvalue,INTARG}, {"test/print","","change value",testprint,WITHFILE}, }; static void __attribute__ ((constructor)) init (void) { ADDCL(cl); } static void __attribute__ ((destructor)) fini (void) { DELCL(cl); }
The fields of struct comlinst are:
- the command path
- a systax help (============ for the menu definition)
- a description
- the pointer to the implementation function
- a flag field, implementation functions have different arguments depending on tags.
NOARG (or 0),INTARG and STRARG are used to forward the command parametes to the implementation function. INTARG means that there is one integer parameter, with STRARG all the characters up to the end of line get passed as a string. It is up to the implementation function parsing the syntax of the parameters and splitting multiple parameters. The last argument of the implementation function must be an int for INTFUN and a char * for a STRARG. If a function need to return a long output then WITHFILE must be set. The first argument of the implementation function is a FILE * variable in this case, this (virtual) file is used to write the output that is sent at the end of the function using the 0000 DATA END WITH '.' rule of the management protocol. All the output is buffered and sent when the implementation function exits to avoid interleaving problems with debug outputs. The use of WITHFD is rare. When this flag is set the file descriptor of the management session is passed to the implementation function as a parameter (after the WITHFILE argument and before the parameter INTARG or STRARG). This integer file descriptor should be never used to send or receive data (it would cause interleaving problems and protocol misalignments) but it can be used to identify the connection.
The example above can be compiled as a shared library:
gcc -shared -o testplugin.so testplugin.c
and loaded in a running vde_switch by the command plugin/add:
vde$ plugin/add ./testplugin.so
(in this case the plugin is in the working directory of the switch, otherwise use the complete path or just testplugin.so if the library is in a standard directory or in a directory named in LD_LIBRARY_PATH.)
vde$ plugin/list 0000 DATA END WITH '.' NAME HELP ------------ ---- test a simple plugin for vde . 1000 Success
vde$ help 0000 DATA END WITH '.' COMMAND PATH SYNTAX HELP ------------ -------------- ------------ ..... test ============ test plugin test/change N change value test/print change value . 1000 Success
The plugin has been loaded and the its commands are available. Let us try.
vde$ test/print 0000 DATA END WITH '.' Test Plugin value 0 . 1000 Success vde$ test/change 42 1000 Success vde$ test/print 0000 DATA END WITH '.' Test Plugin value 42 . 1000 Success
A plugin can also subscribe for switch event notifications. These functions are used by plugins to subscribe or unsubscribe event notifications:
int eventadd(int (*fun)(struct dbgcl *event,void *arg,va_list v),char *path,void *arg); int eventdel(int (*fun)(struct dbgcl *event,void *arg,va_list v),char *path,void *arg);
An event causes fun function to be called. The vararg parameters depends on the event. Vde_switch defines the following events:
PATH FLAGS VARARG PARAMETERS ------------------------------------------------------------------------- port/+ D_PORT|D_PLUS int portno port/- D_PORT|D_MINUS int portno port/descr D_PORT|D_DESCR int portno, int fd, char * descr port/ep/+ D_EP|D_PLUS int portno, int fd port/ep/- D_EP|D_MINUS int portno, int fd packet/in D_PACKET|D_IN int portno, char *packet, int len packet/out D_PACKET|D_OUT int portno, char *packet, int len hash/+ D_HASH|D_PLUS char *extmac hash/- D_HASH|D_MINUS char *extmac fstp/status D_FSTP|D_STATUS int portno, int vlan, int status fstp/root D_FSTP|D_ROOT int portno, int vlan, char *extmac fstp/+ D_FSTP|D_PLUS int portno, int vlan fstp/- D_FSTP|D_MINUS int portno, int vlan
The path argument for eventadd/eventdel is the kind of event the plugin need to handle as listed in the first column of the table above. If the path is just a prefix, all the events matching the prefix gets subscribed. The arg parameter is an opaque argument passed to the function (to keep the internal state of the plugin). The packet/in and packet/out event management functions can drop packets and/or change the packet contents, this is the way to implement packet filtering plugins. For packet/{in,out} management function (the fun passed to eventadd) the return value is the length of the packet. If the return value is less or equal to zero the packet is dropped. It is also possible to rewrite the packet: the buffer is large enough to store MTU bytes long packets.
Plugins can also register their own debug/event items. Each item is described by a struct dbgcl element of an array. The method to register or unregister debug/event items is similar to what has been described for commands above. The plugin should define the following fields of struct dbgcl:
- path: the path of the event,
- help: the comment line shown by debug/list, if help==NULL it is just an event item for other plugins, it cannot be directly used by the management interface (link built in packet/in, packet/out).
- tag: a numerical tag to speed up the discovery of the event type (to avoid strcmp).
When a plugin needs to send an avent notification it uses:
EVENTOUT(CL, ...)
for a debugging output:
DBGOUT(CL, FORMAT, ...)
where CL is the struct dbgcl item. DBGOUT has a similar syntax of fprintf, while the signature of EVENTOUT is open, the sequence of parameters must match those retrieved by the event management function of the client plugin. EVENTOUT should never include newline chars ('\n') and should be called once per notification.
The following code (dump.c) uses a combination of all the support descibed above. This plugin implements a simple hexadecimal packet dumping.
#define _GNU_SOURCE #include <stdio.h> #include <stdlib.h> #include <vdeplugin.h> static int testevent(struct dbgcl *tag,void *arg,va_list v); static int dump(char *arg); struct plugin vde_plugin_data={ .name="dump", .help="dump packets", }; static struct comlist cl[]={ {"dump","============","DUMP Packets",NULL,NOARG}, {"dump/active","0/1","start dumping data",dump,STRARG}, }; #define D_DUMP 0100 static struct dbgcl dl[]= { {"dump/packetin","dump incoming packet",D_DUMP|D_IN}, {"dump/packetout","dump outgoing packet",D_DUMP|D_OUT}, }; static int dump(char *arg) { int active=atoi(arg); int rv; if (active) rv=eventadd(testevent,"packet",dl); else rv=eventdel(testevent,"packet",dl); return 0; } static int testevent(struct dbgcl *event,void *arg,va_list v) { struct dbgcl *this=arg; switch (event->tag) { case D_PACKET|D_OUT: this++; case D_PACKET|D_IN: { int port=va_arg(v,int); unsigned char *buf=va_arg(v,unsigned char *); int len=va_arg(v,int); char *pktdump; size_t dumplen; FILE *out=open_memstream(&pktdump,&dumplen); if (out) { int i; fprintf(out,"Pkt: Port %04d len=%04d ", port, len); for (i=0;i<len;i++) fprintf(out,"%02x ",buf[i]); fclose(out); DBGOUT(this, "%s",pktdump); free(pktdump); } } } return 0; } static void __attribute__ ((constructor)) init (void) { ADDCL(cl); ADDDBGCL(dl); } static void __attribute__ ((destructor)) fini (void) { DELCL(cl); DELDBGCL(dl); }