From patchwork Sun Aug 29 18:36:03 2021 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Dmitrii Banshchikov X-Patchwork-Id: 504224 Return-Path: X-Spam-Checker-Version: SpamAssassin 3.4.0 (2014-02-07) on aws-us-west-2-korg-lkml-1.web.codeaurora.org X-Spam-Level: X-Spam-Status: No, score=-16.8 required=3.0 tests=BAYES_00,DKIM_SIGNED, DKIM_VALID, HEADER_FROM_DIFFERENT_DOMAINS, INCLUDES_CR_TRAILER, INCLUDES_PATCH, MAILING_LIST_MULTI,SPF_HELO_NONE,SPF_PASS,USER_AGENT_GIT autolearn=unavailable autolearn_force=no version=3.4.0 Received: from mail.kernel.org (mail.kernel.org [198.145.29.99]) by smtp.lore.kernel.org (Postfix) with ESMTP id 05D74C432BE for ; Sun, 29 Aug 2021 18:36:53 +0000 (UTC) Received: from vger.kernel.org (vger.kernel.org [23.128.96.18]) by mail.kernel.org (Postfix) with ESMTP id BC20860E73 for ; Sun, 29 Aug 2021 18:36:50 +0000 (UTC) Received: (majordomo@vger.kernel.org) by vger.kernel.org via listexpand id S235968AbhH2Shl (ORCPT ); Sun, 29 Aug 2021 14:37:41 -0400 Received: from lindbergh.monkeyblade.net ([23.128.96.19]:34190 "EHLO lindbergh.monkeyblade.net" rhost-flags-OK-OK-OK-OK) by vger.kernel.org with ESMTP id S235919AbhH2Shg (ORCPT ); Sun, 29 Aug 2021 14:37:36 -0400 Received: from mail-ej1-x636.google.com (mail-ej1-x636.google.com [IPv6:2a00:1450:4864:20::636]) by lindbergh.monkeyblade.net (Postfix) with ESMTPS id 0B234C061760 for ; Sun, 29 Aug 2021 11:36:43 -0700 (PDT) Received: by mail-ej1-x636.google.com with SMTP id x11so26356475ejv.0 for ; Sun, 29 Aug 2021 11:36:42 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=ubique-spb-ru.20150623.gappssmtp.com; s=20150623; h=from:to:cc:subject:date:message-id:in-reply-to:references :mime-version:content-transfer-encoding; bh=BtgTkv3mSDS8aWw4uQYLtSWS3LezVDlLHx+UPyNzf6Q=; b=FZxNkiiu+tPrRWZCWPWvHCA55MOR2gex7Fla2AClUrGIXRvpATjXzwk8xjpgNhfj4B ZUSqYdDU70IZnmWW0RWLYU/eycmZvKMRqoxIU/CS7PugZAV3qmf2YAix6L6vbRZG13qM CO6NzwwD8rWfFa2WxssM1BZ+jg9pD1JGVkAhWMfnCxoHo9wxwvQFJXaCR5yHjqOXiEOO azSSMmi4XBtIcdDKqkMPnfYleSHOG7cskWCaH/7frOI+7ktB4gh9MrD5bRZo2ljchx9U KmXSbMsZvtZloZNhEDdF+wu50jo7qs8H03jWVn3hgMv/MlOivu53nke7HsOV/liYcepG idzA== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20161025; h=x-gm-message-state:from:to:cc:subject:date:message-id:in-reply-to :references:mime-version:content-transfer-encoding; bh=BtgTkv3mSDS8aWw4uQYLtSWS3LezVDlLHx+UPyNzf6Q=; b=RDHxTBl1/mVJgif45+DhL+6NMObrwworOtjYhZOvxZocygPU+ZPolYSI7nyrevegmw Pwrv5tMBAIVUxsMwBUxPgIEiqQFx/7s9DsR2rGPlZ64rSA3asyyhN3S8nzf483moRopi Lohz+ClYbegN6vVRoKj76EXG9gA+OaqBOU6IzPpjHBqyFY1BckeDch7OsTTPyqawdk1S dSIBnbSNTlF3wNWsjntfmFrcQquc5Cu58HcClKxXqZO5Ur4hodhBrRXzWh2P0KQw9m/M z/bokMCs9KX+46GtaQEdMqxCv2bpvZcsOutqB+xd7D8c/f4hobluemNNPYfWKCOq1nuW gexw== X-Gm-Message-State: AOAM530/daDOEtfV0i9tEA3ZqgyEYcvh064IQh/ECM+Dh5eyKrJg56/N kFQFL/bBqGZcV/xwE10hETDFDA== X-Google-Smtp-Source: ABdhPJyNtp5Ml6UutfOPDN9NmiHR5hxX7mEzRu0q/1G6MYGzDTdVeKV0HU29IIQYckeG9BTURyFiSw== X-Received: by 2002:a17:907:2126:: with SMTP id qo6mr21410892ejb.476.1630262201521; Sun, 29 Aug 2021 11:36:41 -0700 (PDT) Received: from localhost ([154.21.15.43]) by smtp.gmail.com with ESMTPSA id l23sm6521425eds.29.2021.08.29.11.36.40 (version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256); Sun, 29 Aug 2021 11:36:40 -0700 (PDT) From: Dmitrii Banshchikov To: bpf@vger.kernel.org Cc: Dmitrii Banshchikov , 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 Message-Id: <20210829183608.2297877-9-me@ubique.spb.ru> X-Mailer: git-send-email 2.25.1 In-Reply-To: <20210829183608.2297877-1-me@ubique.spb.ru> References: <20210829183608.2297877-1-me@ubique.spb.ru> MIME-Version: 1.0 Precedence: bulk List-ID: X-Mailing-List: netdev@vger.kernel.org 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 --- 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 +#include +#include +#include + +#include +#include +#include + +#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 +#include + +#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 #include +#include #include #include @@ -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 +#include + +#include + +#include +#include + +#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