All of lore.kernel.org
 help / color / mirror / Atom feed
From: Dmitrii Banshchikov <me@ubique.spb.ru>
To: bpf@vger.kernel.org
Cc: Dmitrii Banshchikov <me@ubique.spb.ru>,
	ast@kernel.org, davem@davemloft.net, daniel@iogearbox.net,
	andrii@kernel.org, kafai@fb.com, songliubraving@fb.com,
	yhs@fb.com, john.fastabend@gmail.com, kpsingh@kernel.org,
	netdev@vger.kernel.org, rdna@fb.com
Subject: [PATCH bpf-next v2 08/13] bpfilter: Add struct rule
Date: Sun, 29 Aug 2021 22:36:03 +0400	[thread overview]
Message-ID: <20210829183608.2297877-9-me@ubique.spb.ru> (raw)
In-Reply-To: <20210829183608.2297877-1-me@ubique.spb.ru>

struct rule is an equivalent of struct ipt_entry. A rule consists of
zero or more matches and a target. A rule has a pointer to its ipt_entry
in entries blob.  struct rule should simplify iteration over a blob and
avoid blob's guts in code generation.

The inline way of code generation for a rule is performed by
gen_inline_rule() and consists of the following:
        1) Emit instructions for rule's L3 src/dst addresses and
           protocol
        2) Emit instructions for each rule's match by calling match's
           interface
        3) Emit instructions for rule's target by calling target's
           interface

Signed-off-by: Dmitrii Banshchikov <me@ubique.spb.ru>
---
 net/bpfilter/Makefile                         |   2 +-
 net/bpfilter/rule.c                           | 239 ++++++++++++++++++
 net/bpfilter/rule.h                           |  34 +++
 .../testing/selftests/bpf/bpfilter/.gitignore |   1 +
 tools/testing/selftests/bpf/bpfilter/Makefile |   5 +-
 .../selftests/bpf/bpfilter/bpfilter_util.h    |   8 +
 .../selftests/bpf/bpfilter/test_rule.c        |  55 ++++
 7 files changed, 342 insertions(+), 2 deletions(-)
 create mode 100644 net/bpfilter/rule.c
 create mode 100644 net/bpfilter/rule.h
 create mode 100644 tools/testing/selftests/bpf/bpfilter/test_rule.c

diff --git a/net/bpfilter/Makefile b/net/bpfilter/Makefile
index a7c643a1b52a..3f7c5c28cca2 100644
--- a/net/bpfilter/Makefile
+++ b/net/bpfilter/Makefile
@@ -11,7 +11,7 @@ $(LIBBPF_A):
 	$(Q)$(MAKE) -C $(LIBBPF_SRCS) O=$(LIBBPF_OUT)/ OUTPUT=$(LIBBPF_OUT)/ $(LIBBPF_OUT)/libbpf.a
 
 userprogs := bpfilter_umh
-bpfilter_umh-objs := main.o map-common.o codegen.o context.o match.o target.o
+bpfilter_umh-objs := main.o map-common.o codegen.o context.o match.o target.o rule.o
 bpfilter_umh-objs += xt_udp.o
 bpfilter_umh-userldlibs := $(LIBBPF_A) -lelf -lz
 userccflags += -I $(srctree)/tools/include/ -I $(srctree)/tools/include/uapi -I $(srctree)/tools/lib
