Next Previous Contents

4. Example

This section contains annotated example source code. This is just to give some clues, see the full code of the already implemented ebtables modules for more clues.

4.1 Userspace

The userspace ip match module

What follows is annotated code of pieces of the ebt_ip.c code. For brevity, only the --ip-source option is considered.

#include <stdio.h>
#include <getopt.h>
#include "../include/ebtables_u.h"
#include <linux/netfilter_bridge/ebt_ip.h>

At least these includes are needed: respectively for printf(), for the parsing of the options, for all the general ebtables userspace definitions and for the ebtables ip module specific header.

#define IP_SOURCE '1'
#define IP_DEST   '2'

static struct option opts[] =
{
	{ "ip-source"     , required_argument, 0, IP_SOURCE },
	{ "ip-src"        , required_argument, 0, IP_SOURCE },
	{ 0 }
};

Defining the new command line options. This is a struct that the library function getopt_long() understands. The first field specifies the full length option name, the second field specifies if an argument is required or not, the third field should be zero and the last field specifies the value returned by getopt_long() when it encounters this option.

static void print_help()
{
	printf(
"ip options:\n"
"--ip-src    [!] address[/mask]: ip source specification\n"
}

Short and descriptive help.

static void init(struct ebt_entry_match *match)
{
	struct ebt_ip_info *ipinfo = (struct ebt_ip_info *)match->data;

	ipinfo->invflags = 0;
	ipinfo->bitmask = 0;
}

Initialize the module specific data. In the example, some fields in the struct specific to the ip match module are given an initial value.

#define OPT_SOURCE 0x01
static int parse(int c, char **argv, int argc, const struct ebt_u_entry *entry,
   unsigned int *flags, struct ebt_entry_match **match)
{
	struct ebt_ip_info *ipinfo = (struct ebt_ip_info *)(*match)->data;
	char *end;
	long int i;

	switch (c) {
	case IP_SOURCE:
		check_option(flags, OPT_SOURCE);
		ipinfo->bitmask |= EBT_IP_SOURCE;

		if (check_inverse(optarg))
			ipinfo->invflags |= EBT_IP_SOURCE;

		if (optind > argc)
			print_error("Missing IP address argument");
		parse_ip_address(argv[optind - 1], &ipinfo->saddr, &ipinfo->smsk);
		break;

	default:
		return 0;
	}
	return 1;
}

This function parses the option, filling in the match specific struct or exiting on input errors. check_option() makes sure the option isn't specified twice on the command line. check_inverse() checks if the argument equals '!'. optind > argc is possible if the command line input ends with '--ip-source !'.

static void final_check(const struct ebt_u_entry *entry,
   const struct ebt_entry_match *match, const char *name,
   unsigned int hookmask, unsigned int time)
{
	if (entry->ethproto != ETH_P_IP || entry->invflags & EBT_IPROTO)
		print_error("For IP filtering the protocol must be "
		            "specified as IPv4");
}

This function is executed for a rule that uses the (ip) module. When there is an error, it exits using print_error(). entry->ethproto contains the protocol specified by the user. If the protocol wasn't specified, this field will equal zero. The specified protocol is in host endian form, so there is no need for ntohs().

static void print(const struct ebt_u_entry *entry,
   const struct ebt_entry_match *match)
{
	struct ebt_ip_info *ipinfo = (struct ebt_ip_info *)match->data;
	int j;

	if (ipinfo->bitmask & EBT_IP_SOURCE) {
		printf("--ip-src ");
		if (ipinfo->invflags & EBT_IP_SOURCE)
			printf("! ");
		for (j = 0; j < 4; j++)
			printf("%d%s",((unsigned char *)&ipinfo->saddr)[j],
			   (j == 3) ? "" : ".");
		printf("%s ", mask_to_dotted(ipinfo->smsk));
	}
}

Print out the data contained in the match in a form that the user could have used on the command line. End with at least one space.

static int compare(const struct ebt_entry_match *m1,
   const struct ebt_entry_match *m2)
{
	struct ebt_ip_info *ipinfo1 = (struct ebt_ip_info *)m1->data;
	struct ebt_ip_info *ipinfo2 = (struct ebt_ip_info *)m2->data;

	if (ipinfo1->bitmask != ipinfo2->bitmask)
		return 0;
	if (ipinfo1->invflags != ipinfo2->invflags)
		return 0;
	if (ipinfo1->bitmask & EBT_IP_SOURCE) {
		if (ipinfo1->saddr != ipinfo2->saddr)
			return 0;
		if (ipinfo1->smsk != ipinfo2->smsk)
			return 0;
	}
	return 1;
}

Compares the data of two "instances" of the match module, returning 1 if the data is equivalent, else 0.

static struct ebt_u_match ip_match =
{
	EBT_IP_MATCH,
	sizeof(struct ebt_ip_info),
	print_help,
	init,
	parse,
	final_check,
	print,
	compare,
	opts
};

The struct used to register the match to the core ebtables userspace code.

static void _init(void) __attribute((constructor));
static void _init(void)
{
	register_match(&ip_match);
}

The init function registers the match. Initializing its own data should be done with the init() function defined int the above struct.

The (userspace) dnat final_check() function

static void final_check_d(const struct ebt_u_entry *entry,
   const struct ebt_entry_target *target, const char *name,
   unsigned int hookmask, unsigned int time)
{
	struct ebt_nat_info *natinfo = (struct ebt_nat_info *)target->data;

	if (BASE_CHAIN && natinfo->target == EBT_RETURN)
		print_error("--dnat-target RETURN not allowed on base chain");
	CLEAR_BASE_CHAIN_BIT;
	if (((hookmask & ~((1 << NF_BR_PRE_ROUTING) | (1 << NF_BR_LOCAL_OUT)))
	   || strcmp(name, "nat")) &&
	   ((hookmask & ~(1 << NF_BR_BROUTING)) || strcmp(name, "broute")))
		print_error("Wrong chain for dnat");
	if (time == 0 && to_dest_supplied == 0)
		print_error("No dnat address supplied");
}

The target returned by the dnat kernel module (see the man page) is contained in the target field of the module's specific struct (struct ebt_nat_info). First we check that this target isn't RETURN on one of the standard (base) chains. Then we make hookmask ready for direct use by using the CLEAR_BASE_CHAIN_BIT macro. Next is checked if the rule containing this "module instance" is accessible through illegal chains or tables. Finally, the argument time is checked. If it equals zero, the function checks to be sure a destination IP address was specified. to_dest_supplied is a static variable of this ebtables userspace module, initialized to zero by its initialization function.

4.2 Kernel

The ebtables kernel ip match module

What follows is annotated code of pieces of the ebt_ip.c code. For brevity, only the IP source address filtering is considered. As the interface towards the main ebtables code is easy, there is really not much to be said here.

#include <linux/netfilter_bridge/ebtables.h>
#include <linux/netfilter_bridge/ebt_ip.h>
#include <linux/ip.h>
#include <linux/module.h>

We need general ebtables definitions, the ip kernel match module's specific data, the definition of the IP header and some module definitions.

static int ebt_filter_ip(const struct sk_buff *skb, const struct net_device *in,
   const struct net_device *out, const void *data,
   unsigned int datalen)
{
	struct ebt_ip_info *info = (struct ebt_ip_info *)data;

	if (info->bitmask & EBT_IP_SOURCE &&
	   FWINV((((*skb).nh.iph)->saddr & info->smsk) !=
	   info->saddr, EBT_IP_SOURCE))
		return EBT_NOMATCH;
	return EBT_MATCH;
}

This is the filtering function of the ip match module, it is executed for every frame that comes into contact with an ebtables rule that uses the ip match. All it does is tell the ebtables main code if the frame matches or not.

static int ebt_ip_check(const char *tablename, unsigned int hookmask,
   const struct ebt_entry *e, void *data, unsigned int datalen)
{
	struct ebt_ip_info *info = (struct ebt_ip_info *)data;

	if (datalen != sizeof(struct ebt_ip_info))
		return -EINVAL;
	if (e->ethproto != __constant_htons(ETH_P_IP) ||
	   e->invflags & EBT_IPROTO)
		return -EINVAL;
	if (info->bitmask & ~EBT_IP_MASK || info->invflags & ~EBT_IP_MASK)
		return -EINVAL;
	return 0;
}

This function is executed for every rule that uses the ip match, when the kernel receives new table data. It needs to make sure no corrupt ip match data is accepted.

static struct ebt_match filter_ip =
{
	{NULL, NULL}, EBT_IP_MATCH, ebt_filter_ip, ebt_ip_check, NULL,
	THIS_MODULE
};

The struct we'll give to the main ebtables code to register the match.

static int __init init(void)
{
	return ebt_register_match(&filter_ip);
}

static void __exit fini(void)
{
	ebt_unregister_match(&filter_ip);
}

module_init(init);
module_exit(fini);

Functions executed at loading or removing of the ip match module.

MODULE_LICENSE("GPL");

Ofcourse your module is released under the GPL.

The ebtables kernel filter table

This part contains pieces of the ebtable_filter.c code with annotation.

#define FILTER_VALID_HOOKS ((1 << NF_BR_LOCAL_IN) | (1 << NF_BR_FORWARD) | \
   (1 << NF_BR_LOCAL_OUT))

