diff mbox series

[RFC,1/3] netfilter: nf_tables: add infrastructure to provide intermediate representation

Message ID 20180219163706.5388-2-pablo@netfilter.org
State RFC
Delegated to: Pablo Neira
Headers show
Series nftables meets bpf | expand

Commit Message

Pablo Neira Ayuso Feb. 19, 2018, 4:37 p.m. UTC
This infrastructure allows us to build small abstract syntax trees to
represent the rule.

You can use this representation to easily generate another different
target internal representation.

This intermediate representation makes the nf_tables codebase
independent from layout changes in its binary representation, to avoid
hard dependencies between us and the target representation.

Signed-off-by: Pablo Neira Ayuso <pablo@netfilter.org>
---
 include/net/netfilter/nf_tables.h     |   6 ++
 include/net/netfilter/nf_tables_jit.h |  84 +++++++++++++++++++++
 net/netfilter/Makefile                |   3 +-
 net/netfilter/nf_tables_jit.c         | 136 ++++++++++++++++++++++++++++++++++
 net/netfilter/nft_cmp.c               |  87 ++++++++++++++++++++++
 net/netfilter/nft_meta.c              |  19 +++++
 net/netfilter/nft_payload.c           |  22 ++++++
 7 files changed, 356 insertions(+), 1 deletion(-)
 create mode 100644 include/net/netfilter/nf_tables_jit.h
 create mode 100644 net/netfilter/nf_tables_jit.c
diff mbox series

Patch