diff --git a/net/bpfilter/rule.c b/net/bpfilter/rule.c
new file mode 100644
index 000000000000..4b6a7f10db5b
--- /dev/null
+++ b/net/bpfilter/rule.c
@@ -0,0 +1,239 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Copyright (c) 2021 Telegram FZ-LLC
+ */
+
+#define _GNU_SOURCE
+
+#include "rule.h"
+
+#include "../../include/uapi/linux/bpfilter.h"
+
+#include <linux/err.h>
+#include <linux/if_ether.h>
+#include <linux/netfilter/x_tables.h>
+#include <linux/netfilter_ipv4/ip_tables.h>
+
+#include <errno.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include "codegen.h"
+#include "context.h"
+#include "match.h"
+
+static const struct bpfilter_ipt_target *
+ipt_entry_target(const struct bpfilter_ipt_entry *ipt_entry)
+{
+	return (const void *)ipt_entry + ipt_entry->target_offset;
+}
+
+static const struct bpfilter_ipt_match *ipt_entry_match(const struct bpfilter_ipt_entry *entry,
+							size_t offset)
+{
+	return (const void *)entry + offset;
+}
+
+static int ipt_entry_num_matches(const struct bpfilter_ipt_entry *ipt_entry)
+{
+	const struct bpfilter_ipt_match *ipt_match;
+	uint32_t offset = sizeof(*ipt_entry);
+	int num_matches = 0;
+
+	while (offset < ipt_entry->target_offset) {
+		ipt_match = ipt_entry_match(ipt_entry, offset);
+
+		if ((uintptr_t)ipt_match % __alignof__(struct bpfilter_ipt_match))
+			return -EINVAL;
+
+		if (ipt_entry->target_offset < offset + sizeof(*ipt_match))
+			return -EINVAL;
+
+		if (ipt_match->u.match_size < sizeof(*ipt_match))
+			return -EINVAL;
+
+		if (ipt_entry->target_offset < offset + ipt_match->u.match_size)
+			return -EINVAL;
+
+		++num_matches;
+		offset += ipt_match->u.match_size;
+	}
+
+	if (offset != ipt_entry->target_offset)
+		return -EINVAL;
+
+	return num_matches;
+}
+
+static int init_rule_matches(struct context *ctx, const struct bpfilter_ipt_entry *ipt_entry,
+			     struct rule *rule)
+{
+	const struct bpfilter_ipt_match *ipt_match;
+	uint32_t offset = sizeof(*ipt_entry);
+	struct match *match;
+	int err;
+
+	rule->matches = calloc(rule->num_matches, sizeof(rule->matches[0]));
+	if (!rule->matches)
+		return -ENOMEM;
+
+	match = rule->matches;
+	while (offset < ipt_entry->target_offset) {
+		ipt_match = ipt_entry_match(ipt_entry, offset);
+		err = init_match(ctx, ipt_match, match);
+		if (err) {
+			free(rule->matches);
+			rule->matches = NULL;
+			return err;
+		}
+
+		++match;
+		offset += ipt_match->u.match_size;
+	}
+
+	return 0;
+}
+
+static int check_ipt_entry_ip(const struct bpfilter_ipt_ip *ip)
+{
+	if (ip->flags & ~BPFILTER_IPT_F_MASK)
+		return -EINVAL;
+
+	if (ip->invflags & ~BPFILTER_IPT_INV_MASK)
+		return -EINVAL;
+
+	return 0;
+}
+
+bool rule_has_standard_target(const struct rule *rule)
+{
+	return rule->target.target_ops == &standard_target_ops;
+}
+
+bool is_rule_unconditional(const struct rule *rule)
+{
+	static const struct bpfilter_ipt_ip unconditional;
+
+	if (rule->num_matches)
+		return false;
+
+	return !memcmp(&rule->ipt_entry->ip, &unconditional, sizeof(unconditional));
+}
+
+int init_rule(struct context *ctx, const struct bpfilter_ipt_entry *ipt_entry, struct rule *rule)
+{
+	const struct bpfilter_ipt_target *ipt_target;
+	int err;
+
+	err = check_ipt_entry_ip(&ipt_entry->ip);
+	if (err)
+		return err;
+
+	if (ipt_entry->target_offset < sizeof(*ipt_entry))
+		return -EINVAL;
+
+	if (ipt_entry->next_offset < ipt_entry->target_offset + sizeof(*ipt_target))
+		return -EINVAL;
+
+	ipt_target = ipt_entry_target(ipt_entry);
+
+	if (ipt_target->u.target_size < sizeof(*ipt_target))
+		return -EINVAL;
+
+	if (ipt_entry->next_offset < ipt_entry->target_offset + ipt_target->u.target_size)
+		return -EINVAL;
+
+	rule->ipt_entry = ipt_entry;
+
+	err = init_target(ctx, ipt_target, &rule->target);
+	if (err)
+		return err;
+
+	if (rule_has_standard_target(rule)) {
+		if (XT_ALIGN(ipt_entry->target_offset +
+			     sizeof(struct bpfilter_ipt_standard_target)) != ipt_entry->next_offset)
+			return -EINVAL;
+	}
+
+	rule->num_matches = ipt_entry_num_matches(ipt_entry);
+	if (rule->num_matches < 0)
+		return rule->num_matches;
+
+	return init_rule_matches(ctx, ipt_entry, rule);
+}
+
+int gen_inline_rule(struct codegen *ctx, const struct rule *rule)
+{
+	int i, err;
+
+	const struct bpfilter_ipt_ip *ipt_ip = &rule->ipt_entry->ip;
+
+	if (!ipt_ip->src_mask && !ipt_ip->src) {
+		if (ipt_ip->invflags & IPT_INV_SRCIP)
+			return 0;
+	}
+
+	if (!ipt_ip->dst_mask && !ipt_ip->dst) {
+		if (ipt_ip->invflags & IPT_INV_DSTIP)
+			return 0;
+	}
+
+	if (ipt_ip->src_mask || ipt_ip->src) {
+		const int op = ipt_ip->invflags & IPT_INV_SRCIP ? BPF_JEQ : BPF_JNE;
+
+		EMIT(ctx, BPF_LDX_MEM(BPF_W, CODEGEN_REG_SCRATCH1, CODEGEN_REG_L3,
+				      offsetof(struct iphdr, saddr)));
+		EMIT(ctx, BPF_ALU32_IMM(BPF_AND, CODEGEN_REG_SCRATCH1, ipt_ip->src_mask));
+		EMIT_FIXUP(ctx, CODEGEN_FIXUP_NEXT_RULE,
+			   BPF_JMP_IMM(op, CODEGEN_REG_SCRATCH1, ipt_ip->src, 0));
+	}
+
+	if (ipt_ip->dst_mask || ipt_ip->dst) {
+		const int op = ipt_ip->invflags & IPT_INV_DSTIP ? BPF_JEQ : BPF_JNE;
+
+		EMIT(ctx, BPF_LDX_MEM(BPF_W, CODEGEN_REG_SCRATCH2, CODEGEN_REG_L3,
+				      offsetof(struct iphdr, daddr)));
+		EMIT(ctx, BPF_ALU32_IMM(BPF_AND, CODEGEN_REG_SCRATCH2, ipt_ip->dst_mask));
+		EMIT_FIXUP(ctx, CODEGEN_FIXUP_NEXT_RULE,
+			   BPF_JMP_IMM(op, CODEGEN_REG_SCRATCH2, ipt_ip->dst, 0));
+	}
+
+	if (ipt_ip->protocol) {
+		EMIT(ctx, BPF_LDX_MEM(BPF_B, CODEGEN_REG_SCRATCH4, CODEGEN_REG_L3,
+				      offsetof(struct iphdr, protocol)));
+		EMIT_FIXUP(ctx, CODEGEN_FIXUP_NEXT_RULE,
+			   BPF_JMP_IMM(BPF_JNE, CODEGEN_REG_SCRATCH4, ipt_ip->protocol, 0));
+
+		EMIT(ctx, BPF_LDX_MEM(BPF_B, CODEGEN_REG_SCRATCH4, CODEGEN_REG_L3,
+				      offsetof(struct iphdr, protocol)));
+		EMIT(ctx, BPF_MOV64_REG(CODEGEN_REG_L4, CODEGEN_REG_L3));
+		EMIT(ctx, BPF_LDX_MEM(BPF_B, CODEGEN_REG_SCRATCH1, CODEGEN_REG_L3, 0));
+		EMIT(ctx, BPF_ALU32_IMM(BPF_AND, CODEGEN_REG_SCRATCH1, 0x0f));
+		EMIT(ctx, BPF_ALU32_IMM(BPF_LSH, CODEGEN_REG_SCRATCH1, 2));
+		EMIT(ctx, BPF_ALU64_REG(BPF_ADD, CODEGEN_REG_L4, CODEGEN_REG_SCRATCH1));
+	}
+
+	for (i = 0; i < rule->num_matches; ++i) {
+		const struct match *match;
+
+		match = &rule->matches[i];
+		err = match->match_ops->gen_inline(ctx, match);
+		if (err)
+			return err;
+	}
+
+	EMIT_ADD_COUNTER(ctx);
+
+	err = rule->target.target_ops->gen_inline(ctx, &rule->target);
+	if (err)
+		return err;
+
+	codegen_fixup(ctx, CODEGEN_FIXUP_NEXT_RULE);
+
+	return 0;
+}
+
+void free_rule(struct rule *rule)
+{
+	free(rule->matches);
+}
diff --git a/net/bpfilter/rule.h b/net/bpfilter/rule.h
new file mode 100644
index 000000000000..a19e698b5976
--- /dev/null
+++ b/net/bpfilter/rule.h
@@ -0,0 +1,34 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+/*
+ * Copyright (c) 2021 Telegram FZ-LLC
+ */
+
+#ifndef NET_BPFILTER_RULE_H
+#define NET_BPFILTER_RULE_H
+
+#include <stdint.h>
+#include <stdbool.h>
+
+#include "target.h"
+
+struct bpfilter_ipt_entry;
+struct codegen;
+struct context;
+struct match;
+
+struct rule {
+	const struct bpfilter_ipt_entry *ipt_entry;
+	uint32_t came_from;
+	uint32_t hook_mask;
+	uint16_t num_matches;
+	struct match *matches;
+	struct target target;
+};
+
+bool rule_has_standard_target(const struct rule *rule);
+bool is_rule_unconditional(const struct rule *rule);
+int init_rule(struct context *ctx, const struct bpfilter_ipt_entry *ipt_entry, struct rule *rule);
+int gen_inline_rule(struct codegen *ctx, const struct rule *rule);
+void free_rule(struct rule *rule);
+
+#endif // NET_BPFILTER_RULE_H
diff --git a/tools/testing/selftests/bpf/bpfilter/.gitignore b/tools/testing/selftests/bpf/bpfilter/.gitignore
index 89912a44109f..a934ddef58d2 100644
--- a/tools/testing/selftests/bpf/bpfilter/.gitignore
+++ b/tools/testing/selftests/bpf/bpfilter/.gitignore
@@ -4,3 +4,4 @@ test_map
 test_match
 test_xt_udp
 test_target