The valid netfilter hooks for the ebtables filter table are the bridge LOCAL_IN FORWARD and LOCAL_OUT hooks.

static struct ebt_entries initial_chains[] =
{
  {0, "INPUT", 0, EBT_ACCEPT, 0},
  {0, "FORWARD", 0, EBT_ACCEPT, 0},
  {0, "OUTPUT", 0, EBT_ACCEPT, 0}
};

The filter table consists of three chains, initially containing zero rules and having policy ACCEPT.

static struct ebt_replace initial_table =
{
  "filter", FILTER_VALID_HOOKS, 0, 3 * sizeof(struct ebt_entries),
  { [NF_BR_LOCAL_IN]&initial_chains[0], [NF_BR_FORWARD]&initial_chains[1],
    [NF_BR_LOCAL_OUT]&initial_chains[2] }, 0, NULL, (char *)initial_chains
};

This contains all the info about the table.

static int check(const struct ebt_table_info *info, unsigned int valid_hooks)
{
	if (valid_hooks & ~FILTER_VALID_HOOKS)
		return -EINVAL;
	return 0;
}

This function is executed when new table data is given to the kernel. We just check the valid hooks according to userspace are the same as those according to the kernel module.

static struct ebt_table frame_filter =
{ 
  {NULL, NULL}, "filter", &initial_table, FILTER_VALID_HOOKS, 
  RW_LOCK_UNLOCKED, check, NULL
};

