Subject: [RFC,POC 3/3] bpfilter: switch bpfilter to iptables->IMR translation

From: Florian Westphal <[email-masked]>
Date: Sun, 4 Mar 2018 20:40:44 +0100

Translate basic iptables rule blob to the IMR, then ask
IMR to translate to ebpf.

IMR is shared between nft and bpfilter translators.
iptables_gen_append() is the only relevant function here,
as it demonstrates simple 'source/destination matches x' test.

Signed-off-by: Florian Westphal <[email-masked]>
---
 net/bpfilter/Makefile       |  2 +-
 net/bpfilter/bpfilter_gen.h | 15 +++++++++
 net/bpfilter/bpfilter_mod.h | 16 +---------
 net/bpfilter/iptables.c     | 76 +++++++++++++++++++++++++++++++++++++++++++++
 net/bpfilter/iptables.h     |  4 +++
 net/bpfilter/sockopt.c      | 73 +++++++++++++++++++++++++++++++++----------
 6 files changed, 154 insertions(+), 32 deletions(-)
 create mode 100644 net/bpfilter/bpfilter_gen.h
 create mode 100644 net/bpfilter/iptables.c
 create mode 100644 net/bpfilter/iptables.h

diff --git a/net/bpfilter/Makefile b/net/bpfilter/Makefile
index a4064986dc2f..21a8afb60b7c 100644
--- a/net/bpfilter/Makefile
+++ b/net/bpfilter/Makefile
@@ -5,7 +5,7 @@
 
 hostprogs-y := nftjit.ko bpfilter.ko
 always := $(hostprogs-y)
-bpfilter.ko-objs := bpfilter.o tgts.o targets.o tables.o init.o ctor.o sockopt.o gen.o
+bpfilter.ko-objs := bpfilter.o tgts.o targets.o tables.o init.o ctor.o sockopt.o gen.o iptables.o imr.o
 
 NFT_LIBS = -lnftnl
 nftjit.ko-objs := tgts.o targets.o tables.o init.o ctor.o gen.o nftables.o imr.o
diff --git a/net/bpfilter/bpfilter_gen.h b/net/bpfilter/bpfilter_gen.h
new file mode 100644
index 000000000000..71c6e8a73e24
--- /dev/null
+++ b/net/bpfilter/bpfilter_gen.h
@@ -0,0 +1,15 @@
+struct bpfilter_gen_ctx {
+	struct bpf_insn		*img;
+	u32			len_cur;
+	u32			len_max;
+	u32			default_verdict;
+	int			fd;
+	int			ifindex;
+	bool			offloaded;
+};
+
+int bpfilter_gen_init(struct bpfilter_gen_ctx *ctx);
+int bpfilter_gen_prologue(struct bpfilter_gen_ctx *ctx);
+int bpfilter_gen_epilogue(struct bpfilter_gen_ctx *ctx);
+int bpfilter_gen_commit(struct bpfilter_gen_ctx *ctx);
+void bpfilter_gen_destroy(struct bpfilter_gen_ctx *ctx);
diff --git a/net/bpfilter/bpfilter_mod.h b/net/bpfilter/bpfilter_mod.h
index b4209985efff..dc3a90df1788 100644
--- a/net/bpfilter/bpfilter_mod.h
+++ b/net/bpfilter/bpfilter_mod.h
@@ -4,6 +4,7 @@
 
 #include "include/uapi/linux/bpfilter.h"
 #include <linux/list.h>