+test_rule
diff --git a/tools/testing/selftests/bpf/bpfilter/Makefile b/tools/testing/selftests/bpf/bpfilter/Makefile
index 670f28413e42..779add65fa27 100644
--- a/tools/testing/selftests/bpf/bpfilter/Makefile
+++ b/tools/testing/selftests/bpf/bpfilter/Makefile
@@ -14,6 +14,7 @@ TEST_GEN_PROGS += test_map
 TEST_GEN_PROGS += test_match
 TEST_GEN_PROGS += test_xt_udp
 TEST_GEN_PROGS += test_target
+TEST_GEN_PROGS += test_rule
 
 KSFT_KHDR_INSTALL := 1
 
@@ -40,12 +41,14 @@ BPFILTER_MAP_SRCS := $(BPFILTERSRCDIR)/map-common.c
 BPFILTER_CODEGEN_SRCS := $(BPFILTERSRCDIR)/codegen.c $(BPFOBJ) -lelf -lz
 BPFILTER_MATCH_SRCS := $(BPFILTERSRCDIR)/match.c $(BPFILTERSRCDIR)/xt_udp.c
 BPFILTER_TARGET_SRCS := $(BPFILTERSRCDIR)/target.c
+BPFILTER_RULE_SRCS := $(BPFILTERSRCDIR)/rule.c
 
 BPFILTER_COMMON_SRCS := $(BPFILTERSRCDIR)/context.c
 BPFILTER_COMMON_SRCS += $(BPFILTER_MAP_SRCS) $(BPFILTER_CODEGEN_SRCS) $(BPFILTER_MATCH_SRCS)