The ebtables main code likes to use this struct.

static unsigned int
ebt_hook (unsigned int hook, struct sk_buff **pskb, const struct net_device *in,
   const struct net_device *out, int (*okfn)(struct sk_buff *))
{
	return ebt_do_table(hook, pskb, in, out, &frame_filter);
}

This function is executed for every frame that passes a netfilter hook on which this function is registered.

static struct nf_hook_ops ebt_ops_filter[] = {
	{ { NULL, NULL }, ebt_hook, PF_BRIDGE, NF_BR_LOCAL_IN,
	   NF_BR_PRI_FILTER_BRIDGED},
	{ { NULL, NULL }, ebt_hook, PF_BRIDGE, NF_BR_FORWARD,
	   NF_BR_PRI_FILTER_BRIDGED},
	{ { NULL, NULL }, ebt_hook, PF_BRIDGE, NF_BR_LOCAL_OUT,
	   NF_BR_PRI_FILTER_OTHER}
};

Here we say the function ebt_hook() is registered onto the three mentioned netfilter hooks, with a certain priority (e.g. NF_BR_PRI_FILTER_BRIDGED). If multiple functions are registered on the same hook, the priority of the functions determines the order in which they are executed. The lower the priority value, the earlier the function will get executed.

static int __init init(void)
{
	int i, j, ret;

	ret = ebt_register_table(&frame_filter);
	if (ret < 0)
		return ret;
	for (i = 0; i < sizeof(ebt_ops_filter) / sizeof(ebt_ops_filter[0]); i++)
		if ((ret = nf_register_hook(&ebt_ops_filter[i])) < 0)
			goto cleanup;
	return ret;
cleanup:
	for (j = 0; j < i; j++)
		nf_unregister_hook(&ebt_ops_filter[j]);
	ebt_unregister_table(&frame_filter);
	return ret;
}

register the table to the main ebtables code; register ebt_hook() on the appropriate netfilter hooks.

static void __exit fini(void)
{
	int i;

	for (i = 0; i < sizeof(ebt_ops_filter) / sizeof(ebt_ops_filter[0]); i++)
		nf_unregister_hook(&ebt_ops_filter[i]);
	ebt_unregister_table(&frame_filter);
}

Unregister from the netfilter hooks and ebtables.


Next Previous Contents