+#include "bpfilter_gen.h"
 
 struct bpfilter_table {
 	struct hlist_node	hash;
@@ -71,26 +72,11 @@ struct bpfilter_target {
 	u8			rev;
 };
 
-struct bpfilter_gen_ctx {
-	struct bpf_insn		*img;
-	u32			len_cur;
-	u32			len_max;
-	u32			default_verdict;
-	int			fd;
-	int			ifindex;
-	bool			offloaded;
-};
-
 union bpf_attr;
 int sys_bpf(int cmd, union bpf_attr *attr, unsigned int size);
 
-int bpfilter_gen_init(struct bpfilter_gen_ctx *ctx);
-int bpfilter_gen_prologue(struct bpfilter_gen_ctx *ctx);
-int bpfilter_gen_epilogue(struct bpfilter_gen_ctx *ctx);
 int bpfilter_gen_append(struct bpfilter_gen_ctx *ctx,
 			struct bpfilter_ipt_ip *ent, int verdict);
-int bpfilter_gen_commit(struct bpfilter_gen_ctx *ctx);
-void bpfilter_gen_destroy(struct bpfilter_gen_ctx *ctx);
 
 struct bpfilter_target *bpfilter_target_get_by_name(const char *name);
 void bpfilter_target_put(struct bpfilter_target *tgt);
diff --git a/net/bpfilter/iptables.c b/net/bpfilter/iptables.c
new file mode 100644
index 000000000000..055cfa8fbf21
--- /dev/null
+++ b/net/bpfilter/iptables.c
@@ -0,0 +1,76 @@
+#include <string.h>
+#include <stdint.h>
+
+typedef uint16_t __sum16; /* hack */
+#include <linux/ip.h>
+
+#include "bpfilter_mod.h"
+#include "iptables.h"
+#include "imr.h"
+
+static int check_entry(const struct bpfilter_ipt_ip *ent)
+{
+#define M_FF	"\xff\xff\xff\xff"
+	static const __u8 mask1[IFNAMSIZ] = M_FF M_FF M_FF M_FF;
+	static const __u8 mask0[IFNAMSIZ] = { };
+	int ones = strlen(ent->in_iface); ones += ones > 0;
+#undef M_FF
+	if (strlen(ent->out_iface) > 0)
+		return -ENOTSUPP;
+	if (memcmp(ent->in_iface_mask, mask1, ones) ||
+	    memcmp(&ent->in_iface_mask[ones], mask0, sizeof(mask0) - ones))
+		return -ENOTSUPP;
+	if ((ent->src_mask != 0 && ent->src_mask != 0xffffffff) ||
+	    (ent->dst_mask != 0 && ent->dst_mask != 0xffffffff))
+		return -ENOTSUPP;
+
+	return 0;
+}
+
+int iptables_gen_append(struct imr_state *state,
+			struct bpfilter_ipt_ip *ent, int verdict)
+{
+	struct imr_object *left, *right, *relop;
+	int ret;
+
+	ret = check_entry(ent);
+	if (ret < 0)
+		return ret;
+	if (ent->src_mask == 0 && ent->dst_mask == 0)
+		return 0;
+
+	imr_state_rule_begin(state);
+
+	if (ent->src_mask) {
+		left = imr_object_alloc_payload(IMR_PAYLOAD_BASE_NH,
+					        offsetof(struct iphdr, saddr),
+					        sizeof(uint32_t));
+		right = imr_object_alloc_imm32(ent->src);
+
+		relop = imr_object_alloc_relational(IMR_RELOP_EQ, left, right);
+		imr_state_add_obj(state, relop);
+	}
+
+	if (ent->dst_mask) {
+		left = imr_object_alloc_payload(IMR_PAYLOAD_BASE_NH,
+					        offsetof(struct iphdr, daddr),
+					        sizeof(uint32_t));
+		right = imr_object_alloc_imm32(ent->dst);
+
+		relop = imr_object_alloc_relational(IMR_RELOP_EQ, left, right);
+		imr_state_add_obj(state, relop);
+	}
+
+	switch (verdict) {
+	case -1:
+		verdict = IMR_VERDICT_DROP;
+		break;
+	default:
+		verdict = IMR_VERDICT_PASS;
+		break;
+	}
+
+	imr_state_add_obj(state, imr_object_alloc_verdict(verdict));
+
+	return imr_state_rule_end(state);
+}
diff --git a/net/bpfilter/iptables.h b/net/bpfilter/iptables.h
new file mode 100644
index 000000000000..8d1299f10e55
--- /dev/null
+++ b/net/bpfilter/iptables.h
@@ -0,0 +1,4 @@
+#include "imr.h"
+
+int iptables_gen_append(struct imr_state *state,
+			struct bpfilter_ipt_ip *ent, int verdict);
diff --git a/net/bpfilter/sockopt.c b/net/bpfilter/sockopt.c
index 26ad12a11736..c601fa0839a1 100644
--- a/net/bpfilter/sockopt.c
+++ b/net/bpfilter/sockopt.c
@@ -5,8 +5,13 @@
 #include <stdlib.h>
 
 #include <sys/socket.h>
+#include <net/if.h>
+
+#include <linux/if_ether.h>
+#include <arpa/inet.h>
 
 #include "bpfilter_mod.h"
+#include "iptables.h"
 
 /* TODO: Get all of this in here properly done in encoding/decoding layer. */
 static int fetch_name(void *addr, int len, char *name, int name_len)
@@ -144,22 +149,53 @@ int bpfilter_get_entries(void *cmd, int len)
 	return err;
 }
 