-BPFILTER_COMMON_SRCS += $(BPFILTER_TARGET_SRCS)
+BPFILTER_COMMON_SRCS += $(BPFILTER_TARGET_SRCS) $(BPFILTER_RULE_SRCS)
 
 $(OUTPUT)/test_map: test_map.c $(BPFILTER_MAP_SRCS)
 $(OUTPUT)/test_match: test_match.c $(BPFILTER_COMMON_SRCS)
 $(OUTPUT)/test_xt_udp: test_xt_udp.c $(BPFILTER_COMMON_SRCS)
 $(OUTPUT)/test_target: test_target.c $(BPFILTER_COMMON_SRCS)
+$(OUTPUT)/test_rule: test_rule.c $(BPFILTER_COMMON_SRCS)
diff --git a/tools/testing/selftests/bpf/bpfilter/bpfilter_util.h b/tools/testing/selftests/bpf/bpfilter/bpfilter_util.h
index 945633c5415e..07cfe24d763d 100644
--- a/tools/testing/selftests/bpf/bpfilter/bpfilter_util.h
+++ b/tools/testing/selftests/bpf/bpfilter/bpfilter_util.h
@@ -5,6 +5,7 @@
 
 #include <linux/bpfilter.h>
 #include <linux/netfilter/x_tables.h>
