diff mbox

extensions: add libxt_bpf extension

Message ID 1357776727-28547-1-git-send-email-willemb@google.com
State Superseded
Headers show

Commit Message

Willem de Bruijn Jan. 10, 2013, 12:12 a.m. UTC
Changes:
- v2
  - update to match latest kernel module (fixed size match struct)
  - add manual page
  - fix save/restore
  - fix matching whole program on delete

Support filtering using Linux Socket Filters
---
 extensions/libxt_bpf.c   |  199 ++++++++++++++++++++++++++++++++++++++++++++++
 extensions/libxt_bpf.man |   17 ++++
 2 files changed, 216 insertions(+), 0 deletions(-)
 create mode 100644 extensions/libxt_bpf.c
 create mode 100644 extensions/libxt_bpf.man

Comments

Pablo Neira Ayuso Jan. 21, 2013, 12:19 p.m. UTC | #1
On Wed, Jan 09, 2013 at 07:12:07PM -0500, Willem de Bruijn wrote:
> Changes:
> - v2
>   - update to match latest kernel module (fixed size match struct)
>   - add manual page
>   - fix save/restore
>   - fix matching whole program on delete
> 
> Support filtering using Linux Socket Filters
> ---
>  extensions/libxt_bpf.c   |  199 ++++++++++++++++++++++++++++++++++++++++++++++
>  extensions/libxt_bpf.man |   17 ++++
>  2 files changed, 216 insertions(+), 0 deletions(-)
>  create mode 100644 extensions/libxt_bpf.c
>  create mode 100644 extensions/libxt_bpf.man
> 
> diff --git a/extensions/libxt_bpf.c b/extensions/libxt_bpf.c
> new file mode 100644
> index 0000000..8ad540f
> --- /dev/null
> +++ b/extensions/libxt_bpf.c
> @@ -0,0 +1,199 @@
> +/*
> + * Xtables BPF extension
> + *
> + * Written by Willem de Bruijn (willemb@google.com)
> + * Copyright Google, Inc. 2013
> + * Licensed under the GNU General Public License version 2 (GPLv2)
> +*/
> +
> +#include <linux/netfilter/xt_bpf.h>
> +#include <errno.h>
> +#include <fcntl.h>
> +#include <stdio.h>
> +#include <stdlib.h>
> +#include <string.h>
> +#include <sys/stat.h>
> +#include <sys/types.h>
> +#include <unistd.h>
> +#include <xtables.h>
> +
> +#define BCODE_FILE_MAX_LEN_B	1024

This has to be updated now that this sticks to 64 instructions per
filter.

> +
> +enum { O_BCODE_STDIN = 0,
> +       O_BCODE_FILE};

Better:

enum {
        O_...
        O_...
};