+/* We run via XDP so skip non ip ethernet frames */
+static int iptables_accept_non_ipv4(struct imr_state *state)
+{
+	struct imr_object *eth_p_ip, *lltype, *relop;
+	int ret;
+
+	imr_state_rule_begin(state);
+	lltype = imr_object_alloc_payload(IMR_PAYLOAD_BASE_LL,
+					  offsetof(struct ethhdr, h_proto),
+					  sizeof(uint16_t));
+	if (!lltype)
+		return -ENOMEM;
+
+	eth_p_ip = imr_object_alloc_imm32(htons(ETH_P_IP));
+	if (!eth_p_ip) {
+		imr_object_free(lltype);
+		return -ENOMEM;
+	}
+
+	relop = imr_object_alloc_relational(IMR_RELOP_NE, lltype, eth_p_ip);
+	if (!relop) {
+		imr_object_free(eth_p_ip);
+		imr_object_free(lltype);
+		return -ENOMEM;
+	}
+
+	ret = imr_state_add_obj(state, relop);
+	if (ret == 0) {
+		ret = imr_state_add_obj(state, imr_object_alloc_verdict(IMR_VERDICT_PASS));
+		if (ret == 0)
+			return imr_state_rule_end(state);
+	}
+
+	return ret;
+}
+
 static int do_set_replace(struct bpfilter_ipt_replace *req, void *base,
 			  struct bpfilter_table *tbl)
 {
 	unsigned int total_size = req->size;
 	struct bpfilter_table_info *info;
 	struct bpfilter_ipt_entry *ent;
-	struct bpfilter_gen_ctx ctx;
+	struct imr_state *imr_state;
 	unsigned int off, sents = 0, ents = 0;
+	int last_iface = 0;
 	int ret;
 
-	ret = bpfilter_gen_init(&ctx);
-	if (ret < 0)
-		return ret;
-	ret = bpfilter_gen_prologue(&ctx);
-	if (ret < 0)
-		return ret;
 	info = bpfilter_ipv4_table_alloc(tbl, total_size);
 	if (!info)
 		return -ENOMEM;
@@ -167,15 +203,25 @@ static int do_set_replace(struct bpfilter_ipt_replace *req, void *base,
 		free(info);
 		return -EFAULT;
 	}
+	imr_state = imr_state_alloc();
 	base = &info->entries[0];
+	iptables_accept_non_ipv4(imr_state);
 	for (off = 0; off < total_size; off += ent->next_offset) {
 		struct bpfilter_standard_target *tgt;
+		int ifindex;
 		ent = base + off;
+		ifindex = if_nametoindex(ent->ip.in_iface);
+		if (!ifindex)
+			continue;
+		if (last_iface && last_iface != ifindex)
+			return -ENOTSUPP;
+		else
+			last_iface = ifindex;
 		ents++;
 		sents += ent->next_offset;
 		tgt = (void *) ent + ent->target_offset;
 		target_u2k(tgt);
-		ret = bpfilter_gen_append(&ctx, &ent->ip, tgt->verdict);
+		ret = iptables_gen_append(imr_state, &ent->ip, tgt->verdict);
                 if (ret < 0)
                         goto err;
 	}
@@ -183,16 +229,11 @@ static int do_set_replace(struct bpfilter_ipt_replace *req, void *base,
 	info->size = sents;
 	memcpy(info->hook_entry, req->hook_entry, sizeof(info->hook_entry));
 	memcpy(info->underflow, req->underflow, sizeof(info->hook_entry));
-	ret = bpfilter_gen_epilogue(&ctx);
-	if (ret < 0)
-		goto err;
-	ret = bpfilter_gen_commit(&ctx);
-	if (ret < 0)
-		goto err;
+	ret = imr_do_bpf(imr_state);
+	imr_state_free(imr_state);
 	free(tbl->info);
 	tbl->info = info;
-	bpfilter_gen_destroy(&ctx);
-	dprintf(debug_fd, "offloaded %u\n", ctx.offloaded);
+
 	return ret;
 err:
 	free(info);
-- 
2.16.1