+#include <linux/netfilter_ipv4/ip_tables.h>
 
 #include <stdint.h>
 #include <stdio.h>
@@ -39,4 +40,11 @@ static inline void init_error_target(struct xt_error_target *ipt_target, int rev
 	snprintf(ipt_target->errorname, sizeof(ipt_target->errorname), "%s", error_name);
 }
 
+static inline void init_standard_entry(struct ipt_entry *entry, __u16 matches_size)
+{
+	memset(entry, 0, sizeof(*entry));
+	entry->target_offset = sizeof(*entry) + matches_size;
+	entry->next_offset = sizeof(*entry) + matches_size + sizeof(struct xt_standard_target);
+}
+
 #endif // BPFILTER_UTIL_H
diff --git a/tools/testing/selftests/bpf/bpfilter/test_rule.c b/tools/testing/selftests/bpf/bpfilter/test_rule.c
new file mode 100644
index 000000000000..fe12adf32fe5
--- /dev/null
+++ b/tools/testing/selftests/bpf/bpfilter/test_rule.c
@@ -0,0 +1,55 @@
+// SPDX-License-Identifier: GPL-2.0
+
+#define _GNU_SOURCE
+
+#include "rule.h"
+
+#include <linux/bpfilter.h>
+#include <linux/err.h>
+
+#include <linux/netfilter_ipv4/ip_tables.h>
+
+#include <stdio.h>
+#include <stdlib.h>
+
+#include "../../kselftest_harness.h"
+
+#include "context.h"
+#include "rule.h"
+
+#include "bpfilter_util.h"
+
+FIXTURE(test_standard_rule)
+{
+	struct context ctx;
+	struct {
+		struct ipt_entry entry;
+		struct xt_standard_target target;
+	} entry;
+	struct rule rule;
+};
+
+FIXTURE_SETUP(test_standard_rule)
+{
+	const int verdict = BPFILTER_NF_ACCEPT;
+
+	ASSERT_EQ(create_context(&self->ctx), 0);
+	self->ctx.log_file = stderr;
+
+	init_standard_entry(&self->entry.entry, 0);
+	init_standard_target(&self->entry.target, 0, -verdict - 1);
+}
+
+FIXTURE_TEARDOWN(test_standard_rule)
+{
+	free_rule(&self->rule);
+	free_context(&self->ctx);
+}
+
+TEST_F(test_standard_rule, init)
+{
+	ASSERT_EQ(0, init_rule(&self->ctx, (const struct bpfilter_ipt_entry *)&self->entry.entry,
+			       &self->rule));
+}
+
+TEST_HARNESS_MAIN
-- 
2.25.1


  parent reply	other threads:[~2021-08-29 18:36 UTC|newest]

