Jun 2011

C Program with Registered Modules: dnet

Many programs come with modules that can registered and loaded. Some are on demand, others compiled in while still others are precompiled and can be loaded on demand (several Operating System kernels come to mind that have such a capability). In this text, an example of a program that allows a module to be written and compiled onto a program with relative ease. The example program is the dnet test program which ships with libdnet written by Dug Song.

What does dnet Do?

The dnet test program is used as a sort of regression tool for libdnet itself. Many have found dnet to be useful as a diagnostic tool as well. The rocks cluster ships with dnet for example.

The Module Header and Data Structure

Following is the mod.h file:

/*
 * mod.h
 *
 * Copyright (c) 2002 Dug Song <dugsong@monkey.org>
 *
 * $Id: mod.h 316 2002-03-29 06:07:27Z dugsong $
 */

#ifndef MOD_H
#define MOD_H

struct mod {
    char    *name;
    int  type;
    int (*main)(int argc, char *argv[]);
};

/*
 * Module types
 */
#define MOD_TYPE_DATA   0x01    /* generate data */
#define MOD_TYPE_ENCAP  0x02    /* encapsulate data */
#define MOD_TYPE_XMIT   0x04    /* send datagrams */
#define MOD_TYPE_KERN   0x08    /* kernel network info */

#endif /* MOD_H */

First the module structure:

struct mod {
    char    *name;
    int  type;
    int (*main)(int argc, char *argv[]);
};

The first two variables are simple. The string name of the module and the type (which is defined later). The third variable is a pointer to the entrance of execution of the module. In a sense, it is almost easier to think of the third variable as the main() routine of the module, for that is in fact precisely what it is. Following are the currently supported module types by number:

/*
 * Module types
 */
#define MOD_TYPE_DATA   0x01    /* generate data */
#define MOD_TYPE_ENCAP  0x02    /* encapsulate data */
#define MOD_TYPE_XMIT   0x04    /* send datagrams */
#define MOD_TYPE_KERN   0x08    /* kernel network info */

It may not be clear at this point, but one of the interesting points about this method is that new additions can be bolted onto the existing program without too much fuss.

The dnet.c Source

Following is the source code for the main portion of the dnet program:

/*
 * dnet.c
 *
 * Copyright (c) 2001 Dug Song <dugsong@monkey.org>
 *
 * $Id: dnet.c 317 2002-03-29 06:07:49Z dugsong $
 */

#include config.h

#include <sys/types.h>

#include <err.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>

#include dnet.h
#include mod.h

/*
 * XXX - new modules should be registered here
 */
extern struct mod mod_addr;
extern struct mod mod_hex;
extern struct mod mod_rand;
extern struct mod mod_eth;
extern struct mod mod_arp;
extern struct mod mod_ip;
extern struct mod mod_icmp;
extern struct mod mod_tcp;
extern struct mod mod_udp;
extern struct mod mod_send;
extern struct mod mod_fw;
extern struct mod mod_intf;
extern struct mod mod_route;
static struct mod *modules[] = {
    &mod_addr, &mod_hex, &mod_rand, &mod_eth, &mod_arp, &mod_ip, &mod_icmp,
    &mod_tcp, &mod_udp, &mod_send, &mod_fw, &mod_intf, &mod_route, NULL
};

static void
print_modules(int type, char *string)
{
    struct mod **m;
    int i;

    fprintf(stderr, "%s commands:\n", string);
    for (i = 1, m = modules; *m != NULL; m++) {
        if ((m[0]->type & type) != 0) {
            fprintf(stderr, "%-10s", m[0]->name);
            if ((i++ % 8) == 0)
                fprintf(stderr, "\n");
        }
    }
    fprintf(stderr, "\n\n");
}

static void
print_usage(void)
{
    fprintf(stderr, "Usage: dnet <command> <args> ...\n\n");

    print_modules(MOD_TYPE_DATA, "Payload generation");
    print_modules(MOD_TYPE_ENCAP, "Packet encapsulation");
    print_modules(MOD_TYPE_XMIT, "Packet transmission");
    print_modules(MOD_TYPE_KERN, "Kernel interface");
}

static int
do_command(int argc, char *argv[])
{
    struct mod **m;

    for (m = modules; *m != NULL; m++) {
        if (strcmp(argv[0], m[0]->name) == 0)
            return (m[0]->main(argc, argv));
    }
    return (-1);
}

int
main(int argc, char *argv[])
{
    if (argc < 2) {
        print_usage();
        exit(1);
    }
    if (do_command(argc - 1, argv + 1) < 0) {
        print_usage();
        exit(1);
    }
    exit(0);
}

Well... that is a handful. So let us factor it down by part. Forgetting the includes, first up are the module declarations:

Modules Declarations and Array

/*
 * XXX - new modules should be registered here
 */
extern struct mod mod_addr;
extern struct mod mod_hex;
extern struct mod mod_rand;
extern struct mod mod_eth;
extern struct mod mod_arp;
....

Using the extern keyword we make the program aware of the module structures that are in other source files. Next each module is pointed to in a modules array that is NULL terminated:

    &mod_addr, &mod_hex, &mod_rand, &mod_eth, &mod_arp, &mod_ip, &mod_icmp,
    &mod_tcp, &mod_udp, &mod_send, &mod_fw, &mod_intf, &mod_route, NULL
};

The next two functions are helpers. One prints out module information while the other one prints a usage message (which in turn calls the module print function):

static void
print_modules(int type, char *string)
{
    struct mod **m;
    int i;

    fprintf(stderr, "%s commands:\n", string);
    for (i = 1, m = modules; *m != NULL; m++) {
        if ((m[0]->type & type) != 0) {
            fprintf(stderr, "%-10s", m[0]->name);
            if ((i++ % 8) == 0)
                fprintf(stderr, "\n");
        }
    }
    fprintf(stderr, "\n\n");
}

static void
print_usage(void)
{
    fprintf(stderr, "Usage: dnet <command> <args> ...\n\n");

    print_modules(MOD_TYPE_DATA, "Payload generation");
    print_modules(MOD_TYPE_ENCAP, "Packet encapsulation");
    print_modules(MOD_TYPE_XMIT, "Packet transmission");
    print_modules(MOD_TYPE_KERN, "Kernel interface");
}

Note in the print_modules() function that the NULL terminator is put to good use.

Main and Do-it!

Jumping ahead here is the main():

int
main(int argc, char *argv[])
{
    if (argc < 2) {
        print_usage();
        exit(1);
    }
    if (do_command(argc - 1, argv + 1) < 0) {
        print_usage();
        exit(1);
    }
    exit(0);
}

Make sure the syntax is correct..ish; then pass on the command to the function do_command():

static int
do_command(int argc, char *argv[])
{
    struct mod **m;

    for (m = modules; *m != NULL; m++) {
        if (strcmp(argv[0], m[0]->name) == 0)
            return (m[0]->main(argc, argv));
    }
    return (-1);
}

At first glance, that all might look a little strange. So going line by line:

1   struct mod **m;
2   for (m = modules; *m != NULL; m++) {
3       if (strcmp(argv[0], m[0]->name) == 0)
            return (m[0]->main(argc, argv)); }
4   return (-1);
  1. Setup a deep pointer to the mod structure.
  2. Until we hit the NULL terminator spin through modules.
  3. If the command matches the module name then execute a call to the modules main() with the argument values.
  4. Otherwise ... woopsee!

Summary

Based on how Dug setup C modules in dnet we can easily see how we roll our own.