A Simple Example Daemon
[Manual]
This module provides an example daemon based on DES-SERT and explains the most important parts. More...
![]() |
This module provides an example daemon based on DES-SERT and explains the most important parts.
The Essentials
For the most barebone implementation, let us consider it our "Hello World", you just need to include the headers of dessert and libcli and have provide a common C main function.
To make it a real DES-SERT daemon dessert_init() has to be called with a four byte name of the protocol, an 8 bit version number, and some option flags, e.g., to daemonize the program. dessert_run() start the main thread of the daemon.
The CLI should also usually be initialized by init_cli() and started with dessert_cli_run().
#include <dessert.h> #include <libcli.h> int main (int argc, char** argv) { dessert_init("expl", 0x05, DESSERT_OPT_DAEMONIZE); init_cli(); dessert_cli_run(); dessert_run(); return (0); }
Adding Interfaces
Right now the daemon cannot receive or send any packets. Interfaces have to be added. For this task DES-SERT already provides particular CLI commands that just have to be registered.
We register the dessert_cli_cmd_addsysif() and dessert_cli_cmd_addmeshif() functions at the dessert_cli_cfg_iface anchor. All commands registered with this anchor have to be prefixed by the word "interface" in the the CLI.
cli_register_command(dessert_cli, dessert_cli_cfg_iface, "sys", dessert_cli_cmd_addsysif, PRIVILEGE_PRIVILEGED, MODE_CONFIG, "initialize sys interface"); cli_register_command(dessert_cli, dessert_cli_cfg_iface, "mesh", dessert_cli_cmd_addmeshif, PRIVILEGE_PRIVILEGED, MODE_CONFIG, "initialize mesh interface");
dessert_cli_cfg_iface is registered by dessert_init as a root anchor that is usable in the privileged mode. libcli is inspired by the Cisco IOS command line interface and provides the following modes:
- User Mode
- Privileged Mode
- global configuration mode
You should read the libcli documentation but for know it suffices to know that you can enter privileged mode with "enable" and global configuration mode with "configure terminal" when you are connected with telnet.
Now you can use telnet to connect to the compile daemon and add sys and mesh interfaces as follows:
interface sys tap0 10.0.0.1 255.255.255.0 interface mesh eth0
Most often you will not configure the interfaces by hand but let it be configured automatically (see How to use DES-SERT based Daemons and Parsing Configuration Files).
Handling Packets
Although the daemon can receive packets from the network (mesh) interfaces or the TAP (sys) interfase, they are not handled and thus dropped. We have to add at least a callback function that sends packets that are received via the TAP interface over all registered network interfaces and a function that sends packets received from the network interfaces to the user space via the TAP interface. sys and mesh callbacks have to have the prototypes dessert_sysrxcb_t and dessert_meshrxcb_t. In this example the sys callback is named toSys and the mesh callback toMesh.
The mesh callback gets a pointer to the DES-SERT message that already contains the received Ethernet frame. The message is send with dessert_meshsend() over all registered mesh interfaces because the interface parameter is NULL. The callback returns DESSERT_MSG_DROP and signals DES-SERT to delete the message and free the memory.
int toMesh(dessert_msg_t* msg, size_t len, dessert_msg_proc_t *proc, dessert_sysif_t *tunif, dessert_frameid_t id) { dessert_meshsend(msg, NULL); return DESSERT_MSG_DROP; }
The sys callback gets a pointer to the DES-SERT message that was received over the network. The iface parameter is a pointer to the dessert_meshif_t that actually received the DES-SERT message. In this function we assume that the dessert_msg_ifaceflags_cb() callback was registered with higher priority than toSys. Thus the proc pointer will point to a valid processing buffer and the lflags can be used to differentiate message types. When the message is destined to the daemon that received the message and if it is not a broadcast or multicast, than the Ethernet frame is decapsulated and send to the TAP interface via dessert_syssend(). DESSERT_MSG_KEEP is returned to let lower priority callbacks continue to handle the DES-SERT message. This it not the case in this example but you could dump all messages to the log in another sys callback function.
int toSys(dessert_msg_t* msg, size_t len, dessert_msg_proc_t *proc, const dessert_meshif_t *iface, dessert_frameid_t id) { struct ether_header *eth; size_t eth_len; if (proc->lflags & DESSERT_LFLAG_DST_SELF || proc->lflags & DESSERT_LFLAG_DST_BROADCAST || proc->lflags & DESSERT_LFLAG_DST_MULTICAST ) { eth_len = dessert_msg_ethdecap(msg, ð); dessert_syssend(eth, eth_len); free(eth); } return DESSERT_MSG_KEEP; }
Now you can send a packet from host A to host B when they have a link to each other.
Extensions
Consider we want to piggyback some data when packets are received from the sys interface and send over the network. First of all we should create an enumeration as follows:
enum extensions { EXAMPLE_EXT_1 = DESSERT_EXT_USER, EXAMPLE_EXT_2 };
The first entry gets the first value that can be assigned as type value to user provided extensions. Extension types are specific to a particular protocol therefore you can use any value => DESSERT_EXT_USER as you like.
Extensions should normally contain a C-struct. Always use the __packed__ attribute or you might otherwise send more bytes than necessary over the medium.
typedef struct __attribute__((__packed__)) _my_extension { uint8_t value1; uint16_t value2; char char1; } my_extension;
The extension can be added in any callback. In this example we add it to messages in the toMesh callback.
my_extension *mext = NULL; dessert_ext_t *ext = NULL; dessert_msg_addext(msg, &ext, EXAMPLE_EXT_1, sizeof(my_extension)); mext = (my_extension*) &(ext->data); mext->value1 = 123; mext->value2 = 45678; mext->char1 = 'X';
dessert_msg_addext() adds a message to a DES-SERT message with the specified type and size and returns a pointer to the allocated memory via the second parameter (&ext in this example).
Parsing Configuration Files
Last but not least, we probably want to enable the parsing of a configuration file. dessert_cli_get_cfg() tries to open the file provided as parameter to the daemon and if this fail tries to open /etc/DAEMON_NAME.conf. cli_file() parses the file. You should include an assert to check whether cfg is a NULL pointer.
FILE *cfg = dessert_cli_get_cfg(argc, argv); cli_file(dessert_cli, cfg, PRIVILEGE_PRIVILEGED, MODE_CONFIG);
What You Did Not Learn
Because this was a very basic introduction in how to implement daemons with DES-SERT, some topics are available in other modules:
- Removing Registered Interfaces
- multiple callbacks in the sys and mesh pipeline
- advanced usage of the processsing buffer
- Message Size Management
- Custom CLI Commands
- Using CLI passwords
- Using TUN Interfaces
- forwarding and routing
- retrieving DES-SERT extensions
- writing Wireshark dissectors
- providing OIDs via AgentX to an SNMP agent
If there is not link to a topic this information will be added to the documentation in the later releases.
The Complete Example
#include <stdio.h> #include <string.h> #include <dessert.h> #include <libcli.h> struct cli_command *cli_cfg_set; typedef struct __attribute__((__packed__)) _my_extension { uint8_t value1; uint16_t value2; char char1; } my_extension; enum extensions { EXAMPLE_EXT_1 = DESSERT_EXT_USER, EXAMPLE_EXT_2 }; void init_cli() { cli_register_command(dessert_cli, dessert_cli_cfg_iface, "sys", dessert_cli_cmd_addsysif, PRIVILEGE_PRIVILEGED, MODE_CONFIG, "initialize sys interface"); cli_register_command(dessert_cli, dessert_cli_cfg_iface, "mesh", dessert_cli_cmd_addmeshif, PRIVILEGE_PRIVILEGED, MODE_CONFIG, "initialize mesh interface"); } int toMesh(dessert_msg_t* msg, size_t len, dessert_msg_proc_t *proc, dessert_sysif_t *tunif, dessert_frameid_t id) { my_extension *mext = NULL; dessert_ext_t *ext = NULL; dessert_msg_addext(msg, &ext, EXAMPLE_EXT_1, sizeof(my_extension)); mext = (my_extension*) &(ext->data); mext->value1 = 123; mext->value2 = 45678; mext->char1 = 'X'; dessert_meshsend(msg, NULL); return DESSERT_MSG_DROP; } int toSys(dessert_msg_t* msg, size_t len, dessert_msg_proc_t *proc, const dessert_meshif_t *iface, dessert_frameid_t id) { struct ether_header *eth; size_t eth_len; if ( proc->lflags & DESSERT_LFLAG_DST_SELF || proc->lflags & DESSERT_LFLAG_DST_BROADCAST || proc->lflags & DESSERT_LFLAG_DST_MULTICAST ) { eth_len = dessert_msg_ethdecap(msg, ð); dessert_syssend(eth, eth_len); free(eth); } return DESSERT_MSG_KEEP; } int main (int argc, char** argv) { FILE *cfg = dessert_cli_get_cfg(argc, argv); dessert_init("expl", 0x05, DESSERT_OPT_DAEMONIZE); dessert_logcfg(DESSERT_LOG_DEBUG|DESSERT_LOG_STDERR); dessert_sysrxcb_add(toMesh, 100); dessert_meshrxcb_add(dessert_msg_ifaceflags_cb, 15); dessert_meshrxcb_add(toSys, 100); init_cli(); cli_file(dessert_cli, cfg, PRIVILEGE_PRIVILEGED, MODE_CONFIG); dessert_cli_run(); dessert_run(); return (0); }