> +
> +static void bpf_help(void)
> +{
> +	printf(
> +"bpf match options:\n"
> +"--bytecode-file <file>	: a bpf program as generated by\n"
> +"  `tcpdump -i any -ddd <expr> > file\n"
> +"--bytecode <program>	: a bpf program as generated by\n"
> +"  `tcpdump -i any -ddd <expr> | tr '\\n' ','`\n");
> +}
> +
> +static const struct xt_option_entry bpf_opts[] = {
> +	{.name = "bytecode", .id = O_BCODE_STDIN, .type = XTTYPE_STRING},
> +	{.name = "bytecode-file", .id = O_BCODE_FILE, .type = XTTYPE_STRING},
> +	XTOPT_TABLEEND,
> +};
> +
> +static void bpf_parse_string(struct xt_option_call *cb, const char *bpf_program,
> +			     const char separator)
> +{
> +	struct xt_bpf_info *bi = (void *) cb->data;
> +	const char *token;
> +	char sp;
> +	int i;
> +
> +	/* parse head: length. */
> +	if (sscanf(bpf_program, "%hu%c", &bi->bpf_program_num_elem, &sp) != 2 ||
> +		   sp != separator)
> +		xtables_error(PARAMETER_PROBLEM,
> +			      "bpf: error parsing program length");
> +	if (!bi->bpf_program_num_elem)
> +		xtables_error(PARAMETER_PROBLEM,
> +			      "bpf: illegal zero length program");
> +	if (bi->bpf_program_num_elem > XT_BPF_MAX_NUM_INSTR)
> +		xtables_error(PARAMETER_PROBLEM,
> +			      "bpf: number of instructions exceeds maximum");
> +
> +	/* parse instructions. */
> +	i = 0;
> +	token = bpf_program;
> +	while ((token = strchr(token, separator)) && (++token)[0]) {
> +		if (i >= bi->bpf_program_num_elem) {
> +			xtables_error(PARAMETER_PROBLEM,
> +				      "bpf: program length: parameter");

better error reporting?

> +			return;

That return is superfluous. xtables_error calls exit.

> +		}
> +		if (sscanf(token, "%hu %hhu %hhu %u,",
> +			   &bi->bpf_program[i].code,
> +			   &bi->bpf_program[i].jt,
> +			   &bi->bpf_program[i].jf,
> +			   &bi->bpf_program[i].k) != 4) {
> +			xtables_error(PARAMETER_PROBLEM,
> +				      "bpf: error at instr %d", i);
> +			return;

Same thing.

> +		}
> +		i++;
> +	}
> +
> +	if (i != bi->bpf_program_num_elem) {
> +		xtables_error(PARAMETER_PROBLEM,
> +			      "bpf: program length: parsing");

Better error reporting?

> +		return;

Same thing.

> +	}
> +}
> +
> +static void bpf_parse_file(struct xt_option_call *cb, const char *filepath)
> +{
> +	struct stat stats;
> +	char *contents;
> +	int fd, len = 0, ret;
> +
> +	if (access(filepath, F_OK))
> +		xtables_error(PARAMETER_PROBLEM, "bpf: no such file");
> +	if (access(filepath, R_OK))
> +		xtables_error(PARAMETER_PROBLEM, "bpf: no read permissions");

Remove these above so...

> +
> +	fd = open(filepath, O_RDONLY);
> +	if (fd == -1)
> +		xtables_error(PARAMETER_PROBLEM, "bpf: error opening file");

you can use strerror here.

> +	if (fstat(fd, &stats))
> +		xtables_error(PARAMETER_PROBLEM, "bpf: error reading metadata");
> +	if (stats.st_size >= BCODE_FILE_MAX_LEN_B)
> +		xtables_error(PARAMETER_PROBLEM, "bpf: file too large");
> +
> +	contents = malloc(stats.st_size + 1);

Better a buffer allocated in the stack now that we have 64
instructions per filter?

Or use fread to get each line and put it into the filter? so reading
the filter and parsing it happens all at once.

> +	if (!contents)
> +		xtables_error(PARAMETER_PROBLEM, "bpf: allocation failed");
> +	contents[stats.st_size] = 0;
> +
> +	while (len < stats.st_size) {
> +		ret = read(fd, contents + len, stats.st_size - len);
> +		if (ret == -1) {
> +			if (errno == EAGAIN)
> +				continue;
> +			xtables_error(PARAMETER_PROBLEM, "bpf: read failed");
> +		}
> +		if (!ret)
> +			xtables_error(PARAMETER_PROBLEM, "bpf: unexpected EOF");
> +		len += ret;
> +	}
> +
> +	bpf_parse_string(cb, contents, '\n');
> +
> +	free(contents);
> +	close(fd);
> +}
> +
> +static void bpf_parse(struct xt_option_call *cb)
> +{
> +	xtables_option_parse(cb);
> +	switch (cb->entry->id) {
> +	case O_BCODE_STDIN:
> +		bpf_parse_string(cb, cb->arg, ',');
> +		break;
> +	case O_BCODE_FILE:
> +		bpf_parse_file(cb, cb->arg);
> +		break;
> +	default:
> +		xtables_error(PARAMETER_PROBLEM, "bpf: unknown option");
> +	}
> +}
> +
> +static void bpf_print_code(const void *ip, const struct xt_entry_match *match)
> +{
> +	const struct xt_bpf_info *info = (void *) match->data;
> +	int i;
> +
> +	for (i = 0; i < info->bpf_program_num_elem; i++)
> +		printf("%hu %hhu %hhu %u,", info->bpf_program[i].code,
> +					    info->bpf_program[i].jt,
> +					    info->bpf_program[i].jf,
> +					    info->bpf_program[i].k);
> +}
> +
> +static void bpf_save(const void *ip, const struct xt_entry_match *match)
> +{
> +	const struct xt_bpf_info *info = (void *) match->data;
> +
> +	printf(" --bytecode \"%hu,", info->bpf_program_num_elem);
> +	bpf_print_code(ip, match);
> +	printf("\"");
> +}
> +
> +static void bpf_fcheck(struct xt_fcheck_call *cb)
> +{
> +	if (((!!(cb->xflags & (1 << O_BCODE_STDIN))) ^
> +	     (!!(cb->xflags & (1 << O_BCODE_FILE)))) == 0)
> +		xtables_error(PARAMETER_PROBLEM,
> +			      "bpf: pass bytecode or bytecode-file, not both");
> +}
> +
> +static void bpf_print(const void *ip, const struct xt_entry_match *match,
> +		      int numeric)
> +{
> +	printf("match bpf ");
> +	return bpf_print_code(ip, match);
> +}
> +
> +static struct xtables_match bpf_match = {
> +	.family		= NFPROTO_UNSPEC,
> +	.name		= "bpf",
> +	.version	= XTABLES_VERSION,
> +	.size		= XT_ALIGN(sizeof(struct xt_bpf_info)),
> +	.userspacesize	= XT_ALIGN(sizeof(struct xt_bpf_info) - sizeof(void *)),

Use offsetof instead, please.

> +	.help		= bpf_help,
> +	.print		= bpf_print,
> +	.save		= bpf_save,
> +	.x6_parse	= bpf_parse,
> +	.x6_fcheck	= bpf_fcheck,
> +	.x6_options	= bpf_opts,
> +};
> +
> +void _init(void)
> +{
> +	xtables_register_match(&bpf_match);
> +}
> diff --git a/extensions/libxt_bpf.man b/extensions/libxt_bpf.man
> new file mode 100644
> index 0000000..359684c
> --- /dev/null
> +++ b/extensions/libxt_bpf.man
> @@ -0,0 +1,17 @@
> +.TP
> +Match using Linux Socket Filter. Expects a BPF program in decimal format for a device without link layer. This is the format generated by \fBtcpdump \-ddd \-i $DEV\fP, if $DEV is of type DLT_RAW.
> +.TP
> +\fB\-\-bytecode\-file\fP \fIfilename\fP
> +Pass the program stored in a file.
> +For example:
> +.IP
> +iptables \-A OUTPUT \-m bpf \-\-bytecode-file program.bpf \-j ACCEPT

