From fd1e008bc92606582468e27aebc6c55bae398db3 Mon Sep 17 00:00:00 2001 From: liuhuaize Date: Sat, 11 Apr 2026 01:16:22 +0800 Subject: [PATCH 1/3] doc: add bfcli matcher hook compatibility table --- doc/CMakeLists.txt | 24 ++ doc/generate_matcher_hook_compat.py | 253 ++++++++++++++++++ .../bfcli_matcher_hook_compatibility.rst | 69 +++++ doc/usage/bfcli.rst | 9 + 4 files changed, 355 insertions(+) create mode 100644 doc/generate_matcher_hook_compat.py create mode 100644 doc/usage/_generated/bfcli_matcher_hook_compatibility.rst diff --git a/doc/CMakeLists.txt b/doc/CMakeLists.txt index b5a72046a..86dd7397a 100644 --- a/doc/CMakeLists.txt +++ b/doc/CMakeLists.txt @@ -67,8 +67,13 @@ #]] find_package(Doxygen REQUIRED) +find_package(Python3 COMPONENTS Interpreter REQUIRED) find_program(SPHINX_BIN sphinx-build REQUIRED) +set(bfcli_matcher_hook_compatibility + ${CMAKE_CURRENT_SOURCE_DIR}/usage/_generated/bfcli_matcher_hook_compatibility.rst +) + file(GLOB_RECURSE bf_srcs ${CMAKE_SOURCE_DIR}/src/*.h ${CMAKE_SOURCE_DIR}/src/*.c ${CMAKE_SOURCE_DIR}/tests/harness/*.h ${CMAKE_SOURCE_DIR}/tests/harness/*.c @@ -80,6 +85,7 @@ list(FILTER bf_srcs EXCLUDE REGEX "${CMAKE_SOURCE_DIR}/src/external/.*") set(doc_srcs ${CMAKE_CURRENT_SOURCE_DIR}/index.rst ${CMAKE_CURRENT_SOURCE_DIR}/usage/bfcli.rst + ${bfcli_matcher_hook_compatibility} ${CMAKE_CURRENT_SOURCE_DIR}/usage/index.rst ${CMAKE_CURRENT_SOURCE_DIR}/developers/build.rst ${CMAKE_CURRENT_SOURCE_DIR}/developers/contributing.rst @@ -96,6 +102,24 @@ set(doc_srcs configure_file(Doxyfile.in Doxyfile) configure_file(conf.py.in conf.py) +add_custom_command( + COMMAND + ${Python3_EXECUTABLE} + ${CMAKE_CURRENT_SOURCE_DIR}/generate_matcher_hook_compat.py + --matcher-c ${CMAKE_SOURCE_DIR}/src/libbpfilter/matcher.c + --matcher-h ${CMAKE_SOURCE_DIR}/src/libbpfilter/include/bpfilter/matcher.h + --hook-h ${CMAKE_SOURCE_DIR}/src/libbpfilter/include/bpfilter/hook.h + --output ${bfcli_matcher_hook_compatibility} + DEPENDS + ${CMAKE_CURRENT_SOURCE_DIR}/generate_matcher_hook_compat.py + ${CMAKE_SOURCE_DIR}/src/libbpfilter/matcher.c + ${CMAKE_SOURCE_DIR}/src/libbpfilter/include/bpfilter/matcher.h + ${CMAKE_SOURCE_DIR}/src/libbpfilter/include/bpfilter/hook.h + OUTPUT ${bfcli_matcher_hook_compatibility} + COMMENT "Generating bfcli matcher hook compatibility table" + VERBATIM +) + add_custom_command( COMMAND Doxygen::doxygen -s -g ${CMAKE_CURRENT_BINARY_DIR}/Doxyfile.base >/dev/null OUTPUT ${CMAKE_CURRENT_BINARY_DIR}/Doxyfile.base diff --git a/doc/generate_matcher_hook_compat.py b/doc/generate_matcher_hook_compat.py new file mode 100644 index 000000000..23edbf7d8 --- /dev/null +++ b/doc/generate_matcher_hook_compat.py @@ -0,0 +1,253 @@ +#!/usr/bin/env python3 +# SPDX-License-Identifier: GPL-2.0-only + +"""Generate the bfcli matcher-to-hook compatibility table. + +The table is derived from the matcher metadata declared in +src/libbpfilter/matcher.c. Each matcher's supported hooks are computed from +the inverse of its `unsupported_hooks` bitmask. +""" + +from __future__ import annotations + +import argparse +import pathlib +import re +import sys + + +def _read(path: pathlib.Path) -> str: + return path.read_text(encoding="utf-8") + + +def _read_enum_order(path: pathlib.Path, enum_name: str, prefix: str) -> list[str]: + content = _read(path) + match = re.search( + rf"enum\s+{re.escape(enum_name)}\s*\{{(?P.*?)\n\}};", + content, + re.DOTALL, + ) + if not match: + raise ValueError(f"unable to find enum {enum_name} in {path}") + + values: list[str] = [] + for raw_line in match.group("body").splitlines(): + line = raw_line.split("//", 1)[0].split("/*", 1)[0].strip() + if not line: + continue + + token = line.rstrip(",") + if "=" in token: + token = token.split("=", 1)[0].strip() + + if token.startswith(prefix): + values.append(token) + + return values + + +def _read_string_map(path: pathlib.Path, array_name: str, prefix: str) -> dict[str, str]: + content = _read(path) + match = re.search( + rf"{re.escape(array_name)}\[\]\s*=\s*\{{(?P.*?)\n\}};", + content, + re.DOTALL, + ) + if not match: + raise ValueError(f"unable to find array {array_name} in {path}") + + mapping: dict[str, str] = {} + for enum_name, value in re.findall( + rf"\[({prefix}[A-Z0-9_]+)\]\s*=\s*\"([^\"]+)\"", + match.group("body"), + ): + mapping[enum_name] = value + + return mapping + + +def _read_hook_macros(path: pathlib.Path) -> dict[str, list[str]]: + flattened = _read(path).replace("\\\n", " ") + macros: dict[str, list[str]] = {} + for name, expr in re.findall( + r"#define\s+(_BF_HOOKS_[A-Z0-9_]+)\s+([^\n]+)", + flattened, + ): + macros[name] = [token.strip() for token in expr.split(",") if token.strip()] + + return macros + + +def _extract_initializer_blocks(content: str, array_name: str) -> dict[str, str]: + start = content.find(array_name) + if start < 0: + raise ValueError(f"unable to find {array_name}") + + array_start = content.find("{", start) + if array_start < 0: + raise ValueError(f"unable to find initializer for {array_name}") + + depth = 0 + array_end = -1 + for idx in range(array_start, len(content)): + if content[idx] == "{": + depth += 1 + elif content[idx] == "}": + depth -= 1 + if depth == 0: + array_end = idx + break + + if array_end < 0: + raise ValueError(f"unable to find end of initializer for {array_name}") + + body = content[array_start + 1 : array_end] + blocks: dict[str, str] = {} + i = 0 + while i < len(body): + if body[i] == "}": + break + + match = re.search(r"\[(BF_MATCHER_[A-Z0-9_]+)\]\s*=", body[i:]) + if not match: + break + + enum_name = match.group(1) + i += match.end() + + while i < len(body) and body[i].isspace(): + i += 1 + + if i >= len(body) or body[i] != "{": + raise ValueError(f"unable to find block start for {enum_name}") + + depth = 0 + block_start = i + while i < len(body): + if body[i] == "{": + depth += 1 + elif body[i] == "}": + depth -= 1 + if depth == 0: + blocks[enum_name] = body[block_start : i + 1] + i += 1 + break + i += 1 + + return blocks + + +def _extract_unsupported_hooks(block: str) -> list[str]: + match = re.search( + r"\.unsupported_hooks\s*=\s*BF_FLAGS\((?P.*?)\)\s*,", + block, + re.DOTALL, + ) + if not match: + return [] + + return [token.strip() for token in match.group("expr").replace("\n", " ").split(",") if token.strip()] + + +def _expand_hooks(tokens: list[str], macros: dict[str, list[str]]) -> list[str]: + expanded: list[str] = [] + for token in tokens: + if token in macros: + expanded.extend(_expand_hooks(macros[token], macros)) + else: + expanded.append(token) + + return expanded + + +def _render_table( + matcher_order: list[str], + matcher_names: dict[str, str], + hook_order: list[str], + unsupported: dict[str, list[str]], +) -> str: + lines = [ + ".. This file is auto-generated by doc/generate_matcher_hook_compat.py.", + ".. Do not edit manually.", + "", + ".. list-table::", + " :header-rows: 1", + " :widths: 2 9", + "", + " * - Matcher", + " - Supported hooks", + ] + + for matcher in matcher_order: + matcher_name = matcher_names.get(matcher) + if not matcher_name or matcher_name == "": + continue + + blocked = set(unsupported.get(matcher, [])) + supported = [hook for hook in hook_order if hook not in blocked] + rendered_hooks = ", ".join(f"``{hook}``" for hook in supported) + lines.extend( + [ + f" * - ``{matcher_name}``", + f" - {rendered_hooks}", + ] + ) + + lines.append("") + return "\n".join(lines) + + +def render( + matcher_c: pathlib.Path, + matcher_h: pathlib.Path, + hook_h: pathlib.Path, +) -> str: + matcher_content = _read(matcher_c) + matcher_order = _read_enum_order(matcher_h, "bf_matcher_type", "BF_MATCHER_") + hook_order = _read_enum_order(hook_h, "bf_hook", "BF_HOOK_") + matcher_names = _read_string_map(matcher_c, "_bf_matcher_type_strs", "BF_MATCHER_") + hook_macros = _read_hook_macros(matcher_c) + matcher_blocks = _extract_initializer_blocks(matcher_content, "_bf_matcher_metas") + + unsupported: dict[str, list[str]] = {} + for matcher, block in matcher_blocks.items(): + unsupported[matcher] = _expand_hooks(_extract_unsupported_hooks(block), hook_macros) + + return _render_table(matcher_order, matcher_names, hook_order, unsupported) + + +def generate( + matcher_c: pathlib.Path, + matcher_h: pathlib.Path, + hook_h: pathlib.Path, + output: pathlib.Path, +) -> str: + rendered = render(matcher_c, matcher_h, hook_h) + output.parent.mkdir(parents=True, exist_ok=True) + output.write_text(rendered, encoding="utf-8") + return rendered + + +def main() -> int: + parser = argparse.ArgumentParser() + parser.add_argument("--matcher-c", type=pathlib.Path, required=True) + parser.add_argument("--matcher-h", type=pathlib.Path, required=True) + parser.add_argument("--hook-h", type=pathlib.Path, required=True) + parser.add_argument("--output", type=pathlib.Path, required=True) + parser.add_argument("--check", action="store_true") + args = parser.parse_args() + + if args.check: + rendered = render(args.matcher_c, args.matcher_h, args.hook_h) + current = _read(args.output) + if current != rendered: + print(f"{args.output} is out of date", file=sys.stderr) + return 1 + else: + generate(args.matcher_c, args.matcher_h, args.hook_h, args.output) + + return 0 + + +if __name__ == "__main__": + raise SystemExit(main()) diff --git a/doc/usage/_generated/bfcli_matcher_hook_compatibility.rst b/doc/usage/_generated/bfcli_matcher_hook_compatibility.rst new file mode 100644 index 000000000..81dcf45cb --- /dev/null +++ b/doc/usage/_generated/bfcli_matcher_hook_compatibility.rst @@ -0,0 +1,69 @@ +.. This file is auto-generated by doc/generate_matcher_hook_compat.py. +.. Do not edit manually. + +.. list-table:: + :header-rows: 1 + :widths: 2 9 + + * - Matcher + - Supported hooks + * - ``meta.iface`` + - ``BF_HOOK_XDP``, ``BF_HOOK_TC_INGRESS``, ``BF_HOOK_NF_PRE_ROUTING``, ``BF_HOOK_NF_LOCAL_IN``, ``BF_HOOK_NF_FORWARD``, ``BF_HOOK_CGROUP_SKB_INGRESS``, ``BF_HOOK_CGROUP_SKB_EGRESS``, ``BF_HOOK_NF_LOCAL_OUT``, ``BF_HOOK_NF_POST_ROUTING``, ``BF_HOOK_TC_EGRESS`` + * - ``meta.l3_proto`` + - ``BF_HOOK_XDP``, ``BF_HOOK_TC_INGRESS``, ``BF_HOOK_NF_PRE_ROUTING``, ``BF_HOOK_NF_LOCAL_IN``, ``BF_HOOK_NF_FORWARD``, ``BF_HOOK_CGROUP_SKB_INGRESS``, ``BF_HOOK_CGROUP_SKB_EGRESS``, ``BF_HOOK_NF_LOCAL_OUT``, ``BF_HOOK_NF_POST_ROUTING``, ``BF_HOOK_TC_EGRESS``, ``BF_HOOK_CGROUP_SOCK_ADDR_CONNECT4``, ``BF_HOOK_CGROUP_SOCK_ADDR_CONNECT6``, ``BF_HOOK_CGROUP_SOCK_ADDR_SENDMSG4``, ``BF_HOOK_CGROUP_SOCK_ADDR_SENDMSG6`` + * - ``meta.l4_proto`` + - ``BF_HOOK_XDP``, ``BF_HOOK_TC_INGRESS``, ``BF_HOOK_NF_PRE_ROUTING``, ``BF_HOOK_NF_LOCAL_IN``, ``BF_HOOK_NF_FORWARD``, ``BF_HOOK_CGROUP_SKB_INGRESS``, ``BF_HOOK_CGROUP_SKB_EGRESS``, ``BF_HOOK_NF_LOCAL_OUT``, ``BF_HOOK_NF_POST_ROUTING``, ``BF_HOOK_TC_EGRESS``, ``BF_HOOK_CGROUP_SOCK_ADDR_CONNECT4``, ``BF_HOOK_CGROUP_SOCK_ADDR_CONNECT6``, ``BF_HOOK_CGROUP_SOCK_ADDR_SENDMSG4``, ``BF_HOOK_CGROUP_SOCK_ADDR_SENDMSG6`` + * - ``meta.probability`` + - ``BF_HOOK_XDP``, ``BF_HOOK_TC_INGRESS``, ``BF_HOOK_NF_PRE_ROUTING``, ``BF_HOOK_NF_LOCAL_IN``, ``BF_HOOK_NF_FORWARD``, ``BF_HOOK_CGROUP_SKB_INGRESS``, ``BF_HOOK_CGROUP_SKB_EGRESS``, ``BF_HOOK_NF_LOCAL_OUT``, ``BF_HOOK_NF_POST_ROUTING``, ``BF_HOOK_TC_EGRESS``, ``BF_HOOK_CGROUP_SOCK_ADDR_CONNECT4``, ``BF_HOOK_CGROUP_SOCK_ADDR_CONNECT6``, ``BF_HOOK_CGROUP_SOCK_ADDR_SENDMSG4``, ``BF_HOOK_CGROUP_SOCK_ADDR_SENDMSG6`` + * - ``meta.sport`` + - ``BF_HOOK_XDP``, ``BF_HOOK_TC_INGRESS``, ``BF_HOOK_NF_PRE_ROUTING``, ``BF_HOOK_NF_LOCAL_IN``, ``BF_HOOK_NF_FORWARD``, ``BF_HOOK_CGROUP_SKB_INGRESS``, ``BF_HOOK_CGROUP_SKB_EGRESS``, ``BF_HOOK_NF_LOCAL_OUT``, ``BF_HOOK_NF_POST_ROUTING``, ``BF_HOOK_TC_EGRESS`` + * - ``meta.dport`` + - ``BF_HOOK_XDP``, ``BF_HOOK_TC_INGRESS``, ``BF_HOOK_NF_PRE_ROUTING``, ``BF_HOOK_NF_LOCAL_IN``, ``BF_HOOK_NF_FORWARD``, ``BF_HOOK_CGROUP_SKB_INGRESS``, ``BF_HOOK_CGROUP_SKB_EGRESS``, ``BF_HOOK_NF_LOCAL_OUT``, ``BF_HOOK_NF_POST_ROUTING``, ``BF_HOOK_TC_EGRESS``, ``BF_HOOK_CGROUP_SOCK_ADDR_CONNECT4``, ``BF_HOOK_CGROUP_SOCK_ADDR_CONNECT6``, ``BF_HOOK_CGROUP_SOCK_ADDR_SENDMSG4``, ``BF_HOOK_CGROUP_SOCK_ADDR_SENDMSG6`` + * - ``meta.mark`` + - ``BF_HOOK_TC_INGRESS``, ``BF_HOOK_NF_PRE_ROUTING``, ``BF_HOOK_NF_LOCAL_IN``, ``BF_HOOK_NF_FORWARD``, ``BF_HOOK_CGROUP_SKB_INGRESS``, ``BF_HOOK_CGROUP_SKB_EGRESS``, ``BF_HOOK_NF_LOCAL_OUT``, ``BF_HOOK_NF_POST_ROUTING``, ``BF_HOOK_TC_EGRESS`` + * - ``meta.flow_hash`` + - ``BF_HOOK_TC_INGRESS``, ``BF_HOOK_TC_EGRESS`` + * - ``meta.flow_probability`` + - ``BF_HOOK_XDP``, ``BF_HOOK_TC_INGRESS``, ``BF_HOOK_CGROUP_SKB_INGRESS``, ``BF_HOOK_CGROUP_SKB_EGRESS``, ``BF_HOOK_TC_EGRESS`` + * - ``ip4.saddr`` + - ``BF_HOOK_XDP``, ``BF_HOOK_TC_INGRESS``, ``BF_HOOK_NF_PRE_ROUTING``, ``BF_HOOK_NF_LOCAL_IN``, ``BF_HOOK_NF_FORWARD``, ``BF_HOOK_CGROUP_SKB_INGRESS``, ``BF_HOOK_CGROUP_SKB_EGRESS``, ``BF_HOOK_NF_LOCAL_OUT``, ``BF_HOOK_NF_POST_ROUTING``, ``BF_HOOK_TC_EGRESS``, ``BF_HOOK_CGROUP_SOCK_ADDR_SENDMSG4`` + * - ``ip4.snet`` + - ``BF_HOOK_XDP``, ``BF_HOOK_TC_INGRESS``, ``BF_HOOK_NF_PRE_ROUTING``, ``BF_HOOK_NF_LOCAL_IN``, ``BF_HOOK_NF_FORWARD``, ``BF_HOOK_CGROUP_SKB_INGRESS``, ``BF_HOOK_CGROUP_SKB_EGRESS``, ``BF_HOOK_NF_LOCAL_OUT``, ``BF_HOOK_NF_POST_ROUTING``, ``BF_HOOK_TC_EGRESS``, ``BF_HOOK_CGROUP_SOCK_ADDR_SENDMSG4`` + * - ``ip4.daddr`` + - ``BF_HOOK_XDP``, ``BF_HOOK_TC_INGRESS``, ``BF_HOOK_NF_PRE_ROUTING``, ``BF_HOOK_NF_LOCAL_IN``, ``BF_HOOK_NF_FORWARD``, ``BF_HOOK_CGROUP_SKB_INGRESS``, ``BF_HOOK_CGROUP_SKB_EGRESS``, ``BF_HOOK_NF_LOCAL_OUT``, ``BF_HOOK_NF_POST_ROUTING``, ``BF_HOOK_TC_EGRESS``, ``BF_HOOK_CGROUP_SOCK_ADDR_CONNECT4``, ``BF_HOOK_CGROUP_SOCK_ADDR_SENDMSG4`` + * - ``ip4.dnet`` + - ``BF_HOOK_XDP``, ``BF_HOOK_TC_INGRESS``, ``BF_HOOK_NF_PRE_ROUTING``, ``BF_HOOK_NF_LOCAL_IN``, ``BF_HOOK_NF_FORWARD``, ``BF_HOOK_CGROUP_SKB_INGRESS``, ``BF_HOOK_CGROUP_SKB_EGRESS``, ``BF_HOOK_NF_LOCAL_OUT``, ``BF_HOOK_NF_POST_ROUTING``, ``BF_HOOK_TC_EGRESS``, ``BF_HOOK_CGROUP_SOCK_ADDR_CONNECT4``, ``BF_HOOK_CGROUP_SOCK_ADDR_SENDMSG4`` + * - ``ip4.proto`` + - ``BF_HOOK_XDP``, ``BF_HOOK_TC_INGRESS``, ``BF_HOOK_NF_PRE_ROUTING``, ``BF_HOOK_NF_LOCAL_IN``, ``BF_HOOK_NF_FORWARD``, ``BF_HOOK_CGROUP_SKB_INGRESS``, ``BF_HOOK_CGROUP_SKB_EGRESS``, ``BF_HOOK_NF_LOCAL_OUT``, ``BF_HOOK_NF_POST_ROUTING``, ``BF_HOOK_TC_EGRESS``, ``BF_HOOK_CGROUP_SOCK_ADDR_CONNECT4``, ``BF_HOOK_CGROUP_SOCK_ADDR_SENDMSG4`` + * - ``ip4.dscp`` + - ``BF_HOOK_XDP``, ``BF_HOOK_TC_INGRESS``, ``BF_HOOK_NF_PRE_ROUTING``, ``BF_HOOK_NF_LOCAL_IN``, ``BF_HOOK_NF_FORWARD``, ``BF_HOOK_CGROUP_SKB_INGRESS``, ``BF_HOOK_CGROUP_SKB_EGRESS``, ``BF_HOOK_NF_LOCAL_OUT``, ``BF_HOOK_NF_POST_ROUTING``, ``BF_HOOK_TC_EGRESS`` + * - ``ip6.saddr`` + - ``BF_HOOK_XDP``, ``BF_HOOK_TC_INGRESS``, ``BF_HOOK_NF_PRE_ROUTING``, ``BF_HOOK_NF_LOCAL_IN``, ``BF_HOOK_NF_FORWARD``, ``BF_HOOK_CGROUP_SKB_INGRESS``, ``BF_HOOK_CGROUP_SKB_EGRESS``, ``BF_HOOK_NF_LOCAL_OUT``, ``BF_HOOK_NF_POST_ROUTING``, ``BF_HOOK_TC_EGRESS``, ``BF_HOOK_CGROUP_SOCK_ADDR_SENDMSG6`` + * - ``ip6.snet`` + - ``BF_HOOK_XDP``, ``BF_HOOK_TC_INGRESS``, ``BF_HOOK_NF_PRE_ROUTING``, ``BF_HOOK_NF_LOCAL_IN``, ``BF_HOOK_NF_FORWARD``, ``BF_HOOK_CGROUP_SKB_INGRESS``, ``BF_HOOK_CGROUP_SKB_EGRESS``, ``BF_HOOK_NF_LOCAL_OUT``, ``BF_HOOK_NF_POST_ROUTING``, ``BF_HOOK_TC_EGRESS``, ``BF_HOOK_CGROUP_SOCK_ADDR_SENDMSG6`` + * - ``ip6.daddr`` + - ``BF_HOOK_XDP``, ``BF_HOOK_TC_INGRESS``, ``BF_HOOK_NF_PRE_ROUTING``, ``BF_HOOK_NF_LOCAL_IN``, ``BF_HOOK_NF_FORWARD``, ``BF_HOOK_CGROUP_SKB_INGRESS``, ``BF_HOOK_CGROUP_SKB_EGRESS``, ``BF_HOOK_NF_LOCAL_OUT``, ``BF_HOOK_NF_POST_ROUTING``, ``BF_HOOK_TC_EGRESS``, ``BF_HOOK_CGROUP_SOCK_ADDR_CONNECT6``, ``BF_HOOK_CGROUP_SOCK_ADDR_SENDMSG6`` + * - ``ip6.dnet`` + - ``BF_HOOK_XDP``, ``BF_HOOK_TC_INGRESS``, ``BF_HOOK_NF_PRE_ROUTING``, ``BF_HOOK_NF_LOCAL_IN``, ``BF_HOOK_NF_FORWARD``, ``BF_HOOK_CGROUP_SKB_INGRESS``, ``BF_HOOK_CGROUP_SKB_EGRESS``, ``BF_HOOK_NF_LOCAL_OUT``, ``BF_HOOK_NF_POST_ROUTING``, ``BF_HOOK_TC_EGRESS``, ``BF_HOOK_CGROUP_SOCK_ADDR_CONNECT6``, ``BF_HOOK_CGROUP_SOCK_ADDR_SENDMSG6`` + * - ``ip6.nexthdr`` + - ``BF_HOOK_XDP``, ``BF_HOOK_TC_INGRESS``, ``BF_HOOK_NF_PRE_ROUTING``, ``BF_HOOK_NF_LOCAL_IN``, ``BF_HOOK_NF_FORWARD``, ``BF_HOOK_CGROUP_SKB_INGRESS``, ``BF_HOOK_CGROUP_SKB_EGRESS``, ``BF_HOOK_NF_LOCAL_OUT``, ``BF_HOOK_NF_POST_ROUTING``, ``BF_HOOK_TC_EGRESS`` + * - ``ip6.dscp`` + - ``BF_HOOK_XDP``, ``BF_HOOK_TC_INGRESS``, ``BF_HOOK_NF_PRE_ROUTING``, ``BF_HOOK_NF_LOCAL_IN``, ``BF_HOOK_NF_FORWARD``, ``BF_HOOK_CGROUP_SKB_INGRESS``, ``BF_HOOK_CGROUP_SKB_EGRESS``, ``BF_HOOK_NF_LOCAL_OUT``, ``BF_HOOK_NF_POST_ROUTING``, ``BF_HOOK_TC_EGRESS`` + * - ``tcp.sport`` + - ``BF_HOOK_XDP``, ``BF_HOOK_TC_INGRESS``, ``BF_HOOK_NF_PRE_ROUTING``, ``BF_HOOK_NF_LOCAL_IN``, ``BF_HOOK_NF_FORWARD``, ``BF_HOOK_CGROUP_SKB_INGRESS``, ``BF_HOOK_CGROUP_SKB_EGRESS``, ``BF_HOOK_NF_LOCAL_OUT``, ``BF_HOOK_NF_POST_ROUTING``, ``BF_HOOK_TC_EGRESS`` + * - ``tcp.dport`` + - ``BF_HOOK_XDP``, ``BF_HOOK_TC_INGRESS``, ``BF_HOOK_NF_PRE_ROUTING``, ``BF_HOOK_NF_LOCAL_IN``, ``BF_HOOK_NF_FORWARD``, ``BF_HOOK_CGROUP_SKB_INGRESS``, ``BF_HOOK_CGROUP_SKB_EGRESS``, ``BF_HOOK_NF_LOCAL_OUT``, ``BF_HOOK_NF_POST_ROUTING``, ``BF_HOOK_TC_EGRESS``, ``BF_HOOK_CGROUP_SOCK_ADDR_CONNECT4``, ``BF_HOOK_CGROUP_SOCK_ADDR_CONNECT6`` + * - ``tcp.flags`` + - ``BF_HOOK_XDP``, ``BF_HOOK_TC_INGRESS``, ``BF_HOOK_NF_PRE_ROUTING``, ``BF_HOOK_NF_LOCAL_IN``, ``BF_HOOK_NF_FORWARD``, ``BF_HOOK_CGROUP_SKB_INGRESS``, ``BF_HOOK_CGROUP_SKB_EGRESS``, ``BF_HOOK_NF_LOCAL_OUT``, ``BF_HOOK_NF_POST_ROUTING``, ``BF_HOOK_TC_EGRESS`` + * - ``udp.sport`` + - ``BF_HOOK_XDP``, ``BF_HOOK_TC_INGRESS``, ``BF_HOOK_NF_PRE_ROUTING``, ``BF_HOOK_NF_LOCAL_IN``, ``BF_HOOK_NF_FORWARD``, ``BF_HOOK_CGROUP_SKB_INGRESS``, ``BF_HOOK_CGROUP_SKB_EGRESS``, ``BF_HOOK_NF_LOCAL_OUT``, ``BF_HOOK_NF_POST_ROUTING``, ``BF_HOOK_TC_EGRESS`` + * - ``udp.dport`` + - ``BF_HOOK_XDP``, ``BF_HOOK_TC_INGRESS``, ``BF_HOOK_NF_PRE_ROUTING``, ``BF_HOOK_NF_LOCAL_IN``, ``BF_HOOK_NF_FORWARD``, ``BF_HOOK_CGROUP_SKB_INGRESS``, ``BF_HOOK_CGROUP_SKB_EGRESS``, ``BF_HOOK_NF_LOCAL_OUT``, ``BF_HOOK_NF_POST_ROUTING``, ``BF_HOOK_TC_EGRESS``, ``BF_HOOK_CGROUP_SOCK_ADDR_CONNECT4``, ``BF_HOOK_CGROUP_SOCK_ADDR_CONNECT6``, ``BF_HOOK_CGROUP_SOCK_ADDR_SENDMSG4``, ``BF_HOOK_CGROUP_SOCK_ADDR_SENDMSG6`` + * - ``icmp.type`` + - ``BF_HOOK_XDP``, ``BF_HOOK_TC_INGRESS``, ``BF_HOOK_NF_PRE_ROUTING``, ``BF_HOOK_NF_LOCAL_IN``, ``BF_HOOK_NF_FORWARD``, ``BF_HOOK_CGROUP_SKB_INGRESS``, ``BF_HOOK_CGROUP_SKB_EGRESS``, ``BF_HOOK_NF_LOCAL_OUT``, ``BF_HOOK_NF_POST_ROUTING``, ``BF_HOOK_TC_EGRESS`` + * - ``icmp.code`` + - ``BF_HOOK_XDP``, ``BF_HOOK_TC_INGRESS``, ``BF_HOOK_NF_PRE_ROUTING``, ``BF_HOOK_NF_LOCAL_IN``, ``BF_HOOK_NF_FORWARD``, ``BF_HOOK_CGROUP_SKB_INGRESS``, ``BF_HOOK_CGROUP_SKB_EGRESS``, ``BF_HOOK_NF_LOCAL_OUT``, ``BF_HOOK_NF_POST_ROUTING``, ``BF_HOOK_TC_EGRESS`` + * - ``icmpv6.type`` + - ``BF_HOOK_XDP``, ``BF_HOOK_TC_INGRESS``, ``BF_HOOK_NF_PRE_ROUTING``, ``BF_HOOK_NF_LOCAL_IN``, ``BF_HOOK_NF_FORWARD``, ``BF_HOOK_CGROUP_SKB_INGRESS``, ``BF_HOOK_CGROUP_SKB_EGRESS``, ``BF_HOOK_NF_LOCAL_OUT``, ``BF_HOOK_NF_POST_ROUTING``, ``BF_HOOK_TC_EGRESS`` + * - ``icmpv6.code`` + - ``BF_HOOK_XDP``, ``BF_HOOK_TC_INGRESS``, ``BF_HOOK_NF_PRE_ROUTING``, ``BF_HOOK_NF_LOCAL_IN``, ``BF_HOOK_NF_FORWARD``, ``BF_HOOK_CGROUP_SKB_INGRESS``, ``BF_HOOK_CGROUP_SKB_EGRESS``, ``BF_HOOK_NF_LOCAL_OUT``, ``BF_HOOK_NF_POST_ROUTING``, ``BF_HOOK_TC_EGRESS`` diff --git a/doc/usage/bfcli.rst b/doc/usage/bfcli.rst index 602266f19..979e8d6cd 100644 --- a/doc/usage/bfcli.rst +++ b/doc/usage/bfcli.rst @@ -521,6 +521,15 @@ With: - ``$PAYLOAD``: payload to compare to the processed network packet. The exact payload format depends on ``$TYPE``. +Hook Compatibility +################## + +The matcher-to-hook compatibility table below is generated from +``src/libbpfilter/matcher.c`` and reflects each matcher's +``unsupported_hooks`` metadata. + +.. include:: _generated/bfcli_matcher_hook_compatibility.rst + Meta #### From 1523add9f849a3ed01f38a8044cd6f2cb6d8b2ae Mon Sep 17 00:00:00 2001 From: liuhuaize Date: Sat, 11 Apr 2026 11:45:02 +0800 Subject: [PATCH 2/3] doc: address matcher hook review feedback --- .github/workflows/ci.yaml | 8 ++ doc/CMakeLists.txt | 19 ----- doc/generate_matcher_hook_compat.py | 84 +++++++++++++++---- .../bfcli_matcher_hook_compatibility.rst | 64 +++++++------- doc/usage/bfcli.rst | 22 ++--- 5 files changed, 122 insertions(+), 75 deletions(-) diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml index 04049a330..3b78552c6 100644 --- a/.github/workflows/ci.yaml +++ b/.github/workflows/ci.yaml @@ -209,6 +209,14 @@ jobs: fetch-depth: 0 - name: Mark the repository as safe for Git run: git config --global --add safe.directory $GITHUB_WORKSPACE + - name: Check generated matcher hook documentation + run: | + python3 $GITHUB_WORKSPACE/doc/generate_matcher_hook_compat.py \ + --matcher-c $GITHUB_WORKSPACE/src/libbpfilter/matcher.c \ + --matcher-h $GITHUB_WORKSPACE/src/libbpfilter/include/bpfilter/matcher.h \ + --hook-h $GITHUB_WORKSPACE/src/libbpfilter/include/bpfilter/hook.h \ + --output $GITHUB_WORKSPACE/doc/usage/_generated/bfcli_matcher_hook_compatibility.rst \ + --check - name: Configure the build run: cmake -S $GITHUB_WORKSPACE -B $GITHUB_WORKSPACE/build - name: Restore the cached tests results diff --git a/doc/CMakeLists.txt b/doc/CMakeLists.txt index 86dd7397a..b9cfd4c08 100644 --- a/doc/CMakeLists.txt +++ b/doc/CMakeLists.txt @@ -67,7 +67,6 @@ #]] find_package(Doxygen REQUIRED) -find_package(Python3 COMPONENTS Interpreter REQUIRED) find_program(SPHINX_BIN sphinx-build REQUIRED) set(bfcli_matcher_hook_compatibility @@ -102,24 +101,6 @@ set(doc_srcs configure_file(Doxyfile.in Doxyfile) configure_file(conf.py.in conf.py) -add_custom_command( - COMMAND - ${Python3_EXECUTABLE} - ${CMAKE_CURRENT_SOURCE_DIR}/generate_matcher_hook_compat.py - --matcher-c ${CMAKE_SOURCE_DIR}/src/libbpfilter/matcher.c - --matcher-h ${CMAKE_SOURCE_DIR}/src/libbpfilter/include/bpfilter/matcher.h - --hook-h ${CMAKE_SOURCE_DIR}/src/libbpfilter/include/bpfilter/hook.h - --output ${bfcli_matcher_hook_compatibility} - DEPENDS - ${CMAKE_CURRENT_SOURCE_DIR}/generate_matcher_hook_compat.py - ${CMAKE_SOURCE_DIR}/src/libbpfilter/matcher.c - ${CMAKE_SOURCE_DIR}/src/libbpfilter/include/bpfilter/matcher.h - ${CMAKE_SOURCE_DIR}/src/libbpfilter/include/bpfilter/hook.h - OUTPUT ${bfcli_matcher_hook_compatibility} - COMMENT "Generating bfcli matcher hook compatibility table" - VERBATIM -) - add_custom_command( COMMAND Doxygen::doxygen -s -g ${CMAKE_CURRENT_BINARY_DIR}/Doxyfile.base >/dev/null OUTPUT ${CMAKE_CURRENT_BINARY_DIR}/Doxyfile.base diff --git a/doc/generate_matcher_hook_compat.py b/doc/generate_matcher_hook_compat.py index 23edbf7d8..4dc054b81 100644 --- a/doc/generate_matcher_hook_compat.py +++ b/doc/generate_matcher_hook_compat.py @@ -16,11 +16,19 @@ import sys +def _warn(message: str) -> None: + print(f"warning: {message}", file=sys.stderr) + + def _read(path: pathlib.Path) -> str: return path.read_text(encoding="utf-8") -def _read_enum_order(path: pathlib.Path, enum_name: str, prefix: str) -> list[str]: +def _read_enum_order( + path: pathlib.Path, + enum_name: str, + prefix: str, +) -> list[str]: content = _read(path) match = re.search( rf"enum\s+{re.escape(enum_name)}\s*\{{(?P.*?)\n\}};", @@ -46,7 +54,11 @@ def _read_enum_order(path: pathlib.Path, enum_name: str, prefix: str) -> list[st return values -def _read_string_map(path: pathlib.Path, array_name: str, prefix: str) -> dict[str, str]: +def _read_string_map( + path: pathlib.Path, + array_name: str, + prefix: str, +) -> dict[str, str]: content = _read(path) match = re.search( rf"{re.escape(array_name)}\[\]\s*=\s*\{{(?P.*?)\n\}};", @@ -73,12 +85,20 @@ def _read_hook_macros(path: pathlib.Path) -> dict[str, list[str]]: r"#define\s+(_BF_HOOKS_[A-Z0-9_]+)\s+([^\n]+)", flattened, ): - macros[name] = [token.strip() for token in expr.split(",") if token.strip()] + macros[name] = [ + token.strip() for token in expr.split(",") if token.strip() + ] + + if not macros: + _warn(f"unable to find any _BF_HOOKS_* macros in {path}") return macros -def _extract_initializer_blocks(content: str, array_name: str) -> dict[str, str]: +def _extract_initializer_blocks( + content: str, + array_name: str, +) -> dict[str, str]: start = content.find(array_name) if start < 0: raise ValueError(f"unable to find {array_name}") @@ -89,6 +109,8 @@ def _extract_initializer_blocks(content: str, array_name: str) -> dict[str, str] depth = 0 array_end = -1 + # This lightweight parser assumes braces only delimit initializer blocks. + # Braces inside comments or string literals would confuse the depth count. for idx in range(array_start, len(content)): if content[idx] == "{": depth += 1 @@ -146,7 +168,11 @@ def _extract_unsupported_hooks(block: str) -> list[str]: if not match: return [] - return [token.strip() for token in match.group("expr").replace("\n", " ").split(",") if token.strip()] + return [ + token.strip() + for token in match.group("expr").replace("\n", " ").split(",") + if token.strip() + ] def _expand_hooks(tokens: list[str], macros: dict[str, list[str]]) -> list[str]: @@ -160,6 +186,20 @@ def _expand_hooks(tokens: list[str], macros: dict[str, list[str]]) -> list[str]: return expanded +def _validate_hooks( + matcher: str, + hooks: list[str], + hook_order: list[str], +) -> None: + unknown = sorted(set(hook for hook in hooks if hook not in hook_order)) + if unknown: + unknown_str = ", ".join(unknown) + raise ValueError( + f"unknown hooks for {matcher}: {unknown_str}; " + "update the parser if hook macros moved" + ) + + def _render_table( matcher_order: list[str], matcher_names: dict[str, str], @@ -167,15 +207,18 @@ def _render_table( unsupported: dict[str, list[str]], ) -> str: lines = [ - ".. This file is auto-generated by doc/generate_matcher_hook_compat.py.", + ( + ".. This file is auto-generated by " + "doc/generate_matcher_hook_compat.py." + ), ".. Do not edit manually.", "", ".. list-table::", " :header-rows: 1", - " :widths: 2 9", + " :widths: 2 5", "", " * - Matcher", - " - Supported hooks", + " - Unsupported hooks", ] for matcher in matcher_order: @@ -184,8 +227,11 @@ def _render_table( continue blocked = set(unsupported.get(matcher, [])) - supported = [hook for hook in hook_order if hook not in blocked] - rendered_hooks = ", ".join(f"``{hook}``" for hook in supported) + blocked_hooks = [hook for hook in hook_order if hook in blocked] + rendered_hooks = ", ".join(f"``{hook}``" for hook in blocked_hooks) + if not rendered_hooks: + rendered_hooks = "None" + lines.extend( [ f" * - ``{matcher_name}``", @@ -203,15 +249,25 @@ def render( hook_h: pathlib.Path, ) -> str: matcher_content = _read(matcher_c) - matcher_order = _read_enum_order(matcher_h, "bf_matcher_type", "BF_MATCHER_") + matcher_order = _read_enum_order( + matcher_h, "bf_matcher_type", "BF_MATCHER_" + ) hook_order = _read_enum_order(hook_h, "bf_hook", "BF_HOOK_") - matcher_names = _read_string_map(matcher_c, "_bf_matcher_type_strs", "BF_MATCHER_") + matcher_names = _read_string_map( + matcher_c, "_bf_matcher_type_strs", "BF_MATCHER_" + ) hook_macros = _read_hook_macros(matcher_c) - matcher_blocks = _extract_initializer_blocks(matcher_content, "_bf_matcher_metas") + matcher_blocks = _extract_initializer_blocks( + matcher_content, "_bf_matcher_metas" + ) unsupported: dict[str, list[str]] = {} for matcher, block in matcher_blocks.items(): - unsupported[matcher] = _expand_hooks(_extract_unsupported_hooks(block), hook_macros) + unsupported[matcher] = _expand_hooks( + _extract_unsupported_hooks(block), + hook_macros, + ) + _validate_hooks(matcher, unsupported[matcher], hook_order) return _render_table(matcher_order, matcher_names, hook_order, unsupported) diff --git a/doc/usage/_generated/bfcli_matcher_hook_compatibility.rst b/doc/usage/_generated/bfcli_matcher_hook_compatibility.rst index 81dcf45cb..71f6e2c57 100644 --- a/doc/usage/_generated/bfcli_matcher_hook_compatibility.rst +++ b/doc/usage/_generated/bfcli_matcher_hook_compatibility.rst @@ -3,67 +3,67 @@ .. list-table:: :header-rows: 1 - :widths: 2 9 + :widths: 2 5 * - Matcher - - Supported hooks + - Unsupported hooks * - ``meta.iface`` - - ``BF_HOOK_XDP``, ``BF_HOOK_TC_INGRESS``, ``BF_HOOK_NF_PRE_ROUTING``, ``BF_HOOK_NF_LOCAL_IN``, ``BF_HOOK_NF_FORWARD``, ``BF_HOOK_CGROUP_SKB_INGRESS``, ``BF_HOOK_CGROUP_SKB_EGRESS``, ``BF_HOOK_NF_LOCAL_OUT``, ``BF_HOOK_NF_POST_ROUTING``, ``BF_HOOK_TC_EGRESS`` + - ``BF_HOOK_CGROUP_SOCK_ADDR_CONNECT4``, ``BF_HOOK_CGROUP_SOCK_ADDR_CONNECT6``, ``BF_HOOK_CGROUP_SOCK_ADDR_SENDMSG4``, ``BF_HOOK_CGROUP_SOCK_ADDR_SENDMSG6`` * - ``meta.l3_proto`` - - ``BF_HOOK_XDP``, ``BF_HOOK_TC_INGRESS``, ``BF_HOOK_NF_PRE_ROUTING``, ``BF_HOOK_NF_LOCAL_IN``, ``BF_HOOK_NF_FORWARD``, ``BF_HOOK_CGROUP_SKB_INGRESS``, ``BF_HOOK_CGROUP_SKB_EGRESS``, ``BF_HOOK_NF_LOCAL_OUT``, ``BF_HOOK_NF_POST_ROUTING``, ``BF_HOOK_TC_EGRESS``, ``BF_HOOK_CGROUP_SOCK_ADDR_CONNECT4``, ``BF_HOOK_CGROUP_SOCK_ADDR_CONNECT6``, ``BF_HOOK_CGROUP_SOCK_ADDR_SENDMSG4``, ``BF_HOOK_CGROUP_SOCK_ADDR_SENDMSG6`` + - None * - ``meta.l4_proto`` - - ``BF_HOOK_XDP``, ``BF_HOOK_TC_INGRESS``, ``BF_HOOK_NF_PRE_ROUTING``, ``BF_HOOK_NF_LOCAL_IN``, ``BF_HOOK_NF_FORWARD``, ``BF_HOOK_CGROUP_SKB_INGRESS``, ``BF_HOOK_CGROUP_SKB_EGRESS``, ``BF_HOOK_NF_LOCAL_OUT``, ``BF_HOOK_NF_POST_ROUTING``, ``BF_HOOK_TC_EGRESS``, ``BF_HOOK_CGROUP_SOCK_ADDR_CONNECT4``, ``BF_HOOK_CGROUP_SOCK_ADDR_CONNECT6``, ``BF_HOOK_CGROUP_SOCK_ADDR_SENDMSG4``, ``BF_HOOK_CGROUP_SOCK_ADDR_SENDMSG6`` + - None * - ``meta.probability`` - - ``BF_HOOK_XDP``, ``BF_HOOK_TC_INGRESS``, ``BF_HOOK_NF_PRE_ROUTING``, ``BF_HOOK_NF_LOCAL_IN``, ``BF_HOOK_NF_FORWARD``, ``BF_HOOK_CGROUP_SKB_INGRESS``, ``BF_HOOK_CGROUP_SKB_EGRESS``, ``BF_HOOK_NF_LOCAL_OUT``, ``BF_HOOK_NF_POST_ROUTING``, ``BF_HOOK_TC_EGRESS``, ``BF_HOOK_CGROUP_SOCK_ADDR_CONNECT4``, ``BF_HOOK_CGROUP_SOCK_ADDR_CONNECT6``, ``BF_HOOK_CGROUP_SOCK_ADDR_SENDMSG4``, ``BF_HOOK_CGROUP_SOCK_ADDR_SENDMSG6`` + - None * - ``meta.sport`` - - ``BF_HOOK_XDP``, ``BF_HOOK_TC_INGRESS``, ``BF_HOOK_NF_PRE_ROUTING``, ``BF_HOOK_NF_LOCAL_IN``, ``BF_HOOK_NF_FORWARD``, ``BF_HOOK_CGROUP_SKB_INGRESS``, ``BF_HOOK_CGROUP_SKB_EGRESS``, ``BF_HOOK_NF_LOCAL_OUT``, ``BF_HOOK_NF_POST_ROUTING``, ``BF_HOOK_TC_EGRESS`` + - ``BF_HOOK_CGROUP_SOCK_ADDR_CONNECT4``, ``BF_HOOK_CGROUP_SOCK_ADDR_CONNECT6``, ``BF_HOOK_CGROUP_SOCK_ADDR_SENDMSG4``, ``BF_HOOK_CGROUP_SOCK_ADDR_SENDMSG6`` * - ``meta.dport`` - - ``BF_HOOK_XDP``, ``BF_HOOK_TC_INGRESS``, ``BF_HOOK_NF_PRE_ROUTING``, ``BF_HOOK_NF_LOCAL_IN``, ``BF_HOOK_NF_FORWARD``, ``BF_HOOK_CGROUP_SKB_INGRESS``, ``BF_HOOK_CGROUP_SKB_EGRESS``, ``BF_HOOK_NF_LOCAL_OUT``, ``BF_HOOK_NF_POST_ROUTING``, ``BF_HOOK_TC_EGRESS``, ``BF_HOOK_CGROUP_SOCK_ADDR_CONNECT4``, ``BF_HOOK_CGROUP_SOCK_ADDR_CONNECT6``, ``BF_HOOK_CGROUP_SOCK_ADDR_SENDMSG4``, ``BF_HOOK_CGROUP_SOCK_ADDR_SENDMSG6`` + - None * - ``meta.mark`` - - ``BF_HOOK_TC_INGRESS``, ``BF_HOOK_NF_PRE_ROUTING``, ``BF_HOOK_NF_LOCAL_IN``, ``BF_HOOK_NF_FORWARD``, ``BF_HOOK_CGROUP_SKB_INGRESS``, ``BF_HOOK_CGROUP_SKB_EGRESS``, ``BF_HOOK_NF_LOCAL_OUT``, ``BF_HOOK_NF_POST_ROUTING``, ``BF_HOOK_TC_EGRESS`` + - ``BF_HOOK_XDP``, ``BF_HOOK_CGROUP_SOCK_ADDR_CONNECT4``, ``BF_HOOK_CGROUP_SOCK_ADDR_CONNECT6``, ``BF_HOOK_CGROUP_SOCK_ADDR_SENDMSG4``, ``BF_HOOK_CGROUP_SOCK_ADDR_SENDMSG6`` * - ``meta.flow_hash`` - - ``BF_HOOK_TC_INGRESS``, ``BF_HOOK_TC_EGRESS`` + - ``BF_HOOK_XDP``, ``BF_HOOK_NF_PRE_ROUTING``, ``BF_HOOK_NF_LOCAL_IN``, ``BF_HOOK_NF_FORWARD``, ``BF_HOOK_CGROUP_SKB_INGRESS``, ``BF_HOOK_CGROUP_SKB_EGRESS``, ``BF_HOOK_NF_LOCAL_OUT``, ``BF_HOOK_NF_POST_ROUTING``, ``BF_HOOK_CGROUP_SOCK_ADDR_CONNECT4``, ``BF_HOOK_CGROUP_SOCK_ADDR_CONNECT6``, ``BF_HOOK_CGROUP_SOCK_ADDR_SENDMSG4``, ``BF_HOOK_CGROUP_SOCK_ADDR_SENDMSG6`` * - ``meta.flow_probability`` - - ``BF_HOOK_XDP``, ``BF_HOOK_TC_INGRESS``, ``BF_HOOK_CGROUP_SKB_INGRESS``, ``BF_HOOK_CGROUP_SKB_EGRESS``, ``BF_HOOK_TC_EGRESS`` + - ``BF_HOOK_NF_PRE_ROUTING``, ``BF_HOOK_NF_LOCAL_IN``, ``BF_HOOK_NF_FORWARD``, ``BF_HOOK_NF_LOCAL_OUT``, ``BF_HOOK_NF_POST_ROUTING``, ``BF_HOOK_CGROUP_SOCK_ADDR_CONNECT4``, ``BF_HOOK_CGROUP_SOCK_ADDR_CONNECT6``, ``BF_HOOK_CGROUP_SOCK_ADDR_SENDMSG4``, ``BF_HOOK_CGROUP_SOCK_ADDR_SENDMSG6`` * - ``ip4.saddr`` - - ``BF_HOOK_XDP``, ``BF_HOOK_TC_INGRESS``, ``BF_HOOK_NF_PRE_ROUTING``, ``BF_HOOK_NF_LOCAL_IN``, ``BF_HOOK_NF_FORWARD``, ``BF_HOOK_CGROUP_SKB_INGRESS``, ``BF_HOOK_CGROUP_SKB_EGRESS``, ``BF_HOOK_NF_LOCAL_OUT``, ``BF_HOOK_NF_POST_ROUTING``, ``BF_HOOK_TC_EGRESS``, ``BF_HOOK_CGROUP_SOCK_ADDR_SENDMSG4`` + - ``BF_HOOK_CGROUP_SOCK_ADDR_CONNECT4``, ``BF_HOOK_CGROUP_SOCK_ADDR_CONNECT6``, ``BF_HOOK_CGROUP_SOCK_ADDR_SENDMSG6`` * - ``ip4.snet`` - - ``BF_HOOK_XDP``, ``BF_HOOK_TC_INGRESS``, ``BF_HOOK_NF_PRE_ROUTING``, ``BF_HOOK_NF_LOCAL_IN``, ``BF_HOOK_NF_FORWARD``, ``BF_HOOK_CGROUP_SKB_INGRESS``, ``BF_HOOK_CGROUP_SKB_EGRESS``, ``BF_HOOK_NF_LOCAL_OUT``, ``BF_HOOK_NF_POST_ROUTING``, ``BF_HOOK_TC_EGRESS``, ``BF_HOOK_CGROUP_SOCK_ADDR_SENDMSG4`` + - ``BF_HOOK_CGROUP_SOCK_ADDR_CONNECT4``, ``BF_HOOK_CGROUP_SOCK_ADDR_CONNECT6``, ``BF_HOOK_CGROUP_SOCK_ADDR_SENDMSG6`` * - ``ip4.daddr`` - - ``BF_HOOK_XDP``, ``BF_HOOK_TC_INGRESS``, ``BF_HOOK_NF_PRE_ROUTING``, ``BF_HOOK_NF_LOCAL_IN``, ``BF_HOOK_NF_FORWARD``, ``BF_HOOK_CGROUP_SKB_INGRESS``, ``BF_HOOK_CGROUP_SKB_EGRESS``, ``BF_HOOK_NF_LOCAL_OUT``, ``BF_HOOK_NF_POST_ROUTING``, ``BF_HOOK_TC_EGRESS``, ``BF_HOOK_CGROUP_SOCK_ADDR_CONNECT4``, ``BF_HOOK_CGROUP_SOCK_ADDR_SENDMSG4`` + - ``BF_HOOK_CGROUP_SOCK_ADDR_CONNECT6``, ``BF_HOOK_CGROUP_SOCK_ADDR_SENDMSG6`` * - ``ip4.dnet`` - - ``BF_HOOK_XDP``, ``BF_HOOK_TC_INGRESS``, ``BF_HOOK_NF_PRE_ROUTING``, ``BF_HOOK_NF_LOCAL_IN``, ``BF_HOOK_NF_FORWARD``, ``BF_HOOK_CGROUP_SKB_INGRESS``, ``BF_HOOK_CGROUP_SKB_EGRESS``, ``BF_HOOK_NF_LOCAL_OUT``, ``BF_HOOK_NF_POST_ROUTING``, ``BF_HOOK_TC_EGRESS``, ``BF_HOOK_CGROUP_SOCK_ADDR_CONNECT4``, ``BF_HOOK_CGROUP_SOCK_ADDR_SENDMSG4`` + - ``BF_HOOK_CGROUP_SOCK_ADDR_CONNECT6``, ``BF_HOOK_CGROUP_SOCK_ADDR_SENDMSG6`` * - ``ip4.proto`` - - ``BF_HOOK_XDP``, ``BF_HOOK_TC_INGRESS``, ``BF_HOOK_NF_PRE_ROUTING``, ``BF_HOOK_NF_LOCAL_IN``, ``BF_HOOK_NF_FORWARD``, ``BF_HOOK_CGROUP_SKB_INGRESS``, ``BF_HOOK_CGROUP_SKB_EGRESS``, ``BF_HOOK_NF_LOCAL_OUT``, ``BF_HOOK_NF_POST_ROUTING``, ``BF_HOOK_TC_EGRESS``, ``BF_HOOK_CGROUP_SOCK_ADDR_CONNECT4``, ``BF_HOOK_CGROUP_SOCK_ADDR_SENDMSG4`` + - ``BF_HOOK_CGROUP_SOCK_ADDR_CONNECT6``, ``BF_HOOK_CGROUP_SOCK_ADDR_SENDMSG6`` * - ``ip4.dscp`` - - ``BF_HOOK_XDP``, ``BF_HOOK_TC_INGRESS``, ``BF_HOOK_NF_PRE_ROUTING``, ``BF_HOOK_NF_LOCAL_IN``, ``BF_HOOK_NF_FORWARD``, ``BF_HOOK_CGROUP_SKB_INGRESS``, ``BF_HOOK_CGROUP_SKB_EGRESS``, ``BF_HOOK_NF_LOCAL_OUT``, ``BF_HOOK_NF_POST_ROUTING``, ``BF_HOOK_TC_EGRESS`` + - ``BF_HOOK_CGROUP_SOCK_ADDR_CONNECT4``, ``BF_HOOK_CGROUP_SOCK_ADDR_CONNECT6``, ``BF_HOOK_CGROUP_SOCK_ADDR_SENDMSG4``, ``BF_HOOK_CGROUP_SOCK_ADDR_SENDMSG6`` * - ``ip6.saddr`` - - ``BF_HOOK_XDP``, ``BF_HOOK_TC_INGRESS``, ``BF_HOOK_NF_PRE_ROUTING``, ``BF_HOOK_NF_LOCAL_IN``, ``BF_HOOK_NF_FORWARD``, ``BF_HOOK_CGROUP_SKB_INGRESS``, ``BF_HOOK_CGROUP_SKB_EGRESS``, ``BF_HOOK_NF_LOCAL_OUT``, ``BF_HOOK_NF_POST_ROUTING``, ``BF_HOOK_TC_EGRESS``, ``BF_HOOK_CGROUP_SOCK_ADDR_SENDMSG6`` + - ``BF_HOOK_CGROUP_SOCK_ADDR_CONNECT4``, ``BF_HOOK_CGROUP_SOCK_ADDR_CONNECT6``, ``BF_HOOK_CGROUP_SOCK_ADDR_SENDMSG4`` * - ``ip6.snet`` - - ``BF_HOOK_XDP``, ``BF_HOOK_TC_INGRESS``, ``BF_HOOK_NF_PRE_ROUTING``, ``BF_HOOK_NF_LOCAL_IN``, ``BF_HOOK_NF_FORWARD``, ``BF_HOOK_CGROUP_SKB_INGRESS``, ``BF_HOOK_CGROUP_SKB_EGRESS``, ``BF_HOOK_NF_LOCAL_OUT``, ``BF_HOOK_NF_POST_ROUTING``, ``BF_HOOK_TC_EGRESS``, ``BF_HOOK_CGROUP_SOCK_ADDR_SENDMSG6`` + - ``BF_HOOK_CGROUP_SOCK_ADDR_CONNECT4``, ``BF_HOOK_CGROUP_SOCK_ADDR_CONNECT6``, ``BF_HOOK_CGROUP_SOCK_ADDR_SENDMSG4`` * - ``ip6.daddr`` - - ``BF_HOOK_XDP``, ``BF_HOOK_TC_INGRESS``, ``BF_HOOK_NF_PRE_ROUTING``, ``BF_HOOK_NF_LOCAL_IN``, ``BF_HOOK_NF_FORWARD``, ``BF_HOOK_CGROUP_SKB_INGRESS``, ``BF_HOOK_CGROUP_SKB_EGRESS``, ``BF_HOOK_NF_LOCAL_OUT``, ``BF_HOOK_NF_POST_ROUTING``, ``BF_HOOK_TC_EGRESS``, ``BF_HOOK_CGROUP_SOCK_ADDR_CONNECT6``, ``BF_HOOK_CGROUP_SOCK_ADDR_SENDMSG6`` + - ``BF_HOOK_CGROUP_SOCK_ADDR_CONNECT4``, ``BF_HOOK_CGROUP_SOCK_ADDR_SENDMSG4`` * - ``ip6.dnet`` - - ``BF_HOOK_XDP``, ``BF_HOOK_TC_INGRESS``, ``BF_HOOK_NF_PRE_ROUTING``, ``BF_HOOK_NF_LOCAL_IN``, ``BF_HOOK_NF_FORWARD``, ``BF_HOOK_CGROUP_SKB_INGRESS``, ``BF_HOOK_CGROUP_SKB_EGRESS``, ``BF_HOOK_NF_LOCAL_OUT``, ``BF_HOOK_NF_POST_ROUTING``, ``BF_HOOK_TC_EGRESS``, ``BF_HOOK_CGROUP_SOCK_ADDR_CONNECT6``, ``BF_HOOK_CGROUP_SOCK_ADDR_SENDMSG6`` + - ``BF_HOOK_CGROUP_SOCK_ADDR_CONNECT4``, ``BF_HOOK_CGROUP_SOCK_ADDR_SENDMSG4`` * - ``ip6.nexthdr`` - - ``BF_HOOK_XDP``, ``BF_HOOK_TC_INGRESS``, ``BF_HOOK_NF_PRE_ROUTING``, ``BF_HOOK_NF_LOCAL_IN``, ``BF_HOOK_NF_FORWARD``, ``BF_HOOK_CGROUP_SKB_INGRESS``, ``BF_HOOK_CGROUP_SKB_EGRESS``, ``BF_HOOK_NF_LOCAL_OUT``, ``BF_HOOK_NF_POST_ROUTING``, ``BF_HOOK_TC_EGRESS`` + - ``BF_HOOK_CGROUP_SOCK_ADDR_CONNECT4``, ``BF_HOOK_CGROUP_SOCK_ADDR_CONNECT6``, ``BF_HOOK_CGROUP_SOCK_ADDR_SENDMSG4``, ``BF_HOOK_CGROUP_SOCK_ADDR_SENDMSG6`` * - ``ip6.dscp`` - - ``BF_HOOK_XDP``, ``BF_HOOK_TC_INGRESS``, ``BF_HOOK_NF_PRE_ROUTING``, ``BF_HOOK_NF_LOCAL_IN``, ``BF_HOOK_NF_FORWARD``, ``BF_HOOK_CGROUP_SKB_INGRESS``, ``BF_HOOK_CGROUP_SKB_EGRESS``, ``BF_HOOK_NF_LOCAL_OUT``, ``BF_HOOK_NF_POST_ROUTING``, ``BF_HOOK_TC_EGRESS`` + - ``BF_HOOK_CGROUP_SOCK_ADDR_CONNECT4``, ``BF_HOOK_CGROUP_SOCK_ADDR_CONNECT6``, ``BF_HOOK_CGROUP_SOCK_ADDR_SENDMSG4``, ``BF_HOOK_CGROUP_SOCK_ADDR_SENDMSG6`` * - ``tcp.sport`` - - ``BF_HOOK_XDP``, ``BF_HOOK_TC_INGRESS``, ``BF_HOOK_NF_PRE_ROUTING``, ``BF_HOOK_NF_LOCAL_IN``, ``BF_HOOK_NF_FORWARD``, ``BF_HOOK_CGROUP_SKB_INGRESS``, ``BF_HOOK_CGROUP_SKB_EGRESS``, ``BF_HOOK_NF_LOCAL_OUT``, ``BF_HOOK_NF_POST_ROUTING``, ``BF_HOOK_TC_EGRESS`` + - ``BF_HOOK_CGROUP_SOCK_ADDR_CONNECT4``, ``BF_HOOK_CGROUP_SOCK_ADDR_CONNECT6``, ``BF_HOOK_CGROUP_SOCK_ADDR_SENDMSG4``, ``BF_HOOK_CGROUP_SOCK_ADDR_SENDMSG6`` * - ``tcp.dport`` - - ``BF_HOOK_XDP``, ``BF_HOOK_TC_INGRESS``, ``BF_HOOK_NF_PRE_ROUTING``, ``BF_HOOK_NF_LOCAL_IN``, ``BF_HOOK_NF_FORWARD``, ``BF_HOOK_CGROUP_SKB_INGRESS``, ``BF_HOOK_CGROUP_SKB_EGRESS``, ``BF_HOOK_NF_LOCAL_OUT``, ``BF_HOOK_NF_POST_ROUTING``, ``BF_HOOK_TC_EGRESS``, ``BF_HOOK_CGROUP_SOCK_ADDR_CONNECT4``, ``BF_HOOK_CGROUP_SOCK_ADDR_CONNECT6`` + - ``BF_HOOK_CGROUP_SOCK_ADDR_SENDMSG4``, ``BF_HOOK_CGROUP_SOCK_ADDR_SENDMSG6`` * - ``tcp.flags`` - - ``BF_HOOK_XDP``, ``BF_HOOK_TC_INGRESS``, ``BF_HOOK_NF_PRE_ROUTING``, ``BF_HOOK_NF_LOCAL_IN``, ``BF_HOOK_NF_FORWARD``, ``BF_HOOK_CGROUP_SKB_INGRESS``, ``BF_HOOK_CGROUP_SKB_EGRESS``, ``BF_HOOK_NF_LOCAL_OUT``, ``BF_HOOK_NF_POST_ROUTING``, ``BF_HOOK_TC_EGRESS`` + - ``BF_HOOK_CGROUP_SOCK_ADDR_CONNECT4``, ``BF_HOOK_CGROUP_SOCK_ADDR_CONNECT6``, ``BF_HOOK_CGROUP_SOCK_ADDR_SENDMSG4``, ``BF_HOOK_CGROUP_SOCK_ADDR_SENDMSG6`` * - ``udp.sport`` - - ``BF_HOOK_XDP``, ``BF_HOOK_TC_INGRESS``, ``BF_HOOK_NF_PRE_ROUTING``, ``BF_HOOK_NF_LOCAL_IN``, ``BF_HOOK_NF_FORWARD``, ``BF_HOOK_CGROUP_SKB_INGRESS``, ``BF_HOOK_CGROUP_SKB_EGRESS``, ``BF_HOOK_NF_LOCAL_OUT``, ``BF_HOOK_NF_POST_ROUTING``, ``BF_HOOK_TC_EGRESS`` + - ``BF_HOOK_CGROUP_SOCK_ADDR_CONNECT4``, ``BF_HOOK_CGROUP_SOCK_ADDR_CONNECT6``, ``BF_HOOK_CGROUP_SOCK_ADDR_SENDMSG4``, ``BF_HOOK_CGROUP_SOCK_ADDR_SENDMSG6`` * - ``udp.dport`` - - ``BF_HOOK_XDP``, ``BF_HOOK_TC_INGRESS``, ``BF_HOOK_NF_PRE_ROUTING``, ``BF_HOOK_NF_LOCAL_IN``, ``BF_HOOK_NF_FORWARD``, ``BF_HOOK_CGROUP_SKB_INGRESS``, ``BF_HOOK_CGROUP_SKB_EGRESS``, ``BF_HOOK_NF_LOCAL_OUT``, ``BF_HOOK_NF_POST_ROUTING``, ``BF_HOOK_TC_EGRESS``, ``BF_HOOK_CGROUP_SOCK_ADDR_CONNECT4``, ``BF_HOOK_CGROUP_SOCK_ADDR_CONNECT6``, ``BF_HOOK_CGROUP_SOCK_ADDR_SENDMSG4``, ``BF_HOOK_CGROUP_SOCK_ADDR_SENDMSG6`` + - None * - ``icmp.type`` - - ``BF_HOOK_XDP``, ``BF_HOOK_TC_INGRESS``, ``BF_HOOK_NF_PRE_ROUTING``, ``BF_HOOK_NF_LOCAL_IN``, ``BF_HOOK_NF_FORWARD``, ``BF_HOOK_CGROUP_SKB_INGRESS``, ``BF_HOOK_CGROUP_SKB_EGRESS``, ``BF_HOOK_NF_LOCAL_OUT``, ``BF_HOOK_NF_POST_ROUTING``, ``BF_HOOK_TC_EGRESS`` + - ``BF_HOOK_CGROUP_SOCK_ADDR_CONNECT4``, ``BF_HOOK_CGROUP_SOCK_ADDR_CONNECT6``, ``BF_HOOK_CGROUP_SOCK_ADDR_SENDMSG4``, ``BF_HOOK_CGROUP_SOCK_ADDR_SENDMSG6`` * - ``icmp.code`` - - ``BF_HOOK_XDP``, ``BF_HOOK_TC_INGRESS``, ``BF_HOOK_NF_PRE_ROUTING``, ``BF_HOOK_NF_LOCAL_IN``, ``BF_HOOK_NF_FORWARD``, ``BF_HOOK_CGROUP_SKB_INGRESS``, ``BF_HOOK_CGROUP_SKB_EGRESS``, ``BF_HOOK_NF_LOCAL_OUT``, ``BF_HOOK_NF_POST_ROUTING``, ``BF_HOOK_TC_EGRESS`` + - ``BF_HOOK_CGROUP_SOCK_ADDR_CONNECT4``, ``BF_HOOK_CGROUP_SOCK_ADDR_CONNECT6``, ``BF_HOOK_CGROUP_SOCK_ADDR_SENDMSG4``, ``BF_HOOK_CGROUP_SOCK_ADDR_SENDMSG6`` * - ``icmpv6.type`` - - ``BF_HOOK_XDP``, ``BF_HOOK_TC_INGRESS``, ``BF_HOOK_NF_PRE_ROUTING``, ``BF_HOOK_NF_LOCAL_IN``, ``BF_HOOK_NF_FORWARD``, ``BF_HOOK_CGROUP_SKB_INGRESS``, ``BF_HOOK_CGROUP_SKB_EGRESS``, ``BF_HOOK_NF_LOCAL_OUT``, ``BF_HOOK_NF_POST_ROUTING``, ``BF_HOOK_TC_EGRESS`` + - ``BF_HOOK_CGROUP_SOCK_ADDR_CONNECT4``, ``BF_HOOK_CGROUP_SOCK_ADDR_CONNECT6``, ``BF_HOOK_CGROUP_SOCK_ADDR_SENDMSG4``, ``BF_HOOK_CGROUP_SOCK_ADDR_SENDMSG6`` * - ``icmpv6.code`` - - ``BF_HOOK_XDP``, ``BF_HOOK_TC_INGRESS``, ``BF_HOOK_NF_PRE_ROUTING``, ``BF_HOOK_NF_LOCAL_IN``, ``BF_HOOK_NF_FORWARD``, ``BF_HOOK_CGROUP_SKB_INGRESS``, ``BF_HOOK_CGROUP_SKB_EGRESS``, ``BF_HOOK_NF_LOCAL_OUT``, ``BF_HOOK_NF_POST_ROUTING``, ``BF_HOOK_TC_EGRESS`` + - ``BF_HOOK_CGROUP_SOCK_ADDR_CONNECT4``, ``BF_HOOK_CGROUP_SOCK_ADDR_CONNECT6``, ``BF_HOOK_CGROUP_SOCK_ADDR_SENDMSG4``, ``BF_HOOK_CGROUP_SOCK_ADDR_SENDMSG6`` diff --git a/doc/usage/bfcli.rst b/doc/usage/bfcli.rst index 979e8d6cd..6dbf200b3 100644 --- a/doc/usage/bfcli.rst +++ b/doc/usage/bfcli.rst @@ -521,16 +521,6 @@ With: - ``$PAYLOAD``: payload to compare to the processed network packet. The exact payload format depends on ``$TYPE``. -Hook Compatibility -################## - -The matcher-to-hook compatibility table below is generated from -``src/libbpfilter/matcher.c`` and reflects each matcher's -``unsupported_hooks`` metadata. - -.. include:: _generated/bfcli_matcher_hook_compatibility.rst - - Meta #### @@ -853,6 +843,18 @@ ICMPv6 The following ICMPv6 type name are recognized by bpfilter: destination-unreachable, packet-too-big, time-exceeded, echo-request, echo-reply, mld-listener-query, mld-listener-report, mld-listener-reduction, nd-router-solicit, nd-router-advert, nd-neighbor-solicit, nd-neighbor-advert, parameter-problem, mld2-listener-report. +Hook Compatibility +################## + +The matcher-to-hook compatibility table below is generated from +``src/libbpfilter/matcher.c`` and reflects each matcher's +``unsupported_hooks`` metadata. + +An unsupported list of ``None`` means the matcher is compatible with every +hook. + +.. include:: _generated/bfcli_matcher_hook_compatibility.rst + .. _IEEE 802 number: https://www.iana.org/assignments/ieee-802-numbers/ieee-802-numbers.xhtml cli,core: convert meta.l3_proto to new framework) .. _internet protocol number: https://www.iana.org/assignments/protocol-numbers/protocol-numbers.xhtml From f574d466d6a408f9d87ab45605a197fcffe55666 Mon Sep 17 00:00:00 2001 From: liuhuaize Date: Sat, 11 Apr 2026 14:07:00 +0800 Subject: [PATCH 3/3] doc: tighten matcher hook checks --- .github/workflows/ci.yaml | 24 +++++++++++++++-------- doc/generate_matcher_hook_compat.py | 30 +++++++++++++++++++++++++---- doc/usage/bfcli.rst | 1 + 3 files changed, 43 insertions(+), 12 deletions(-) diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml index 3b78552c6..077690bc0 100644 --- a/.github/workflows/ci.yaml +++ b/.github/workflows/ci.yaml @@ -23,6 +23,22 @@ permissions: pull-requests: write jobs: + check-generated-docs: + timeout-minutes: 5 + runs-on: [ "8-core-ubuntu" ] + name: "Check generated docs" + steps: + - name: Checkout bpfilter + uses: actions/checkout@v4 + - name: Check matcher hook documentation + run: | + python3 $GITHUB_WORKSPACE/doc/generate_matcher_hook_compat.py \ + --matcher-c $GITHUB_WORKSPACE/src/libbpfilter/matcher.c \ + --matcher-h $GITHUB_WORKSPACE/src/libbpfilter/include/bpfilter/matcher.h \ + --hook-h $GITHUB_WORKSPACE/src/libbpfilter/include/bpfilter/hook.h \ + --output $GITHUB_WORKSPACE/doc/usage/_generated/bfcli_matcher_hook_compatibility.rst \ + --check + create-images: timeout-minutes: 15 strategy: @@ -209,14 +225,6 @@ jobs: fetch-depth: 0 - name: Mark the repository as safe for Git run: git config --global --add safe.directory $GITHUB_WORKSPACE - - name: Check generated matcher hook documentation - run: | - python3 $GITHUB_WORKSPACE/doc/generate_matcher_hook_compat.py \ - --matcher-c $GITHUB_WORKSPACE/src/libbpfilter/matcher.c \ - --matcher-h $GITHUB_WORKSPACE/src/libbpfilter/include/bpfilter/matcher.h \ - --hook-h $GITHUB_WORKSPACE/src/libbpfilter/include/bpfilter/hook.h \ - --output $GITHUB_WORKSPACE/doc/usage/_generated/bfcli_matcher_hook_compatibility.rst \ - --check - name: Configure the build run: cmake -S $GITHUB_WORKSPACE -B $GITHUB_WORKSPACE/build - name: Restore the cached tests results diff --git a/doc/generate_matcher_hook_compat.py b/doc/generate_matcher_hook_compat.py index 4dc054b81..dd14f04b6 100644 --- a/doc/generate_matcher_hook_compat.py +++ b/doc/generate_matcher_hook_compat.py @@ -4,8 +4,8 @@ """Generate the bfcli matcher-to-hook compatibility table. The table is derived from the matcher metadata declared in -src/libbpfilter/matcher.c. Each matcher's supported hooks are computed from -the inverse of its `unsupported_hooks` bitmask. +src/libbpfilter/matcher.c. Each matcher's unsupported hooks are extracted +from its `unsupported_hooks` bitmask. """ from __future__ import annotations @@ -175,11 +175,23 @@ def _extract_unsupported_hooks(block: str) -> list[str]: ] -def _expand_hooks(tokens: list[str], macros: dict[str, list[str]]) -> list[str]: +def _expand_hooks( + tokens: list[str], + macros: dict[str, list[str]], + seen: set[str] | None = None, +) -> list[str]: + if seen is None: + seen = set() + expanded: list[str] = [] for token in tokens: if token in macros: - expanded.extend(_expand_hooks(macros[token], macros)) + if token in seen: + raise ValueError(f"circular hook macro reference: {token}") + + expanded.extend( + _expand_hooks(macros[token], macros, seen | {token}) + ) else: expanded.append(token) @@ -295,6 +307,16 @@ def main() -> int: if args.check: rendered = render(args.matcher_c, args.matcher_h, args.hook_h) + if not args.output.exists(): + print( + ( + f"{args.output} does not exist; run without --check " + "to generate it" + ), + file=sys.stderr, + ) + return 1 + current = _read(args.output) if current != rendered: print(f"{args.output} is out of date", file=sys.stderr) diff --git a/doc/usage/bfcli.rst b/doc/usage/bfcli.rst index 6dbf200b3..2bf485266 100644 --- a/doc/usage/bfcli.rst +++ b/doc/usage/bfcli.rst @@ -521,6 +521,7 @@ With: - ``$PAYLOAD``: payload to compare to the processed network packet. The exact payload format depends on ``$TYPE``. + Meta ####