diff --git a/include/net/netfilter/nf_tables.h b/include/net/netfilter/nf_tables.h
index 663b015dace5..8a6406f3811f 100644
--- a/include/net/netfilter/nf_tables.h
+++ b/include/net/netfilter/nf_tables.h
@@ -712,6 +712,8 @@  struct nft_expr_type {
 
 #define NFT_EXPR_STATEFUL		0x1
 
+struct nft_ast_expr;
+
 /**
  *	struct nft_expr_ops - nf_tables expression operations
  *
@@ -722,6 +724,7 @@  struct nft_expr_type {
  *	@dump: function to dump parameters
  *	@type: expression type
  *	@validate: validate expression, called during loop detection
+ *	@delinearize: convert expression to intermediate representation
  *	@data: extra data to attach to this expression operation
  */
 struct nft_expr;
@@ -743,6 +746,9 @@  struct nft_expr_ops {
 	int				(*validate)(const struct nft_ctx *ctx,
 						    const struct nft_expr *expr,
 						    const struct nft_data **data);
+	int				(*delinearize)(struct nft_ast_expr **regs,
+						       const struct nft_expr *expr,
+						       struct list_head *stmt);
 	const struct nft_expr_type	*type;
 	void				*data;
 };
diff --git a/include/net/netfilter/nf_tables_jit.h b/include/net/netfilter/nf_tables_jit.h
new file mode 100644
index 000000000000..124d3da91b0d
--- /dev/null
+++ b/include/net/netfilter/nf_tables_jit.h
@@ -0,0 +1,84 @@ 
+#ifndef _NFTABLES_JIT_H_
+#define _NFTABLES_JIT_H_
+
+#include <uapi/linux/netfilter/nf_tables.h>
+
+enum nft_ast_expr_type {
+	NFT_AST_EXPR_UNSPEC	= 0,
+	NFT_AST_EXPR_RELATIONAL,
+	NFT_AST_EXPR_VALUE,
+	NFT_AST_EXPR_META,
+	NFT_AST_EXPR_PAYLOAD,
+};
+
+enum nft_ast_expr_ops {
+	NFT_AST_OP_INVALID,
+	NFT_AST_OP_EQ,
+	NFT_AST_OP_NEQ,
+	NFT_AST_OP_LT,
+	NFT_AST_OP_LTE,
+	NFT_AST_OP_GT,
+	NFT_AST_OP_GTE,
+	NFT_AST_OP_AND,
+	NFT_AST_OP_OR,
+	NFT_AST_OP_XOR,
+};
+
+/**
+ *	struct nft_ast_expr - nf_tables delinearized expression
+ *
+ *	@type: expression type
+ *	@op: type of operation
+ *	@len: length of expression
+ */
+struct nft_ast_expr {
+	enum nft_ast_expr_type		type;
+	enum nft_ast_expr_ops		op;
+	u32				len;
+	union {
+		struct {
+			struct nft_data		data;
+		} value;
+		struct {
+			enum nft_meta_keys	key;
+		} meta;
+		struct {
+			enum nft_payload_bases	base;
+			u32			offset;
+		} payload;
+		struct {
+			struct nft_ast_expr	*left;
+			struct nft_ast_expr	*right;
+		} relational;
+	};
+};
+
+struct nft_ast_expr *nft_ast_expr_alloc(enum nft_ast_expr_type type);
+void nft_ast_expr_destroy(struct nft_ast_expr *expr);
+
+enum nft_ast_stmt_type {
+	NFT_AST_STMT_EXPR		= 0,
+};
+
+/**
+ *	struct nft_ast_stmt - nf_tables delinearized statement
+ *
+ *	@type: statement type
+ */
+struct nft_ast_stmt {
+	struct list_head			list;
+
+	enum nft_ast_stmt_type			type;
+	union {
+		struct nft_ast_expr		*expr;
+	};
+};
+
+struct nft_ast_stmt *nft_ast_stmt_alloc(enum nft_ast_stmt_type type);
+void nft_ast_stmt_list_release(struct list_head *ast_stmt_list);
+
+void nft_ast_stmt_list_print(struct list_head *stmt_list);
+
+int nft_delinearize(struct list_head *ast_stmt_list, struct nft_rule *rule);
+
+#endif
diff --git a/net/netfilter/Makefile b/net/netfilter/Makefile
index 5d9b8b959e58..8b0006e3ae6f 100644
--- a/net/netfilter/Makefile
+++ b/net/netfilter/Makefile
@@ -75,7 +75,8 @@  obj-$(CONFIG_NF_DUP_NETDEV)	+= nf_dup_netdev.o
 # nf_tables
 nf_tables-objs := nf_tables_core.o nf_tables_api.o nf_tables_trace.o \
 		  nft_immediate.o nft_cmp.o nft_range.o nft_bitwise.o \
-		  nft_byteorder.o nft_payload.o nft_lookup.o nft_dynset.o
+		  nft_byteorder.o nft_payload.o nft_lookup.o nft_dynset.o \
+		  nf_tables_jit.o
 
 obj-$(CONFIG_NF_TABLES)		+= nf_tables.o
 obj-$(CONFIG_NF_TABLES_INET)	+= nf_tables_inet.o
diff --git a/net/netfilter/nf_tables_jit.c b/net/netfilter/nf_tables_jit.c
new file mode 100644
index 000000000000..e971b94bbc69
--- /dev/null
+++ b/net/netfilter/nf_tables_jit.c
@@ -0,0 +1,136 @@ 
+#include <linux/init.h>
+#include <linux/module.h>
+#include <linux/netfilter.h>
+#include <net/netfilter/nf_tables.h>
+#include <net/netfilter/nf_tables_jit.h>
+
+struct nft_ast_expr *nft_ast_expr_alloc(enum nft_ast_expr_type type)
+{
+	struct nft_ast_expr *expr;
+
+	expr = kmalloc(sizeof(struct nft_ast_expr), GFP_KERNEL);
+	if (expr == NULL)
+		return NULL;
+
+	expr->type = type;
+	expr->op   = NFT_AST_OP_INVALID;
+
+	return expr;
+}
+EXPORT_SYMBOL_GPL(nft_ast_expr_alloc);
+
+void nft_ast_expr_destroy(struct nft_ast_expr *expr)
+{
+	switch (expr->type) {
+	case NFT_AST_EXPR_VALUE:
+	case NFT_AST_EXPR_META:
+	case NFT_AST_EXPR_PAYLOAD:
+		kfree(expr);
+		break;
+	case NFT_AST_EXPR_RELATIONAL:
+		nft_ast_expr_destroy(expr->relational.left);
+		nft_ast_expr_destroy(expr->relational.right);
+		break;
+	default:
+		WARN_ONCE(1, "Unknown expr %u at destroy\n", expr->type);
+	}
+}
+EXPORT_SYMBOL_GPL(nft_ast_expr_destroy);
+
+struct nft_ast_stmt *nft_ast_stmt_alloc(enum nft_ast_stmt_type type)
+{
+	struct nft_ast_stmt *stmt;
+
+	stmt = kmalloc(sizeof(struct nft_ast_stmt), GFP_KERNEL);
+	if (stmt == NULL)
+		return NULL;
+
+	stmt->type = type;
+	return stmt;
+}
+EXPORT_SYMBOL_GPL(nft_ast_stmt_alloc);
+
+static void nft_ast_stmt_free(struct nft_ast_stmt *stmt)
+{
+	nft_ast_expr_destroy(stmt->expr);
+	kfree(stmt);
+}
+
+void nft_ast_stmt_list_release(struct list_head *ast_stmt_list)
+{
+	struct nft_ast_stmt *stmt, *next;
+
+	list_for_each_entry_safe(stmt, next, ast_stmt_list, list) {
+		list_del(&stmt->list);
+		nft_ast_stmt_free(stmt);
+	}
+}
+EXPORT_SYMBOL_GPL(nft_ast_stmt_list_release);
+
+int nft_delinearize(struct list_head *ast_stmt_list, struct nft_rule *rule)
+{
+	struct nft_ast_expr *regs[NFT_REG32_15 + 1];
+	struct nft_expr *expr;
+	int err;
+
+	expr = nft_expr_first(rule);
+	while (expr->ops && expr != nft_expr_last(rule)) {
+		if (!expr->ops->delinearize)
+			return -EOPNOTSUPP;
+
+		err = expr->ops->delinearize(regs, expr, ast_stmt_list);
+		if (err < 0)
+			return err;
+
+		expr = nft_expr_next(expr);
+	}
+	return 0;
+}
+EXPORT_SYMBOL_GPL(nft_delinearize);
+
+/* TODO use pr_debug() here. */
+static void nft_ast_expr_print(struct nft_ast_expr *expr)
+{
+	pr_info("expr type %u len %u\n", expr->type, expr->len);
+
+	switch (expr->type) {
+	case NFT_AST_EXPR_VALUE:
+		pr_info("value %x %x %x %x\n",
+			expr->value.data.data[0], expr->value.data.data[1],
+			expr->value.data.data[1], expr->value.data.data[2]);
+	        break;
+	case NFT_AST_EXPR_META:
+		pr_info("meta key %u\n", expr->meta.key);
+		break;
+	case NFT_AST_EXPR_PAYLOAD:
+		pr_info("payload base %u offset %u\n",
+			expr->payload.base, expr->payload.offset);
+	break;
+	case NFT_AST_EXPR_RELATIONAL:
+		pr_info("relational\n");
+		pr_info("       left %p\n", expr->relational.left);
+		nft_ast_expr_print(expr->relational.left);
+		pr_info("       right %p\n", expr->relational.right);
+		nft_ast_expr_print(expr->relational.right);
+		break;
+	default:
+		pr_info("UNKNOWN\n");
+		break;
+	}
+}
+
+void nft_ast_stmt_list_print(struct list_head *stmt_list)
+{
+	struct nft_ast_stmt *stmt;
+
+	list_for_each_entry(stmt, stmt_list, list) {
+		pr_info("stmt %u\n", stmt->type);
+
+		switch (stmt->type) {
+		case NFT_AST_STMT_EXPR:
+			nft_ast_expr_print(stmt->expr);
+			break;
+		}
+	}
+}
+EXPORT_SYMBOL_GPL(nft_ast_stmt_list_print);
diff --git a/net/netfilter/nft_cmp.c b/net/netfilter/nft_cmp.c
index fa90a8402845..d994a9e0392e 100644
--- a/net/netfilter/nft_cmp.c
+++ b/net/netfilter/nft_cmp.c
@@ -16,6 +16,7 @@ 
 #include <linux/netfilter/nf_tables.h>
 #include <net/netfilter/nf_tables_core.h>
 #include <net/netfilter/nf_tables.h>
+#include <net/netfilter/nf_tables_jit.h>
 
 struct nft_cmp_expr {
 	struct nft_data		data;
@@ -109,12 +110,78 @@  static int nft_cmp_dump(struct sk_buff *skb, const struct nft_expr *expr)
 	return -1;
 }
 
+static enum nft_ast_expr_ops nft_cmp_to_ops[NFT_CMP_GTE + 1] = {
+	[NFT_CMP_EQ]	= NFT_AST_OP_EQ,
+	[NFT_CMP_NEQ]	= NFT_AST_OP_NEQ,
+	[NFT_CMP_LT]	= NFT_AST_OP_LT,
+	[NFT_CMP_LTE]	= NFT_AST_OP_LTE,
+	[NFT_CMP_GT]	= NFT_AST_OP_GT,
+	[NFT_CMP_GTE]	= NFT_AST_OP_GTE,
+};
+
+static int nft_ast_expr_cmp_op(enum nft_cmp_ops op)
+{
+	BUG_ON(op > NFT_CMP_GTE + 1);
+
+	return nft_cmp_to_ops[op];
+}
+
+static int __nft_cmp_delinearize(struct nft_ast_expr **regs,
+				 const struct nft_cmp_expr *priv,
+				 struct list_head *stmt_list)
+{
+	struct nft_ast_expr *right, *tree;
+	struct nft_ast_stmt *stmt;
+	int err;
+
+	if (regs[priv->sreg] == NULL)
+		return -EINVAL;
+
+	right = nft_ast_expr_alloc(NFT_AST_EXPR_VALUE);
+	if (right == NULL)
+		return -ENOMEM;
+
+	right->value.data = priv->data;
+	right->len = priv->len;
+
+	err = -ENOMEM;
+	tree = nft_ast_expr_alloc(NFT_AST_EXPR_RELATIONAL);
+	if (tree == NULL)
+		goto err1;
+
+	tree->op = nft_ast_expr_cmp_op(priv->op);
+	tree->relational.left = regs[priv->sreg];
+	tree->relational.right = right;
+	tree->len = tree->relational.left->len;
+
+	stmt = nft_ast_stmt_alloc(NFT_AST_STMT_EXPR);
+	if (stmt == NULL)
+		goto err2;
+
+	stmt->expr = tree;
+	list_add_tail(&stmt->list, stmt_list);
+	return 0;
+err2:
+	nft_ast_expr_destroy(tree);
+err1:
+	nft_ast_expr_destroy(right);
+	return err;
+}
+
+static int nft_cmp_delinearize(struct nft_ast_expr **regs,
+			       const struct nft_expr *expr,
+			       struct list_head *stmt_list)
+{
+	return __nft_cmp_delinearize(regs, nft_expr_priv(expr), stmt_list);
+}
+
 static const struct nft_expr_ops nft_cmp_ops = {
 	.type		= &nft_cmp_type,
 	.size		= NFT_EXPR_SIZE(sizeof(struct nft_cmp_expr)),
 	.eval		= nft_cmp_eval,
 	.init		= nft_cmp_init,
 	.dump		= nft_cmp_dump,
+	.delinearize	= nft_cmp_delinearize,
 };
 
 static int nft_cmp_fast_init(const struct nft_ctx *ctx,
@@ -164,12 +231,32 @@  static int nft_cmp_fast_dump(struct sk_buff *skb, const struct nft_expr *expr)
 	return -1;
 }
 
+static int nft_cmp_fast_delinearize(struct nft_ast_expr **regs,
+				    const struct nft_expr *expr,
+				    struct list_head *stmt_list)
+{
+	const struct nft_cmp_fast_expr *priv = nft_expr_priv(expr);
+	struct nft_cmp_expr cmp = {
+		.data   = {
+			.data   = {
+				[0] = priv->data,
+			},
+		},
+		.sreg   = priv->sreg,
+		.len    = priv->len,
+		.op     = NFT_AST_OP_EQ,
+	};
+
+	return __nft_cmp_delinearize(regs, &cmp, stmt_list);
+}
+
 const struct nft_expr_ops nft_cmp_fast_ops = {
 	.type		= &nft_cmp_type,
 	.size		= NFT_EXPR_SIZE(sizeof(struct nft_cmp_fast_expr)),
 	.eval		= NULL,	/* inlined */
 	.init		= nft_cmp_fast_init,
 	.dump		= nft_cmp_fast_dump,
+	.delinearize	= nft_cmp_fast_delinearize,
 };
 
 static const struct nft_expr_ops *
diff --git a/net/netfilter/nft_meta.c b/net/netfilter/nft_meta.c
index 8fb91940e2e7..920434a9470c 100644
--- a/net/netfilter/nft_meta.c
+++ b/net/netfilter/nft_meta.c
@@ -24,6 +24,7 @@ 
 #include <net/tcp_states.h> /* for TCP_TIME_WAIT */
 #include <net/netfilter/nf_tables.h>
 #include <net/netfilter/nf_tables_core.h>
+#include <net/netfilter/nf_tables_jit.h>
 #include <net/netfilter/nft_meta.h>
 
 #include <uapi/linux/netfilter_bridge.h> /* NF_BR_PRE_ROUTING */
@@ -469,6 +470,23 @@  void nft_meta_set_destroy(const struct nft_ctx *ctx,
 }
 EXPORT_SYMBOL_GPL(nft_meta_set_destroy);
 
+static int nft_meta_get_delinearize(struct nft_ast_expr **regs,
+				    const struct nft_expr *expr,
+				    struct list_head *stmt_list)
+{
+	struct nft_meta *priv = nft_expr_priv(expr);
+	struct nft_ast_expr *dlexpr;
+
+	dlexpr = nft_ast_expr_alloc(NFT_AST_EXPR_META);
+	if (dlexpr == NULL)
+		return -ENOMEM;
+
+	dlexpr->meta.key = priv->key;
+
+	regs[priv->dreg] = dlexpr;
+	return 0;
+}
+
 static struct nft_expr_type nft_meta_type;
 static const struct nft_expr_ops nft_meta_get_ops = {
 	.type		= &nft_meta_type,
@@ -477,6 +495,7 @@  static const struct nft_expr_ops nft_meta_get_ops = {
 	.init		= nft_meta_get_init,
 	.dump		= nft_meta_get_dump,
 	.validate	= nft_meta_get_validate,
+	.delinearize	= nft_meta_get_delinearize,
 };
 
 static const struct nft_expr_ops nft_meta_set_ops = {
diff --git a/net/netfilter/nft_payload.c b/net/netfilter/nft_payload.c
index e110b0ebbf58..0d3b065b701d 100644
--- a/net/netfilter/nft_payload.c
+++ b/net/netfilter/nft_payload.c
@@ -18,6 +18,7 @@ 
 #include <linux/netfilter/nf_tables.h>
 #include <net/netfilter/nf_tables_core.h>
 #include <net/netfilter/nf_tables.h>
+#include <net/netfilter/nf_tables_jit.h>
 /* For layer 4 checksum field offset. */
 #include <linux/tcp.h>
 #include <linux/udp.h>
@@ -153,12 +154,32 @@  static int nft_payload_dump(struct sk_buff *skb, const struct nft_expr *expr)
 	return -1;
 }
 
+static int nft_payload_delinearize(struct nft_ast_expr **regs,
+				   const struct nft_expr *expr,
+				   struct list_head *stmt_list)
+{
+	struct nft_payload *priv = nft_expr_priv(expr);
+	struct nft_ast_expr *dlexpr;
+
+	dlexpr = nft_ast_expr_alloc(NFT_AST_EXPR_PAYLOAD);
+	if (dlexpr == NULL)
+		return -ENOMEM;
+
+	dlexpr->payload.base	= priv->base;
+	dlexpr->payload.offset	= priv->offset;
+	dlexpr->len		= priv->len;
+
+	regs[priv->dreg] = dlexpr;
+	return 0;
+}
+
 static const struct nft_expr_ops nft_payload_ops = {
 	.type		= &nft_payload_type,
 	.size		= NFT_EXPR_SIZE(sizeof(struct nft_payload)),
 	.eval		= nft_payload_eval,
 	.init		= nft_payload_init,
 	.dump		= nft_payload_dump,
+	.delinearize	= nft_payload_delinearize,
 };
 
 const struct nft_expr_ops nft_payload_fast_ops = {
@@ -167,6 +188,7 @@  const struct nft_expr_ops nft_payload_fast_ops = {
 	.eval		= nft_payload_eval,
 	.init		= nft_payload_init,
 	.dump		= nft_payload_dump,
+	.delinearize	= nft_payload_delinearize,
 };
 
 static inline void nft_csum_replace(__sum16 *sum, __wsum fsum, __wsum tsum)