It's a good idea to describe the format of the file here.

> +.TP
> +\fB\-\-bytecode\fP \fIcode\fP
> +Pass the program on the command line. In this case all end-lines
> +must have been converted to commas (for instance using `tr '\\n' ','`).
> +For example:
> +.IP
> +iptables \-A OUTPUT \-m bpf \-\-bytecode
> +         '6,40 0 0 14, 21 0 3 2048,48 0 0 25,21 0 1 20,6 0 0 96,6 0 0 0'

Please, a filter that actually works ;-)

> +         \-j ACCEPT
> -- 
> 1.7.7.3
> 
> --
> To unsubscribe from this list: send the line "unsubscribe netfilter-devel" in
> the body of a message to majordomo@vger.kernel.org
> More majordomo info at  http://vger.kernel.org/majordomo-info.html
--
To unsubscribe from this list: send the line "unsubscribe netfilter-devel" in
the body of a message to majordomo@vger.kernel.org
More majordomo info at  http://vger.kernel.org/majordomo-info.html
Willem de Bruijn Jan. 24, 2013, 2:04 a.m. UTC | #2
On Mon, Jan 21, 2013 at 7:19 AM, Pablo Neira Ayuso <pablo@netfilter.org> wrote:
> On Wed, Jan 09, 2013 at 07:12:07PM -0500, Willem de Bruijn wrote:
>> Changes:
>> - v2
>>   - update to match latest kernel module (fixed size match struct)
>>   - add manual page
>>   - fix save/restore
>>   - fix matching whole program on delete
>>
>> Support filtering using Linux Socket Filters
>> ---
>>  extensions/libxt_bpf.c   |  199 ++++++++++++++++++++++++++++++++++++++++++++++
>>  extensions/libxt_bpf.man |   17 ++++
>>  2 files changed, 216 insertions(+), 0 deletions(-)
>>  create mode 100644 extensions/libxt_bpf.c
>>  create mode 100644 extensions/libxt_bpf.man
>>
>> diff --git a/extensions/libxt_bpf.c b/extensions/libxt_bpf.c
>> new file mode 100644
>> index 0000000..8ad540f
>> --- /dev/null
>> +++ b/extensions/libxt_bpf.c
>> @@ -0,0 +1,199 @@
>> +/*
>> + * Xtables BPF extension
>> + *
>> + * Written by Willem de Bruijn (willemb@google.com)
>> + * Copyright Google, Inc. 2013
>> + * Licensed under the GNU General Public License version 2 (GPLv2)
>> +*/
>> +
>> +#include <linux/netfilter/xt_bpf.h>
>> +#include <errno.h>
>> +#include <fcntl.h>
>> +#include <stdio.h>
>> +#include <stdlib.h>
>> +#include <string.h>
>> +#include <sys/stat.h>
>> +#include <sys/types.h>
>> +#include <unistd.h>
>> +#include <xtables.h>
>> +
>> +#define BCODE_FILE_MAX_LEN_B 1024
>
> This has to be updated now that this sticks to 64 instructions per
> filter.

