#endif
 BPF_PROG_TYPE(BPF_PROG_TYPE_SYSCALL, bpf_syscall,
              void *, void *)
+#ifdef CONFIG_NETFILTER
+BPF_PROG_TYPE(BPF_PROG_TYPE_NETFILTER, netfilter,
+             struct bpf_nf_ctx, struct bpf_nf_ctx)
+#endif
 
 BPF_MAP_TYPE(BPF_MAP_TYPE_ARRAY, array_map_ops)
 BPF_MAP_TYPE(BPF_MAP_TYPE_PERCPU_ARRAY, percpu_array_map_ops)
 
 #include <linux/bsearch.h>
 #include <linux/kobject.h>
 #include <linux/sysfs.h>
+
+#include <net/netfilter/nf_bpf_link.h>
+
 #include <net/sock.h>
 #include "../tools/lib/bpf/relo_core.h"
 
        BTF_KFUNC_HOOK_SK_SKB,
        BTF_KFUNC_HOOK_SOCKET_FILTER,
        BTF_KFUNC_HOOK_LWT,
+       BTF_KFUNC_HOOK_NETFILTER,
        BTF_KFUNC_HOOK_MAX,
 };
 
        case BPF_PROG_TYPE_LWT_XMIT:
        case BPF_PROG_TYPE_LWT_SEG6LOCAL:
                return BTF_KFUNC_HOOK_LWT;
+       case BPF_PROG_TYPE_NETFILTER:
+               return BTF_KFUNC_HOOK_NETFILTER;
        default:
                return BTF_KFUNC_HOOK_MAX;
        }
 
        ret = ret ?: register_btf_kfunc_id_set(BPF_PROG_TYPE_LWT_IN, &bpf_kfunc_set_skb);
        ret = ret ?: register_btf_kfunc_id_set(BPF_PROG_TYPE_LWT_XMIT, &bpf_kfunc_set_skb);
        ret = ret ?: register_btf_kfunc_id_set(BPF_PROG_TYPE_LWT_SEG6LOCAL, &bpf_kfunc_set_skb);
+       ret = ret ?: register_btf_kfunc_id_set(BPF_PROG_TYPE_NETFILTER, &bpf_kfunc_set_skb);
        return ret ?: register_btf_kfunc_id_set(BPF_PROG_TYPE_XDP, &bpf_kfunc_set_xdp);
 }
 late_initcall(bpf_kfunc_init);
 
 // SPDX-License-Identifier: GPL-2.0
 #include <linux/bpf.h>
+#include <linux/filter.h>
 #include <linux/netfilter.h>
 
 #include <net/netfilter/nf_bpf_link.h>
 static unsigned int nf_hook_run_bpf(void *bpf_prog, struct sk_buff *skb,
                                    const struct nf_hook_state *s)
 {
-       return NF_ACCEPT;
+       const struct bpf_prog *prog = bpf_prog;
+       struct bpf_nf_ctx ctx = {
+               .state = s,
+               .skb = skb,
+       };
+
+       return bpf_prog_run(prog, &ctx);
 }
 
 struct bpf_nf_link {
 
        return bpf_link_settle(&link_primer);
 }
+
+const struct bpf_prog_ops netfilter_prog_ops = {
+};
+
+static bool nf_ptr_to_btf_id(struct bpf_insn_access_aux *info, const char *name)
+{
+       struct btf *btf;
+       s32 type_id;
+
+       btf = bpf_get_btf_vmlinux();
+       if (IS_ERR_OR_NULL(btf))
+               return false;
+
+       type_id = btf_find_by_name_kind(btf, name, BTF_KIND_STRUCT);
+       if (WARN_ON_ONCE(type_id < 0))
+               return false;
+
+       info->btf = btf;
+       info->btf_id = type_id;
+       info->reg_type = PTR_TO_BTF_ID | PTR_TRUSTED;
+       return true;
+}
+
+static bool nf_is_valid_access(int off, int size, enum bpf_access_type type,
+                              const struct bpf_prog *prog,
+                              struct bpf_insn_access_aux *info)
+{
+       if (off < 0 || off >= sizeof(struct bpf_nf_ctx))
+               return false;
+
+       if (type == BPF_WRITE)
+               return false;
+
+       switch (off) {
+       case bpf_ctx_range(struct bpf_nf_ctx, skb):
+               if (size != sizeof_field(struct bpf_nf_ctx, skb))
+                       return false;
+
+               return nf_ptr_to_btf_id(info, "sk_buff");
+       case bpf_ctx_range(struct bpf_nf_ctx, state):
+               if (size != sizeof_field(struct bpf_nf_ctx, state))
+                       return false;
+
+               return nf_ptr_to_btf_id(info, "nf_hook_state");
+       default:
+               return false;
+       }
+
+       return false;
+}
+
+static const struct bpf_func_proto *
+bpf_nf_func_proto(enum bpf_func_id func_id, const struct bpf_prog *prog)
+{
+       return bpf_base_func_proto(func_id);
+}
+
+const struct bpf_verifier_ops netfilter_verifier_ops = {
+       .is_valid_access        = nf_is_valid_access,
+       .get_func_proto         = bpf_nf_func_proto,
+};