struct match_ops defines a polymorphic interface for matches. A match consists of pointers to struct match_ops and struct xt_entry_match which contains a payload for the match's type. The match interface supports the following operations: - check: validate a rule's match. - gen_inline: generate eBPF bytecode for the match. All match_ops structures are kept in a map by their name. Co-developed-by: Dmitrii Banshchikov <me@xxxxxxxxxxxxx> Signed-off-by: Dmitrii Banshchikov <me@xxxxxxxxxxxxx> Signed-off-by: Quentin Deslandes <qde@xxxxxxxx> --- net/bpfilter/Makefile | 1 + net/bpfilter/context.c | 43 ++++++++++++ net/bpfilter/context.h | 3 + net/bpfilter/match.c | 55 +++++++++++++++ net/bpfilter/match.h | 35 ++++++++++ .../testing/selftests/bpf/bpfilter/.gitignore | 1 + tools/testing/selftests/bpf/bpfilter/Makefile | 7 ++ .../selftests/bpf/bpfilter/bpfilter_util.h | 22 ++++++ .../selftests/bpf/bpfilter/test_match.c | 69 +++++++++++++++++++ 9 files changed, 236 insertions(+) create mode 100644 net/bpfilter/match.c create mode 100644 net/bpfilter/match.h create mode 100644 tools/testing/selftests/bpf/bpfilter/bpfilter_util.h create mode 100644 tools/testing/selftests/bpf/bpfilter/test_match.c diff --git a/net/bpfilter/Makefile b/net/bpfilter/Makefile index ac039f1fac34..2f8d867a6038 100644 --- a/net/bpfilter/Makefile +++ b/net/bpfilter/Makefile @@ -13,6 +13,7 @@ $(LIBBPF_A): userprogs := bpfilter_umh bpfilter_umh-objs := main.o logger.o map-common.o bpfilter_umh-objs += context.o codegen.o +bpfilter_umh-objs += match.o bpfilter_umh-userldlibs := $(LIBBPF_A) -lelf -lz userccflags += -I $(srctree)/tools/include/ -I $(srctree)/tools/include/uapi diff --git a/net/bpfilter/context.c b/net/bpfilter/context.c index fdfd5fe78424..b5e172412fab 100644 --- a/net/bpfilter/context.c +++ b/net/bpfilter/context.c @@ -8,11 +8,54 @@ #include "context.h" +#include <linux/kernel.h> + +#include <string.h> + +#include "logger.h" +#include "map-common.h" +#include "match.h" + +static const struct match_ops *match_ops[] = { }; + +static int init_match_ops_map(struct context *ctx) +{ + int r; + + r = create_map(&ctx->match_ops_map, ARRAY_SIZE(match_ops)); + if (r) { + BFLOG_ERR("failed to create matches map: %s", STRERR(r)); + return r; + } + + for (int i = 0; i < ARRAY_SIZE(match_ops); ++i) { + const struct match_ops *m = match_ops[i]; + + r = map_upsert(&ctx->match_ops_map, m->name, (void *)m); + if (r) { + BFLOG_ERR("failed to upsert in matches map: %s", + STRERR(r)); + return r; + } + } + + return 0; +} + int create_context(struct context *ctx) { + int r; + + r = init_match_ops_map(ctx); + if (r) { + BFLOG_ERR("failed to initialize matches map: %s", STRERR(r)); + return r; + } + return 0; } void free_context(struct context *ctx) { + free_map(&ctx->match_ops_map); } diff --git a/net/bpfilter/context.h b/net/bpfilter/context.h index df41b9707a81..e36aa8ebf57e 100644 --- a/net/bpfilter/context.h +++ b/net/bpfilter/context.h @@ -7,7 +7,10 @@ #ifndef NET_BPFILTER_CONTEXT_H #define NET_BPFILTER_CONTEXT_H +#include <search.h> + struct context { + struct hsearch_data match_ops_map; }; int create_context(struct context *ctx); diff --git a/net/bpfilter/match.c b/net/bpfilter/match.c new file mode 100644 index 000000000000..fdb0926442a8 --- /dev/null +++ b/net/bpfilter/match.c @@ -0,0 +1,55 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Copyright (c) 2021 Telegram FZ-LLC + * Copyright (c) 2022 Meta Platforms, Inc. and affiliates. + */ + +#define _GNU_SOURCE + +#include "match.h" + +#include <linux/err.h> + +#include <errno.h> +#include <string.h> + +#include "context.h" +#include "logger.h" +#include "map-common.h" + +int init_match(struct context *ctx, const struct bpfilter_ipt_match *ipt_match, + struct match *match) +{ + const size_t maxlen = sizeof(ipt_match->u.user.name); + const struct match_ops *found; + int r; + + if (strnlen(ipt_match->u.user.name, maxlen) == maxlen) { + BFLOG_ERR("failed to init match: name too long"); + return -EINVAL; + } + + found = map_find(&ctx->match_ops_map, ipt_match->u.user.name); + if (IS_ERR(found)) { + BFLOG_ERR("failed to find match by name: '%s'", + ipt_match->u.user.name); + return PTR_ERR(found); + } + + if (found->size + sizeof(*ipt_match) != ipt_match->u.match_size || + found->revision != ipt_match->u.user.revision) { + BFLOG_ERR("invalid match: '%s'", ipt_match->u.user.name); + return -EINVAL; + } + + r = found->check(ctx, ipt_match); + if (r) { + BFLOG_ERR("match check failed: %s", STRERR(r)); + return r; + } + + match->match_ops = found; + match->ipt_match = ipt_match; + + return 0; +} diff --git a/net/bpfilter/match.h b/net/bpfilter/match.h new file mode 100644 index 000000000000..c6541e6a6567 --- /dev/null +++ b/net/bpfilter/match.h @@ -0,0 +1,35 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +/* + * Copyright (c) 2021 Telegram FZ-LLC + * Copyright (c) 2022 Meta Platforms, Inc. and affiliates. + */ + +#ifndef NET_BPFILTER_MATCH_H +#define NET_BPFILTER_MATCH_H + +#include "../../include/uapi/linux/bpfilter.h" + +#include <stdint.h> + +struct bpfilter_ipt_match; +struct codegen; +struct context; +struct match; + +struct match_ops { + char name[BPFILTER_EXTENSION_MAXNAMELEN]; + uint8_t revision; + uint16_t size; + int (*check)(struct context *ctx, const struct bpfilter_ipt_match *ipt_match); + int (*gen_inline)(struct codegen *ctx, const struct match *match); +}; + +struct match { + const struct match_ops *match_ops; + const struct bpfilter_ipt_match *ipt_match; +}; + +int init_match(struct context *ctx, const struct bpfilter_ipt_match *ipt_match, + struct match *match); + +#endif // NET_BPFILTER_MATCH_H diff --git a/tools/testing/selftests/bpf/bpfilter/.gitignore b/tools/testing/selftests/bpf/bpfilter/.gitignore index 39ec0c09dff4..9ac1b3caf246 100644 --- a/tools/testing/selftests/bpf/bpfilter/.gitignore +++ b/tools/testing/selftests/bpf/bpfilter/.gitignore @@ -1,3 +1,4 @@ # SPDX-License-Identifier: GPL-2.0-only tools/** test_map +test_match diff --git a/tools/testing/selftests/bpf/bpfilter/Makefile b/tools/testing/selftests/bpf/bpfilter/Makefile index e3b8bf76a10c..10642c1d6a87 100644 --- a/tools/testing/selftests/bpf/bpfilter/Makefile +++ b/tools/testing/selftests/bpf/bpfilter/Makefile @@ -11,6 +11,7 @@ BPFDIR := $(LIBDIR)/bpf CFLAGS += -Wall -g -pthread -I$(TOOLSINCDIR) -I$(APIDIR) -I$(BPFILTERSRCDIR) TEST_GEN_PROGS += test_map +TEST_GEN_PROGS += test_match KSFT_KHDR_INSTALL := 1 @@ -34,5 +35,11 @@ $(BPFOBJ): $(wildcard $(BPFDIR)/*.[ch] $(BPFDIR)/Makefile) \ BPFILTER_MAP_SRCS := $(BPFILTERSRCDIR)/map-common.c BPFILTER_CODEGEN_SRCS := $(BPFILTERSRCDIR)/codegen.c $(BPFOBJ) -lelf -lz +BPFILTER_MATCH_SRCS := $(BPFILTERSRCDIR)/match.c + +BPFILTER_COMMON_SRCS := $(BPFILTER_MAP_SRCS) +BPFILTER_COMMON_SRCS += $(BPFILTERSRCDIR)/context.c $(BPFILTERSRCDIR)/logger.c +BPFILTER_COMMON_SRCS += $(BPFILTER_MATCH_SRCS) $(OUTPUT)/test_map: test_map.c $(BPFILTER_MAP_SRCS) +$(OUTPUT)/test_match: test_match.c $(BPFILTER_COMMON_SRCS) diff --git a/tools/testing/selftests/bpf/bpfilter/bpfilter_util.h b/tools/testing/selftests/bpf/bpfilter/bpfilter_util.h new file mode 100644 index 000000000000..705fd1777a67 --- /dev/null +++ b/tools/testing/selftests/bpf/bpfilter/bpfilter_util.h @@ -0,0 +1,22 @@ +/* SPDX-License-Identifier: GPL-2.0 */ + +#ifndef BPFILTER_UTIL_H +#define BPFILTER_UTIL_H + +#include <linux/netfilter/x_tables.h> + +#include <stdio.h> +#include <stdint.h> +#include <string.h> + +static inline void init_entry_match(struct xt_entry_match *match, + uint16_t size, uint8_t revision, + const char *name) +{ + memset(match, 0, sizeof(*match)); + sprintf(match->u.user.name, "%s", name); + match->u.user.match_size = size; + match->u.user.revision = revision; +} + +#endif // BPFILTER_UTIL_H diff --git a/tools/testing/selftests/bpf/bpfilter/test_match.c b/tools/testing/selftests/bpf/bpfilter/test_match.c new file mode 100644 index 000000000000..4a0dc1b14e4d --- /dev/null +++ b/tools/testing/selftests/bpf/bpfilter/test_match.c @@ -0,0 +1,69 @@ +// SPDX-License-Identifier: GPL-2.0 + +#define _GNU_SOURCE + +#include <linux/netfilter/x_tables.h> +#include <linux/netfilter/xt_tcpudp.h> + +#include "../../kselftest_harness.h" + +#include "context.h" +#include "logger.h" +#include "match.h" + +#include "bpfilter_util.h" + +/** + * struct udp_match - Dummy test structure. + * + * This structure provides enough space to allow for name too long, so it + * doesn't overwrite anything. + */ +struct udp_match { + struct xt_entry_match ipt_match; + char placeholder[32]; +}; + +FIXTURE(test_match_init) +{ + struct context ctx; + struct udp_match udp_match; + struct match match; +}; + +FIXTURE_SETUP(test_match_init) +{ + logger_set_file(stderr); + ASSERT_EQ(0, create_context(&self->ctx)); +}; + +FIXTURE_TEARDOWN(test_match_init) +{ + free_context(&self->ctx); +} + +TEST_F(test_match_init, name_too_long) +{ + init_entry_match(&self->udp_match.ipt_match, sizeof(self->udp_match), 0, + "this match name is supposed to be way too long..."); + + ASSERT_EQ(init_match(&self->ctx, + (const struct bpfilter_ipt_match *)&self->udp_match + .ipt_match, + &self->match), + -EINVAL); +} + +TEST_F(test_match_init, not_found) +{ + init_entry_match(&self->udp_match.ipt_match, sizeof(self->udp_match), 0, + "doesn't exist"); + + ASSERT_EQ(init_match(&self->ctx, + (const struct bpfilter_ipt_match *)&self->udp_match + .ipt_match, + &self->match), + -ENOENT); +} + +TEST_HARNESS_MAIN -- 2.38.1