Oh.. rereading my comments after sending the revised patch I see that
I missed this. Will fix up in the next revision. If my little
arithmetic below is correct, it should be either 1603, or a little bit
larger (this is mainly a safety against enormous files).

>> +
>> +enum { O_BCODE_STDIN = 0,
>> +       O_BCODE_FILE};
>
> Better:
>
> enum {
>         O_...
>         O_...
> };

Done

>> +
>> +static void bpf_help(void)
>> +{
>> +     printf(
>> +"bpf match options:\n"
>> +"--bytecode-file <file>      : a bpf program as generated by\n"
>> +"  `tcpdump -i any -ddd <expr> > file\n"
>> +"--bytecode <program>        : a bpf program as generated by\n"
>> +"  `tcpdump -i any -ddd <expr> | tr '\\n' ','`\n");
>> +}
>> +
>> +static const struct xt_option_entry bpf_opts[] = {
>> +     {.name = "bytecode", .id = O_BCODE_STDIN, .type = XTTYPE_STRING},
>> +     {.name = "bytecode-file", .id = O_BCODE_FILE, .type = XTTYPE_STRING},
>> +     XTOPT_TABLEEND,
>> +};
>> +
>> +static void bpf_parse_string(struct xt_option_call *cb, const char *bpf_program,
>> +                          const char separator)
>> +{
>> +     struct xt_bpf_info *bi = (void *) cb->data;
>> +     const char *token;
>> +     char sp;
>> +     int i;
>> +
>> +     /* parse head: length. */
>> +     if (sscanf(bpf_program, "%hu%c", &bi->bpf_program_num_elem, &sp) != 2 ||
>> +                sp != separator)
>> +             xtables_error(PARAMETER_PROBLEM,
>> +                           "bpf: error parsing program length");
>> +     if (!bi->bpf_program_num_elem)
>> +             xtables_error(PARAMETER_PROBLEM,
>> +                           "bpf: illegal zero length program");
>> +     if (bi->bpf_program_num_elem > XT_BPF_MAX_NUM_INSTR)
>> +             xtables_error(PARAMETER_PROBLEM,
>> +                           "bpf: number of instructions exceeds maximum");
>> +
>> +     /* parse instructions. */
>> +     i = 0;
>> +     token = bpf_program;
>> +     while ((token = strchr(token, separator)) && (++token)[0]) {
>> +             if (i >= bi->bpf_program_num_elem) {
>> +                     xtables_error(PARAMETER_PROBLEM,
>> +                                   "bpf: program length: parameter");
>
> better error reporting?

Done

>> +                     return;

Done

> That return is superfluous. xtables_error calls exit.
>
>> +             }
>> +             if (sscanf(token, "%hu %hhu %hhu %u,",
>> +                        &bi->bpf_program[i].code,
>> +                        &bi->bpf_program[i].jt,
>> +                        &bi->bpf_program[i].jf,
>> +                        &bi->bpf_program[i].k) != 4) {
>> +                     xtables_error(PARAMETER_PROBLEM,
>> +                                   "bpf: error at instr %d", i);
>> +                     return;
>
> Same thing.

Done

>> +             }
>> +             i++;
>> +     }
>> +
>> +     if (i != bi->bpf_program_num_elem) {
>> +             xtables_error(PARAMETER_PROBLEM,
>> +                           "bpf: program length: parsing");
>
> Better error reporting?

Done

>> +             return;
>
> Same thing.

Done

>> +     }
>> +}
>> +
>> +static void bpf_parse_file(struct xt_option_call *cb, const char *filepath)
>> +{
>> +     struct stat stats;
>> +     char *contents;
>> +     int fd, len = 0, ret;
>> +
>> +     if (access(filepath, F_OK))
>> +             xtables_error(PARAMETER_PROBLEM, "bpf: no such file");
>> +     if (access(filepath, R_OK))
>> +             xtables_error(PARAMETER_PROBLEM, "bpf: no read permissions");
>
> Remove these above so...

Done

>
>> +
>> +     fd = open(filepath, O_RDONLY);
>> +     if (fd == -1)
>> +             xtables_error(PARAMETER_PROBLEM, "bpf: error opening file");
>
> you can use strerror here.

Done

>
>> +     if (fstat(fd, &stats))
>> +             xtables_error(PARAMETER_PROBLEM, "bpf: error reading metadata");
>> +     if (stats.st_size >= BCODE_FILE_MAX_LEN_B)
>> +             xtables_error(PARAMETER_PROBLEM, "bpf: file too large");
>> +
>> +     contents = malloc(stats.st_size + 1);
>
> Better a buffer allocated in the stack now that we have 64
> instructions per filter?

The decimal notation still allows variable size, causing us to have to
allocate an upper estimate. This is 3 characters for the first line +
64 * (5 + 1 + 3 + 1 + 3 + 1 + 10 + 1) == 1603 B if I didn't mess up
somewhere.

> Or use fread to get each line and put it into the filter? so reading
> the filter and parsing it happens all at once.

That would involve duplicating some of the parsing logic from
bpf_parse_string, and the same error reports (program length mismatch,
instruction errors, etcetera). I think that parsing in only one
location is preferable (but I'm happy to change the code if you
prefer).

>> +     if (!contents)
>> +             xtables_error(PARAMETER_PROBLEM, "bpf: allocation failed");
>> +     contents[stats.st_size] = 0;
>> +
>> +     while (len < stats.st_size) {
>> +             ret = read(fd, contents + len, stats.st_size - len);
>> +             if (ret == -1) {
>> +                     if (errno == EAGAIN)
>> +                             continue;
>> +                     xtables_error(PARAMETER_PROBLEM, "bpf: read failed");
>> +             }
>> +             if (!ret)
>> +                     xtables_error(PARAMETER_PROBLEM, "bpf: unexpected EOF");
>> +             len += ret;
>> +     }
>> +
>> +     bpf_parse_string(cb, contents, '\n');
>> +
>> +     free(contents);
>> +     close(fd);
>> +}
>> +
>> +static void bpf_parse(struct xt_option_call *cb)
>> +{
>> +     xtables_option_parse(cb);
>> +     switch (cb->entry->id) {
>> +     case O_BCODE_STDIN:
>> +             bpf_parse_string(cb, cb->arg, ',');
>> +             break;
>> +     case O_BCODE_FILE:
>> +             bpf_parse_file(cb, cb->arg);
>> +             break;
>> +     default:
>> +             xtables_error(PARAMETER_PROBLEM, "bpf: unknown option");
>> +     }
>> +}
>> +
>> +static void bpf_print_code(const void *ip, const struct xt_entry_match *match)
>> +{
>> +     const struct xt_bpf_info *info = (void *) match->data;
>> +     int i;
>> +
>> +     for (i = 0; i < info->bpf_program_num_elem; i++)
>> +             printf("%hu %hhu %hhu %u,", info->bpf_program[i].code,
>> +                                         info->bpf_program[i].jt,
>> +                                         info->bpf_program[i].jf,
>> +                                         info->bpf_program[i].k);
>> +}
>> +
>> +static void bpf_save(const void *ip, const struct xt_entry_match *match)
>> +{
>> +     const struct xt_bpf_info *info = (void *) match->data;
>> +
>> +     printf(" --bytecode \"%hu,", info->bpf_program_num_elem);
>> +     bpf_print_code(ip, match);
>> +     printf("\"");
>> +}
>> +
>> +static void bpf_fcheck(struct xt_fcheck_call *cb)
>> +{
>> +     if (((!!(cb->xflags & (1 << O_BCODE_STDIN))) ^
>> +          (!!(cb->xflags & (1 << O_BCODE_FILE)))) == 0)
>> +             xtables_error(PARAMETER_PROBLEM,
>> +                           "bpf: pass bytecode or bytecode-file, not both");
>> +}
>> +
>> +static void bpf_print(const void *ip, const struct xt_entry_match *match,
>> +                   int numeric)
>> +{
>> +     printf("match bpf ");
>> +     return bpf_print_code(ip, match);
>> +}
>> +
>> +static struct xtables_match bpf_match = {
>> +     .family         = NFPROTO_UNSPEC,
>> +     .name           = "bpf",
>> +     .version        = XTABLES_VERSION,
>> +     .size           = XT_ALIGN(sizeof(struct xt_bpf_info)),
>> +     .userspacesize  = XT_ALIGN(sizeof(struct xt_bpf_info) - sizeof(void *)),
>
> Use offsetof instead, please.

Done.

>> +     .help           = bpf_help,
>> +     .print          = bpf_print,
>> +     .save           = bpf_save,
>> +     .x6_parse       = bpf_parse,
>> +     .x6_fcheck      = bpf_fcheck,
>> +     .x6_options     = bpf_opts,
>> +};
>> +
>> +void _init(void)
>> +{
>> +     xtables_register_match(&bpf_match);
>> +}
>> diff --git a/extensions/libxt_bpf.man b/extensions/libxt_bpf.man
>> new file mode 100644
>> index 0000000..359684c
>> --- /dev/null
>> +++ b/extensions/libxt_bpf.man
>> @@ -0,0 +1,17 @@
>> +.TP
>> +Match using Linux Socket Filter. Expects a BPF program in decimal format for a device without link layer. This is the format generated by \fBtcpdump \-ddd \-i $DEV\fP, if $DEV is of type DLT_RAW.
>> +.TP
>> +\fB\-\-bytecode\-file\fP \fIfilename\fP
>> +Pass the program stored in a file.
>> +For example:
>> +.IP
>> +iptables \-A OUTPUT \-m bpf \-\-bytecode-file program.bpf \-j ACCEPT
>
> It's a good idea to describe the format of the file here.

