diff --git a/net/bpfilter/Makefile b/net/bpfilter/Makefile
index 4a78a665b3f1..7def305f0af3 100644
--- a/net/bpfilter/Makefile
+++ b/net/bpfilter/Makefile
@@ -15,6 +15,7 @@ bpfilter_umh-objs := main.o logger.o map-common.o
 bpfilter_umh-objs += context.o codegen.o
 bpfilter_umh-objs += match.o xt_udp.o target.o rule.o table.o
 bpfilter_umh-objs += sockopt.o
+bpfilter_umh-objs += filter-table.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 81c9751a2a2d..6bb0362a79c8 100644
--- a/net/bpfilter/context.c
+++ b/net/bpfilter/context.c
@@ -13,6 +13,7 @@
 
 #include <string.h>
 
+#include "filter-table.h"
 #include "logger.h"
 #include "map-common.h"
 #include "match.h"
@@ -73,7 +74,7 @@ static int init_target_ops_map(struct context *ctx)
 	return 0;
 }
 
-static const struct table_ops *table_ops[] = {};
+static const struct table_ops *table_ops[] = { &filter_table_ops };
 
 static int init_table_ops_map(struct context *ctx)
 {
diff --git a/net/bpfilter/filter-table.c b/net/bpfilter/filter-table.c
new file mode 100644
index 000000000000..452a6d5b2fd0
--- /dev/null
+++ b/net/bpfilter/filter-table.c
@@ -0,0 +1,344 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Copyright (c) 2021 Telegram FZ-LLC
+ * Copyright (c) 2022 Meta Platforms, Inc. and affiliates.
+ */
+
+#define _GNU_SOURCE
+
+#include "filter-table.h"
+
+#include "../../include/uapi/linux/bpfilter.h"
+
+#include <linux/kernel.h>
+#include <linux/err.h>
+
+#include <errno.h>
+#include <stdlib.h>
+#include <unistd.h>
+
+#include <bpf/bpf.h>
+
+#include "codegen.h"
+#include "context.h"
+#include "logger.h"
+#include "msgfmt.h"
+#include "rule.h"
+#include "sockopt.h"
+
+struct filter_table_context {
+	struct shared_codegen shared;
+	struct codegen local_in;
+	struct codegen forward;
+	struct codegen local_out;
+};
+
+static struct table *filter_table_create(struct context *ctx,
+					 const struct bpfilter_ipt_replace *ipt_replace)
+{
+	struct table *t = create_table(ctx, ipt_replace);
+
+	if (!IS_ERR_OR_NULL(t))
+		t->table_ops = &filter_table_ops;
+
+	return t;
+}
+
+static int filter_table_codegen(struct context *ctx, struct table *table)
+{
+	struct filter_table_context *table_ctx;
+	int r;
+
+	BUG_ON(table->table_ops != &filter_table_ops);
+
+	if (table->ctx) {
+		BFLOG_ERR("filter table has no context");
+		return -EINVAL;
+	}
+
+	table_ctx = calloc(1, sizeof(*table_ctx));
+	if (!table_ctx) {
+		BFLOG_ERR("out of memory");
+		return -ENOMEM;
+	}
+
+	create_shared_codegen(&table_ctx->shared);
+
+	// INPUT chain
+	r = create_codegen(&table_ctx->local_in, BPF_PROG_TYPE_SCHED_CLS);
+	if (r) {
+		BFLOG_ERR("TC codegen for INPUT chain creation failed");
+		goto err_free_table_ctx;
+	}
+
+	table_ctx->local_in.ctx = ctx;
+	table_ctx->local_in.shared_codegen = &table_ctx->shared;
+	table_ctx->local_in.iptables_hook = BPFILTER_INET_HOOK_LOCAL_IN;
+	table_ctx->local_in.bpf_tc_hook = BPF_TC_INGRESS;
+
+	r = try_codegen(&table_ctx->local_in, table);
+	if (r) {
+		BFLOG_ERR("failed to generate LOCAL_IN code");
+		goto err_free_local_in;
+	}
+
+	// FORWARD chain
+	r = create_codegen(&table_ctx->forward, BPF_PROG_TYPE_SCHED_CLS);
+	if (r) {
+		BFLOG_ERR("TC codegen for FORWARD chain create failed");
+		goto err_free_local_in;
+	}
+
+	table_ctx->forward.ctx = ctx;
+	table_ctx->forward.shared_codegen = &table_ctx->shared;
+	table_ctx->forward.iptables_hook = BPFILTER_INET_HOOK_FORWARD;
+	table_ctx->forward.bpf_tc_hook = BPF_TC_INGRESS;
+
+	r = try_codegen(&table_ctx->forward, table);
+	if (r) {
+		BFLOG_ERR("failed to generate LOCAL_FORWARD code");
+		goto err_free_local_fwd;
+	}
+
+	// OUTPUT chain
+	r = create_codegen(&table_ctx->local_out, BPF_PROG_TYPE_SCHED_CLS);
+	if (r) {
+		BFLOG_ERR("TC codegen for OUTPUT chain creation failed");
+		goto err_free_local_fwd;
+	}
+
+	table_ctx->local_out.ctx = ctx;
+	table_ctx->local_out.shared_codegen = &table_ctx->shared;
+	table_ctx->local_out.iptables_hook = BPFILTER_INET_HOOK_LOCAL_OUT;
+	table_ctx->local_out.bpf_tc_hook = BPF_TC_EGRESS;
+
+	r = try_codegen(&table_ctx->local_out, table);
+	if (r) {
+		BFLOG_ERR("failed to generate LOCAL_OUT code");
+		goto err_free_local_out;
+	}
+
+	table->ctx = table_ctx;
+
+	return 0;
+
+err_free_local_out:
+	free_codegen(&table_ctx->local_out);
+err_free_local_fwd:
+	free_codegen(&table_ctx->forward);
+err_free_local_in:
+	free_codegen(&table_ctx->local_in);
+err_free_table_ctx:
+	free(table_ctx);
+
+	return r;
+}
+
+static int filter_table_install(struct context *ctx, struct table *table)
+{
+	struct filter_table_context *table_ctx;
+	int r;
+
+	if (!table->ctx)
+		return -EINVAL;
+
+	table_ctx = (struct filter_table_context *)table->ctx;
+
+	r = table_ctx->local_in.codegen_ops->load_img(&table_ctx->local_in);
+	if (r < 0) {
+		BFLOG_ERR("failed to load chain INPUT in table filter: %s",
+			  table_ctx->local_in.log_buf);
+		return r;
+	}
+
+	r = table_ctx->forward.codegen_ops->load_img(&table_ctx->forward);
+	if (r < 0) {
+		BFLOG_ERR("failed to load chain FORWARD in table filter: %s",
+			  table_ctx->forward.log_buf);
+		goto err_unload_local_in;
+	}
+
+	r = table_ctx->local_out.codegen_ops->load_img(&table_ctx->local_out);
+	if (r < 0) {
+		BFLOG_ERR("failed to load chain OUTPUT in table filter: %s",
+			  table_ctx->local_out.log_buf);
+		goto err_unload_forward;
+	}
+
+	BFLOG_DBG("installed filter table");
+
+	return 0;
+
+err_unload_forward:
+	table_ctx->forward.codegen_ops->unload_img(&table_ctx->forward);
+err_unload_local_in:
+	table_ctx->local_in.codegen_ops->unload_img(&table_ctx->local_in);
+
+	return r;
+}
+
+static void filter_table_uninstall(struct context *ctx, struct table *table)
+{
+	struct filter_table_context *table_ctx;
+
+	BUG_ON(!table->ctx);
+
+	table_ctx = (struct filter_table_context *)table->ctx;
+
+	table_ctx->local_in.codegen_ops->unload_img(&table_ctx->local_in);
+	table_ctx->forward.codegen_ops->unload_img(&table_ctx->forward);
+	table_ctx->local_out.codegen_ops->unload_img(&table_ctx->local_out);
+}
+
+static void filter_table_free(struct table *table)
+{
+	if (table->ctx) {
+		struct filter_table_context *table_ctx;
+
+		table_ctx = (struct filter_table_context *)table->ctx;
+
+		free_codegen(&table_ctx->local_in);
+		free_codegen(&table_ctx->forward);
+		free_codegen(&table_ctx->local_out);
+		free(table_ctx);
+	}
+
+	free_table(table);
+}
+
+static void filter_table_update_counters(struct table *table)
+{
+	int r;
+	struct rule *rule;
+	struct filter_table_context *ctx = table->ctx;
+	struct shared_codegen *shared = &ctx->shared;
+	int map_fd = shared->maps_fd[CODEGEN_MAP_COUNTERS];
+
+	for (uint32_t i = 0; i < table->num_rules; ++i) {
+		rule = &table->rules[i];
+
+		r = bpf_map_lookup_elem(map_fd, &rule->index,
+					(void *)&rule->ipt_entry->counters);
+		if (r < 0) {
+			BFLOG_DBG("couldn't fetch counter for rule at %p",
+				  rule);
+		}
+	}
+}
+
+const struct table_ops filter_table_ops = {
+	.name = "filter",
+	.create = filter_table_create,
+	.codegen = filter_table_codegen,
+	.install = filter_table_install,
+	.uninstall = filter_table_uninstall,
+	.free = filter_table_free,
+	.update_counters = filter_table_update_counters
+};
+
+static uint8_t filter_table_replace_blob[] = {
+	0x66, 0x69, 0x6c, 0x74, 0x65, 0x72, 0x00, 0x00,
+	0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+	0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+	0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+	0x0e, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00,
+	0x78, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+	0x00, 0x00, 0x00, 0x00, 0x98, 0x00, 0x00, 0x00,
+	0x30, 0x01, 0x00, 0x00,	0x00, 0x00, 0x00, 0x00,
+	0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+	0x98, 0x00, 0x00, 0x00, 0x30, 0x01, 0x00, 0x00,
+	0x00, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00,
+	0x10, 0x32, 0x40, 0x36, 0x43, 0x56, 0x00, 0x00,
+	0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+	0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+	0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+	0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+	0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+	0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+	0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+	0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+	0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+	0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+	0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+	0x70, 0x00, 0x98, 0x00, 0x00, 0x00, 0x00, 0x00,
+	0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+	0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+	0x28, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+	0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+	0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+	0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+	0xfe, 0xff, 0xff, 0xff, 0x00, 0x00, 0x00, 0x00,
+	0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+	0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+	0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+	0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+	0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+	0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+	0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+	0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+	0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+	0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+	0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+	0x70, 0x00, 0x98, 0x00, 0x00, 0x00, 0x00, 0x00,
+	0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+	0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+	0x28, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+	0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+	0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+	0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+	0xfe, 0xff, 0xff, 0xff, 0x00, 0x00, 0x00, 0x00,
+	0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+	0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+	0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+	0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+	0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+	0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+	0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+	0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+	0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+	0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+	0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+	0x70, 0x00, 0x98, 0x00, 0x00, 0x00, 0x00, 0x00,
+	0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+	0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+	0x28, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+	0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+	0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+	0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+	0xfe, 0xff, 0xff, 0xff, 0x00, 0x00, 0x00, 0x00,
+	0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+	0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+	0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+	0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+	0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+	0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+	0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+	0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+	0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+	0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+	0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+	0x70, 0x00, 0xb0, 0x00, 0x00, 0x00, 0x00, 0x00,
+	0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+	0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+	0x40, 0x00, 0x45, 0x52, 0x52, 0x4f, 0x52, 0x00,
+	0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+	0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+	0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+	0x45, 0x52, 0x52, 0x4f, 0x52, 0x00, 0x00, 0x00,
+	0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+	0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+	0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00
+};
+
+int create_filter_table(struct context *ctx)
+{
+	struct mbox_request req;
+
+	req.addr = (__u64)filter_table_replace_blob;
+	req.len = ARRAY_SIZE(filter_table_replace_blob);
+	req.is_set = 1;
+	req.cmd = BPFILTER_IPT_SO_SET_REPLACE;
+	req.pid = getpid();
+
+	return handle_sockopt_request(ctx, &req);
+}
diff --git a/net/bpfilter/filter-table.h b/net/bpfilter/filter-table.h
new file mode 100644
index 000000000000..7d5a8464456f
--- /dev/null
+++ b/net/bpfilter/filter-table.h
@@ -0,0 +1,18 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+/*
+ * Copyright (c) 2021 Telegram FZ-LLC
+ * Copyright (c) 2022 Meta Platforms, Inc. and affiliates.
+ */
+
+#ifndef NET_BPFILTER_FILTER_TABLE_H
+#define NET_BPFILTER_FILTER_TABLE_H
+
+#include "table.h"
+
+struct context;
+
+extern const struct table_ops filter_table_ops;
+
+int create_filter_table(struct context *ctx);
+
+#endif // NET_BPFILTER_FILTER_TABLE_H
diff --git a/tools/testing/selftests/bpf/bpfilter/.gitignore b/tools/testing/selftests/bpf/bpfilter/.gitignore
index a934ddef58d2..926cbb0cfb59 100644
--- a/tools/testing/selftests/bpf/bpfilter/.gitignore
+++ b/tools/testing/selftests/bpf/bpfilter/.gitignore
@@ -5,3 +5,4 @@ test_match
 test_xt_udp
 test_target
 test_rule
+test_codegen
diff --git a/tools/testing/selftests/bpf/bpfilter/Makefile b/tools/testing/selftests/bpf/bpfilter/Makefile
index 53634699d427..9eb558dfc99a 100644
--- a/tools/testing/selftests/bpf/bpfilter/Makefile
+++ b/tools/testing/selftests/bpf/bpfilter/Makefile
@@ -15,6 +15,7 @@ TEST_GEN_PROGS += test_match
 TEST_GEN_PROGS += test_xt_udp
 TEST_GEN_PROGS += test_target
 TEST_GEN_PROGS += test_rule
+TEST_GEN_PROGS += test_codegen
 
 KSFT_KHDR_INSTALL := 1
 
@@ -46,9 +47,11 @@ BPFILTER_COMMON_SRCS := $(BPFILTER_MAP_SRCS) $(BPFILTER_CODEGEN_SRCS)
 BPFILTER_COMMON_SRCS += $(BPFILTERSRCDIR)/context.c $(BPFILTERSRCDIR)/logger.c
 BPFILTER_COMMON_SRCS += $(BPFILTER_MATCH_SRCS) $(BPFILTER_TARGET_SRCS)
 BPFILTER_COMMON_SRCS += $(BPFILTER_RULE_SRCS) $(BPFILTERSRCDIR)/table.c
+BPFILTER_COMMON_SRCS +=  $(BPFILTERSRCDIR)/filter-table.c $(BPFILTERSRCDIR)/sockopt.c
 
 $(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)
+$(OUTPUT)/test_codegen: test_codegen.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 8dd7911fa06f..846b50bdab07 100644
--- a/tools/testing/selftests/bpf/bpfilter/bpfilter_util.h
+++ b/tools/testing/selftests/bpf/bpfilter/bpfilter_util.h
@@ -3,12 +3,39 @@
 #ifndef BPFILTER_UTIL_H
 #define BPFILTER_UTIL_H
 
+#include <linux/bpf.h>
 #include <linux/netfilter/x_tables.h>
 #include <linux/netfilter_ipv4/ip_tables.h>
 
 #include <stdio.h>
 #include <stdint.h>
 #include <string.h>
+#include <sys/syscall.h>
+#include <unistd.h>
+
+static inline int sys_bpf(int cmd, union bpf_attr *attr, unsigned int size)
+{
+	return syscall(SYS_bpf, cmd, attr, size);
+}
+
+static inline int bpf_prog_test_run(int fd, const void *data,
+				    unsigned int data_size, uint32_t *retval)
+{
+	union bpf_attr attr = {};
+	int r;
+
+	attr.test.prog_fd = fd;
+	attr.test.data_in = (uintptr_t)data;
+	attr.test.data_size_in = data_size;
+	attr.test.repeat = 1000000;
+
+	r = sys_bpf(BPF_PROG_TEST_RUN, &attr, sizeof(attr));
+
+	if (retval)
+		*retval = attr.test.retval;
+
+	return r;
+}
 
 static inline void init_entry_match(struct xt_entry_match *match,
 				    uint16_t size, uint8_t revision,
diff --git a/tools/testing/selftests/bpf/bpfilter/test_codegen.c b/tools/testing/selftests/bpf/bpfilter/test_codegen.c
new file mode 100644
index 000000000000..9f11b7b8a126
--- /dev/null
+++ b/tools/testing/selftests/bpf/bpfilter/test_codegen.c
@@ -0,0 +1,338 @@
+// SPDX-License-Identifier: GPL-2.0
+
+#define _GNU_SOURCE
+
+#include <linux/bpf.h>
+#include <linux/bpfilter.h>
+
+#include <linux/err.h>
+#include <linux/pkt_cls.h>
+
+#include "../../kselftest_harness.h"
+
+#include <stdint.h>
+
+#include "codegen.h"
+#include "context.h"
+#include "filter-table.h"
+#include "logger.h"
+#include "table.h"
+
+#include "bpfilter_util.h"
+
+FIXTURE(test_codegen)
+{
+	struct context ctx;
+	struct shared_codegen shared_codegen;
+	struct codegen codegen;
+	struct table *table;
+	int prog_fd;
+	uint32_t retval;
+	union bpf_attr attr;
+};
+
+FIXTURE_VARIANT(test_codegen)
+{
+	const struct bpfilter_ipt_replace *replace_blob;
+	size_t replace_blob_size;
+	const uint8_t *packet;
+	size_t packet_size;
+	enum bpf_prog_type prog_type;
+	int hook;
+	int expected_retval;
+};
+
+/*
+ *  Generated by iptables-save v1.8.2 on Sat May  8 05:22:41 2021
+ * *filter
+ * :INPUT ACCEPT [0:0]
+ * :FORWARD ACCEPT [0:0]
+ * :OUTPUT ACCEPT [0:0]
+ * -A INPUT -s 1.1.1.1/32 -d 2.2.2.2/32 -j DROP
+ * -A INPUT -s 2.2.0.0/16 -d 3.0.0.0/8 -j DROP
+ * -A INPUT -p udp -m udp --sport 100 --dport 500 -j DROP
+ * COMMIT
+ */
+
+static const uint8_t user_defined_chain_blob[] = {
+	0x66, 0x69, 0x6c, 0x74, 0x65, 0x72, 0x00, 0x00,
+	0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+	0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+	0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+	0x0e, 0x00, 0x00, 0x00, 0x07, 0x00, 0x00, 0x00,
+	0x70, 0x04, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+	0x00, 0x00, 0x00, 0x00, 0x90, 0x02, 0x00, 0x00,
+	0x28, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+	0x00, 0x00, 0x00, 0x00, 0xf8, 0x01, 0x00, 0x00,
+	0x90, 0x02, 0x00, 0x00, 0x28, 0x03, 0x00, 0x00,
+	0x00, 0x00, 0x00, 0x00, 0x06, 0x00, 0x00, 0x00,
+	0xe0, 0x26, 0x99, 0xca, 0x67, 0x55, 0x00, 0x00,
+	0x01, 0x01, 0x01, 0x01, 0x02, 0x02, 0x02, 0x02,
+	0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+	0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+	0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+	0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+	0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+	0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+	0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+	0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+	0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+	0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+	0x70, 0x00, 0x98, 0x00, 0x00, 0x00, 0x00, 0x00,
+	0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+	0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+	0x28, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+	0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+	0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+	0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+	0xff, 0xff, 0xff, 0xff, 0x00, 0x00, 0x00, 0x00,
+	0x02, 0x02, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00,
+	0xff, 0xff, 0x00, 0x00, 0xff, 0x00, 0x00, 0x00,
+	0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+	0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+	0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+	0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+	0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+	0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+	0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+	0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+	0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+	0x70, 0x00, 0x98, 0x00, 0x00, 0x00, 0x00, 0x00,
+	0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+	0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+	0x28, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+	0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+	0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+	0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+	0xff, 0xff, 0xff, 0xff, 0x00, 0x00, 0x00, 0x00,
+	0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+	0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+	0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+	0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+	0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+	0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+	0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+	0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+	0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+	0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+	0x11, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+	0xa0, 0x00, 0xc8, 0x00, 0x00, 0x00, 0x00, 0x00,
+	0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+	0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+	0x30, 0x00, 0x75, 0x64, 0x70, 0x00, 0x00, 0x00,
+	0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+	0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+	0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+	0x64, 0x00, 0x64, 0x00, 0xf4, 0x01, 0xf4, 0x01,
+	0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+	0x28, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+	0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+	0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+	0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+	0xff, 0xff, 0xff, 0xff, 0x00, 0x00, 0x00, 0x00,
+	0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+	0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+	0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+	0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+	0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+	0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+	0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+	0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+	0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+	0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+	0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+	0x70, 0x00, 0x98, 0x00, 0x00, 0x00, 0x00, 0x00,
+	0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+	0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+	0x28, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+	0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+	0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+	0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+	0xfe, 0xff, 0xff, 0xff, 0x00, 0x00, 0x00, 0x00,
+	0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+	0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+	0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+	0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+	0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+	0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+	0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+	0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+	0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+	0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+	0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+	0x70, 0x00, 0x98, 0x00, 0x00, 0x00, 0x00, 0x00,
+	0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+	0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+	0x28, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+	0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+	0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+	0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+	0xfe, 0xff, 0xff, 0xff, 0x00, 0x00, 0x00, 0x00,
+	0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+	0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+	0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+	0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+	0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+	0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+	0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+	0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+	0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+	0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+	0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+	0x70, 0x00, 0x98, 0x00, 0x00, 0x00, 0x00, 0x00,
+	0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+	0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+	0x28, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+	0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+	0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+	0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+	0xfe, 0xff, 0xff, 0xff, 0x00, 0x00, 0x00, 0x00,
+	0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+	0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+	0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+	0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+	0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+	0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+	0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+	0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+	0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+	0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+	0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+	0x70, 0x00, 0xb0, 0x00, 0x00, 0x00, 0x00, 0x00,
+	0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+	0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+	0x40, 0x00, 0x45, 0x52, 0x52, 0x4f, 0x52, 0x00,
+	0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+	0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+	0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+	0x45, 0x52, 0x52, 0x4f, 0x52, 0x00, 0x00, 0x00,
+	0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+	0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+	0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00
+};
+
+// Generated by scapy
+// Ether(src='00:11:22:33:44:55',dst='66:77:88:99:aa:bb')/IP(src='1.1.1.1',dst='2.2.2.2')/UDP(sport=100,dport=200)
+static const uint8_t udp_packet_1[] = {
+	0x66, 0x77, 0x88, 0x99, 0xaa, 0xbb, 0x00, 0x11,
+	0x22, 0x33, 0x44, 0x55, 0x08, 0x00, 0x45, 0x00,
+	0x00, 0x1c, 0x00, 0x01, 0x00, 0x00, 0x40, 0x11,
+	0x74, 0xcb, 0x01, 0x01, 0x01, 0x01, 0x02, 0x02,
+	0x02, 0x02, 0x00, 0x64, 0x00, 0xc8, 0x00, 0x08,
+	0xf8, 0xac,
+};
+
+// Ether(src='00:11:22:33:44:55',dst='66:77:88:99:aa:bb')/IP(src='2.2.2.2',dst='3.1.4.1')/UDP(sport=100,dport=200)
+static const uint8_t udp_packet_2[] = {
+	0x66, 0x77, 0x88, 0x99, 0xaa, 0xbb, 0x00, 0x11,
+	0x22, 0x33, 0x44, 0x55, 0x08, 0x00, 0x45, 0x00,
+	0x00, 0x1c, 0x00, 0x01, 0x00, 0x00, 0x40, 0x11,
+	0x6f, 0xcb, 0x02, 0x02, 0x02, 0x02, 0x03, 0x01,
+	0x04, 0x01, 0x00, 0x64, 0x00, 0xc8, 0x00, 0x08,
+	0xf3, 0xac,
+};
+
+// Ether(src='00:11:22:33:44:55',dst='66:77:88:99:aa:bb')/IP(src='2.7.1.8',dst='3.1.4.1')/UDP(sport=100,dport=500)
+static const uint8_t udp_packet_3[] = {
+	0x66, 0x77, 0x88, 0x99, 0xaa, 0xbb, 0x00, 0x11,
+	0x22, 0x33, 0x44, 0x55, 0x08, 0x00, 0x45, 0x00,
+	0x00, 0x1c, 0x00, 0x01, 0x00, 0x00, 0x40, 0x11,
+	0x70, 0xc0, 0x02, 0x07, 0x01, 0x08, 0x03, 0x01,
+	0x04, 0x01, 0x00, 0x64, 0x01, 0xf4, 0x00, 0x08,
+	0xf3, 0x75,
+};
+
+// Ether(src='00:11:22:33:44:55',dst='66:77:88:99:aa:bb')/IP(src='5.5.5.5',dst='5.5.5.5')/UDP(sport=300,dport=300)
+static const uint8_t udp_packet_4[] = {
+	0x66, 0x77, 0x88, 0x99, 0xaa, 0xbb, 0x00, 0x11,
+	0x22, 0x33, 0x44, 0x55, 0x08, 0x00, 0x45, 0x00,
+	0x00, 0x1c, 0x00, 0x01, 0x00, 0x00, 0x40, 0x11,
+	0x66, 0xbd, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05,
+	0x05, 0x05, 0x01, 0x2c, 0x01, 0x2c, 0x00, 0x08,
+	0xe9, 0x72,
+};
+
+FIXTURE_VARIANT_ADD(test_codegen, drop_by_ip_tc) {
+	.replace_blob = (const struct bpfilter_ipt_replace *)user_defined_chain_blob,
+	.replace_blob_size = ARRAY_SIZE(user_defined_chain_blob),
+	.packet = udp_packet_1,
+	.packet_size = ARRAY_SIZE(udp_packet_1),
+	.prog_type = BPF_PROG_TYPE_SCHED_CLS,
+	.hook = BPFILTER_INET_HOOK_LOCAL_IN,
+	.expected_retval = TC_ACT_SHOT,
+};
+
+FIXTURE_VARIANT_ADD(test_codegen, drop_by_net_tc) {
+	.replace_blob = (const struct bpfilter_ipt_replace *)user_defined_chain_blob,
+	.replace_blob_size = ARRAY_SIZE(user_defined_chain_blob),
+	.packet = udp_packet_2,
+	.packet_size = ARRAY_SIZE(udp_packet_2),
+	.prog_type = BPF_PROG_TYPE_SCHED_CLS,
+	.hook = BPFILTER_INET_HOOK_LOCAL_IN,
+	.expected_retval = TC_ACT_SHOT,
+};
+
+FIXTURE_VARIANT_ADD(test_codegen, drop_by_udp_port_tc) {
+	.replace_blob = (const struct bpfilter_ipt_replace *)user_defined_chain_blob,
+	.replace_blob_size = ARRAY_SIZE(user_defined_chain_blob),
+	.packet = udp_packet_3,
+	.packet_size = ARRAY_SIZE(udp_packet_3),
+	.prog_type = BPF_PROG_TYPE_SCHED_CLS,
+	.hook = BPFILTER_INET_HOOK_LOCAL_IN,
+	.expected_retval = TC_ACT_SHOT,
+};
+
+FIXTURE_VARIANT_ADD(test_codegen, accept_tc) {
+	.replace_blob = (const struct bpfilter_ipt_replace *)user_defined_chain_blob,
+	.replace_blob_size = ARRAY_SIZE(user_defined_chain_blob),
+	.packet = udp_packet_4,
+	.packet_size = ARRAY_SIZE(udp_packet_4),
+	.prog_type = BPF_PROG_TYPE_SCHED_CLS,
+	.hook = BPFILTER_INET_HOOK_LOCAL_IN,
+	.expected_retval = TC_ACT_UNSPEC,
+};
+
+FIXTURE_SETUP(test_codegen)
+{
+	logger_set_file(stderr);
+	ASSERT_EQ(create_context(&self->ctx), 0);
+
+	create_shared_codegen(&self->shared_codegen);
+	ASSERT_EQ(0, create_codegen(&self->codegen, variant->prog_type));
+
+	self->codegen.ctx = &self->ctx;
+	self->codegen.shared_codegen = &self->shared_codegen;
+	self->codegen.iptables_hook = variant->hook;
+
+	self->table = filter_table_ops.create(&self->ctx, variant->replace_blob);
+	ASSERT_FALSE(IS_ERR_OR_NULL(self->table));
+
+	ASSERT_EQ(0, try_codegen(&self->codegen, self->table));
+
+	self->prog_fd = load_img(&self->codegen);
+	ASSERT_GT(self->prog_fd, -1)
+	TH_LOG("load_img(): '%s': %s", STRERR(self->prog_fd),
+	       self->codegen.log_buf);
+};
+
+FIXTURE_TEARDOWN(test_codegen)
+{
+	filter_table_ops.free(self->table);
+	unload_img(&self->codegen);
+	free_codegen(&self->codegen);
+	free_context(&self->ctx);
+	if (self->prog_fd > -1)
+		close(self->prog_fd);
+};
+
+TEST_F(test_codegen, test_run)
+{
+	EXPECT_EQ(0, bpf_prog_test_run(self->prog_fd, variant->packet,
+				       variant->packet_size, &self->retval))
+	TH_LOG("cannot bpf_prog_test_run(): '%s'", STRERR(errno));
+	EXPECT_EQ(self->retval, variant->expected_retval)
+	TH_LOG("expected: %d, actual: %d\n", variant->expected_retval,
+	       self->retval);
+}
+
+TEST_HARNESS_MAIN