Thread overview: 24+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
2021-08-29 18:35 [PATCH bpf-next v2 00/13] bpfilter Dmitrii Banshchikov
2021-08-29 18:35 ` [PATCH bpf-next v2 01/13] bpfilter: Add types for usermode helper Dmitrii Banshchikov
2021-08-29 18:35 ` [PATCH bpf-next v2 02/13] bpfilter: Add logging facility Dmitrii Banshchikov
2021-08-29 18:35 ` [PATCH bpf-next v2 03/13] tools: Add bpfilter usermode helper header Dmitrii Banshchikov
2021-08-29 18:35 ` [PATCH bpf-next v2 04/13] bpfilter: Add map container Dmitrii Banshchikov
2021-08-29 18:36 ` [PATCH bpf-next v2 05/13] bpfilter: Add codegen infrastructure Dmitrii Banshchikov
2021-08-29 18:36 ` [PATCH bpf-next v2 06/13] bpfilter: Add struct match Dmitrii Banshchikov
2021-08-29 18:36 ` [PATCH bpf-next v2 07/13] bpfilter: Add struct target Dmitrii Banshchikov
2021-08-29 18:36 ` Dmitrii Banshchikov [this message]
2021-08-29 18:36 ` [PATCH bpf-next v2 09/13] bpfilter: Add struct table Dmitrii Banshchikov
2021-08-29 18:36 ` [PATCH bpf-next v2 10/13] bpfilter: Add table codegen Dmitrii Banshchikov
2021-08-29 18:36 ` [PATCH bpf-next v2 11/13] bpfilter: Add handling of setsockopt() calls Dmitrii Banshchikov
2021-08-29 18:36 ` [PATCH bpf-next v2 12/13] bpfilter: Add filter table Dmitrii Banshchikov
2021-08-30 19:45   ` Alexei Starovoitov
2021-08-30 20:54     ` Dmitrii Banshchikov
2021-08-30 23:45       ` Alexei Starovoitov
2021-08-31 12:52         ` Dmitrii Banshchikov
2021-08-31 15:45           ` Alexei Starovoitov
2021-08-29 18:36 ` [PATCH bpf-next v2 13/13] bpfilter: Handle setsockopts Dmitrii Banshchikov
2021-08-29 19:13 ` [PATCH bpf-next v2 00/13] bpfilter Raymond Burkholder
2021-08-30 12:54   ` Dmitrii Banshchikov
2021-08-31  1:56 ` Jamal Hadi Salim
2021-08-31 12:48   ` Dmitrii Banshchikov
2021-08-31 13:38     ` Jamal Hadi Salim

Reply instructions:

You may reply publicly to this message via plain-text email
using any one of the following methods:

* Save the following mbox file, import it into your mail client,
  and reply-to-all from there: mbox

  Avoid top-posting and favor interleaved quoting:
  https://en.wikipedia.org/wiki/Posting_style#Interleaved_style

* Reply using the --to, --cc, and --in-reply-to
  switches of git-send-email(1):

  git send-email \
    --in-reply-to=20210829183608.2297877-9-me@ubique.spb.ru \
    --to=me@ubique.spb.ru \
    --cc=andrii@kernel.org \
    --cc=ast@kernel.org \
    --cc=bpf@vger.kernel.org \
    --cc=daniel@iogearbox.net \
    --cc=davem@davemloft.net \
    --cc=john.fastabend@gmail.com \
    --cc=kafai@fb.com \
    --cc=kpsingh@kernel.org \
    --cc=netdev@vger.kernel.org \
    --cc=rdna@fb.com \
    --cc=songliubraving@fb.com \
    --cc=yhs@fb.com \
    /path/to/YOUR_REPLY

  https://kernel.org/pub/software/scm/git/docs/git-send-email.html

* If your mail client supports setting the In-Reply-To header
  via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line before the message body.
This is an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.