Patchwork [next] extensions: add libxt_bpf extension

login
register
mail settings
Submitter Willem de Bruijn
Date Jan. 24, 2013, 2 a.m.
Message ID <1358992858-20172-1-git-send-email-willemb@google.com>
Download mbox | patch
Permalink /patch/215095/
State Accepted
Headers show

Comments

Willem de Bruijn - Jan. 24, 2013, 2 a.m.
Changes:
- v3
  - more informative error messages and man page
- 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   | 193 +++++++++++++++++++++++++++++++++++++++++++++++
 extensions/libxt_bpf.man |  35 +++++++++
 2 files changed, 228 insertions(+)
 create mode 100644 extensions/libxt_bpf.c
 create mode 100644 extensions/libxt_bpf.man
Pablo Neira - April 1, 2013, 10:16 p.m.
On Wed, Jan 23, 2013 at 09:00:58PM -0500, Willem de Bruijn wrote:
> Changes:
> - v3
>   - more informative error messages and man page
> - 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

Applied with changes:

* include a copy of include/linux/netfilter/xt_bpf.h in the tree.

* Removed the --bytecode-file option. The original
  proposal was to accept BPF code in a file in human readable format.
  Now, with the nfbpf_compile utility, it's very easy to generate the
  filter using tcpdump-like syntax. We can recover the file parameter
  later on if you find it useful.

* Removed the trailing comma in the backtick format, the parser works
  just fine for me here.

* Fix error message if --bytecode is missing.

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

Patch

diff --git a/extensions/libxt_bpf.c b/extensions/libxt_bpf.c
new file mode 100644
index 0000000..15dc870
--- /dev/null
+++ b/extensions/libxt_bpf.c
@@ -0,0 +1,193 @@ 
+/*
+ * 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: real program length exceeds"
+				      " the encoded length parameter");
+		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);
+		i++;
+	}
+
+	if (i != bi->bpf_program_num_elem)
+		xtables_error(PARAMETER_PROBLEM,
+			      "bpf: parsed program length is less than the"
+			      " encoded length parameter");
+}
+
+static void bpf_parse_file(struct xt_option_call *cb, const char *filepath)
+{
+	struct stat stats;
+	char *contents;
+	int fd, len = 0, ret;
+
+	fd = open(filepath, O_RDONLY);
+	if (fd == -1)
+		xtables_error(PARAMETER_PROBLEM, "bpf: error opening file: %s",
+			      strerror(errno));
+	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(offsetof(struct xt_bpf_info, filter)),
+	.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..468c023
--- /dev/null
+++ b/extensions/libxt_bpf.man
@@ -0,0 +1,35 @@ 
+.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
+.IP
+The file format matches the output of the tcpdump command: one line
+that stores the number of instructions, followed by one line for each
+instruction. Instruction lines follow the pattern 'u16 u8 u8 u32'
+in decimal notation. Fields encode the operation, jump offset if true,
+jump offset if false and generic multiuse field 'K'. Comments are not
+supported.
+.IP
+For example, to read only packets matching 'ip proto 6', insert the
+following, without the comments or trailing whitespace:
+.br
+4               # number of instructions
+.br
+48 0 0 9        # load byte  ip->proto
+.br
+21 0 1 6        # jump equal IPPROTO_TCP
+.br
+6 0 0 1         # return     pass (non-zero)
+.br
+6 0 0 0         # return     fail (zero)
+.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, the same program passed on the command line becomes:
+.IP
+iptables \-A OUTPUT \-m bpf \-\-bytecode '4,48 0 0 9,21 0 1 6,6 0 0 1,6 0 0 0,' \-j ACCEPT