Done. Maybe a bit too verbose now. Let me know, if so.

>> +.TP
>> +\fB\-\-bytecode\fP \fIcode\fP
>> +Pass the program on the command line. In this case all end-lines
>> +must have been converted to commas (for instance using `tr '\\n' ','`).
>> +For example:
>> +.IP
>> +iptables \-A OUTPUT \-m bpf \-\-bytecode
>> +         '6,40 0 0 14, 21 0 3 2048,48 0 0 25,21 0 1 20,6 0 0 96,6 0 0 0'
>
> Please, a filter that actually works ;-)

Oops! Done (and tested).

>> +         \-j ACCEPT
>> --
>> 1.7.7.3
>>
>> --
>> To unsubscribe from this list: send the line "unsubscribe netfilter-devel" in
>> the body of a message to majordomo@vger.kernel.org
>> More majordomo info at  http://vger.kernel.org/majordomo-info.html

Thanks!
--
To unsubscribe from this list: send the line "unsubscribe netfilter-devel" in
the body of a message to majordomo@vger.kernel.org
More majordomo info at  http://vger.kernel.org/majordomo-info.html
diff mbox

Patch

diff --git a/extensions/libxt_bpf.c b/extensions/libxt_bpf.c
new file mode 100644
index 0000000..8ad540f
--- /dev/null
+++ b/extensions/libxt_bpf.c
@@ -0,0 +1,199 @@ 
+/*
+ * Xtables BPF extension
+ *
+ * Written by Willem de Bruijn (willemb@google.com)
+ * Copyright Google, Inc. 2013
+ * Licensed under the GNU General Public License version 2 (GPLv2)
+*/
+
+#include <linux/netfilter/xt_bpf.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <sys/stat.h>
+#include <sys/types.h>
+#include <unistd.h>
+#include <xtables.h>
+
+#define BCODE_FILE_MAX_LEN_B	1024
+
+enum { O_BCODE_STDIN = 0,
+       O_BCODE_FILE};
+
+static void bpf_help(void)
+{
+	printf(
+"bpf match options:\n"
+"--bytecode-file <file>	: a bpf program as generated by\n"
+"  `tcpdump -i any -ddd <expr> > file\n"
+"--bytecode <program>	: a bpf program as generated by\n"
+"  `tcpdump -i any -ddd <expr> | tr '\\n' ','`\n");
+}
+
+static const struct xt_option_entry bpf_opts[] = {
+	{.name = "bytecode", .id = O_BCODE_STDIN, .type = XTTYPE_STRING},
+	{.name = "bytecode-file", .id = O_BCODE_FILE, .type = XTTYPE_STRING},
+	XTOPT_TABLEEND,
+};
+
+static void bpf_parse_string(struct xt_option_call *cb, const char *bpf_program,
+			     const char separator)
+{
+	struct xt_bpf_info *bi = (void *) cb->data;
+	const char *token;
+	char sp;
+	int i;
+
+	/* parse head: length. */
+	if (sscanf(bpf_program, "%hu%c", &bi->bpf_program_num_elem, &sp) != 2 ||
+		   sp != separator)
+		xtables_error(PARAMETER_PROBLEM,
+			      "bpf: error parsing program length");
+	if (!bi->bpf_program_num_elem)
+		xtables_error(PARAMETER_PROBLEM,
+			      "bpf: illegal zero length program");
+	if (bi->bpf_program_num_elem > XT_BPF_MAX_NUM_INSTR)
+		xtables_error(PARAMETER_PROBLEM,
+			      "bpf: number of instructions exceeds maximum");
+
+	/* parse instructions. */
+	i = 0;
+	token = bpf_program;
+	while ((token = strchr(token, separator)) && (++token)[0]) {
+		if (i >= bi->bpf_program_num_elem) {
+			xtables_error(PARAMETER_PROBLEM,
+				      "bpf: program length: parameter");
+			return;
+		}
+		if (sscanf(token, "%hu %hhu %hhu %u,",
+			   &bi->bpf_program[i].code,
+			   &bi->bpf_program[i].jt,
+			   &bi->bpf_program[i].jf,
+			   &bi->bpf_program[i].k) != 4) {
+			xtables_error(PARAMETER_PROBLEM,
+				      "bpf: error at instr %d", i);
+			return;
+		}
+		i++;
+	}
+
+	if (i != bi->bpf_program_num_elem) {
+		xtables_error(PARAMETER_PROBLEM,
+			      "bpf: program length: parsing");
+		return;
+	}
+}
+
+static void bpf_parse_file(struct xt_option_call *cb, const char *filepath)
+{
+	struct stat stats;
+	char *contents;
+	int fd, len = 0, ret;
+
+	if (access(filepath, F_OK))
+		xtables_error(PARAMETER_PROBLEM, "bpf: no such file");
+	if (access(filepath, R_OK))
+		xtables_error(PARAMETER_PROBLEM, "bpf: no read permissions");
+
+	fd = open(filepath, O_RDONLY);
+	if (fd == -1)
+		xtables_error(PARAMETER_PROBLEM, "bpf: error opening file");
+	if (fstat(fd, &stats))
+		xtables_error(PARAMETER_PROBLEM, "bpf: error reading metadata");
+	if (stats.st_size >= BCODE_FILE_MAX_LEN_B)
+		xtables_error(PARAMETER_PROBLEM, "bpf: file too large");
+
+	contents = malloc(stats.st_size + 1);
+	if (!contents)
+		xtables_error(PARAMETER_PROBLEM, "bpf: allocation failed");
+	contents[stats.st_size] = 0;
+
+	while (len < stats.st_size) {
+		ret = read(fd, contents + len, stats.st_size - len);
+		if (ret == -1) {
+			if (errno == EAGAIN)
+				continue;
+			xtables_error(PARAMETER_PROBLEM, "bpf: read failed");
+		}
+		if (!ret)
+			xtables_error(PARAMETER_PROBLEM, "bpf: unexpected EOF");
+		len += ret;
+	}
+
+	bpf_parse_string(cb, contents, '\n');
+
+	free(contents);
+	close(fd);
+}
+
+static void bpf_parse(struct xt_option_call *cb)
+{
+	xtables_option_parse(cb);
+	switch (cb->entry->id) {
+	case O_BCODE_STDIN:
+		bpf_parse_string(cb, cb->arg, ',');
+		break;
+	case O_BCODE_FILE:
+		bpf_parse_file(cb, cb->arg);
+		break;
+	default:
+		xtables_error(PARAMETER_PROBLEM, "bpf: unknown option");
+	}
+}
+
+static void bpf_print_code(const void *ip, const struct xt_entry_match *match)
+{
+	const struct xt_bpf_info *info = (void *) match->data;
+	int i;
+
+	for (i = 0; i < info->bpf_program_num_elem; i++)
+		printf("%hu %hhu %hhu %u,", info->bpf_program[i].code,
+					    info->bpf_program[i].jt,
+					    info->bpf_program[i].jf,
+					    info->bpf_program[i].k);
+}
+
+static void bpf_save(const void *ip, const struct xt_entry_match *match)
+{
+	const struct xt_bpf_info *info = (void *) match->data;
+
+	printf(" --bytecode \"%hu,", info->bpf_program_num_elem);
+	bpf_print_code(ip, match);
+	printf("\"");
+}
+
+static void bpf_fcheck(struct xt_fcheck_call *cb)
+{
+	if (((!!(cb->xflags & (1 << O_BCODE_STDIN))) ^
+	     (!!(cb->xflags & (1 << O_BCODE_FILE)))) == 0)
+		xtables_error(PARAMETER_PROBLEM,
+			      "bpf: pass bytecode or bytecode-file, not both");
+}
+
+static void bpf_print(const void *ip, const struct xt_entry_match *match,
+		      int numeric)
+{
+	printf("match bpf ");
+	return bpf_print_code(ip, match);
+}
+
+static struct xtables_match bpf_match = {
+	.family		= NFPROTO_UNSPEC,
+	.name		= "bpf",
+	.version	= XTABLES_VERSION,
+	.size		= XT_ALIGN(sizeof(struct xt_bpf_info)),
+	.userspacesize	= XT_ALIGN(sizeof(struct xt_bpf_info) - sizeof(void *)),
+	.help		= bpf_help,
+	.print		= bpf_print,
+	.save		= bpf_save,
+	.x6_parse	= bpf_parse,
+	.x6_fcheck	= bpf_fcheck,
+	.x6_options	= bpf_opts,
+};
+
+void _init(void)
+{
+	xtables_register_match(&bpf_match);
+}
diff --git a/extensions/libxt_bpf.man b/extensions/libxt_bpf.man
new file mode 100644
index 0000000..359684c
--- /dev/null
+++ b/extensions/libxt_bpf.man
@@ -0,0 +1,17 @@ 
+.TP
+Match using Linux Socket Filter. Expects a BPF program in decimal format for a device without link layer. This is the format generated by \fBtcpdump \-ddd \-i $DEV\fP, if $DEV is of type DLT_RAW.
+.TP
+\fB\-\-bytecode\-file\fP \fIfilename\fP
+Pass the program stored in a file.
+For example:
+.IP
+iptables \-A OUTPUT \-m bpf \-\-bytecode-file program.bpf \-j ACCEPT
+.TP
+\fB\-\-bytecode\fP \fIcode\fP
+Pass the program on the command line. In this case all end-lines
+must have been converted to commas (for instance using `tr '\\n' ','`).
+For example:
+.IP
+iptables \-A OUTPUT \-m bpf \-\-bytecode
+         '6,40 0 0 14, 21 0 3 2048,48 0 0 25,21 0 1 20,6 0 0 96,6 0 0 0'
+         \-j ACCEPT