xref: /linux/tools/testing/selftests/bpf/test_bpftool.py (revision 03ab8e6297acd1bc0eedaa050e2a1635c576fd11)
173633274SMichal Rostecki# SPDX-License-Identifier: GPL-2.0
273633274SMichal Rostecki# Copyright (c) 2020 SUSE LLC.
373633274SMichal Rostecki
473633274SMichal Rosteckiimport collections
573633274SMichal Rosteckiimport functools
673633274SMichal Rosteckiimport json
773633274SMichal Rosteckiimport os
873633274SMichal Rosteckiimport socket
973633274SMichal Rosteckiimport subprocess
1073633274SMichal Rosteckiimport unittest
1173633274SMichal Rostecki
1273633274SMichal Rostecki
1373633274SMichal Rostecki# Add the source tree of bpftool and /usr/local/sbin to PATH
1473633274SMichal Rosteckicur_dir = os.path.dirname(os.path.realpath(__file__))
1573633274SMichal Rosteckibpftool_dir = os.path.abspath(os.path.join(cur_dir, "..", "..", "..", "..",
1673633274SMichal Rostecki                                           "tools", "bpf", "bpftool"))
1773633274SMichal Rosteckios.environ["PATH"] = bpftool_dir + ":/usr/local/sbin:" + os.environ["PATH"]
1873633274SMichal Rostecki
1973633274SMichal Rostecki
2073633274SMichal Rosteckiclass IfaceNotFoundError(Exception):
2173633274SMichal Rostecki    pass
2273633274SMichal Rostecki
2373633274SMichal Rostecki
2473633274SMichal Rosteckiclass UnprivilegedUserError(Exception):
2573633274SMichal Rostecki    pass
2673633274SMichal Rostecki
2773633274SMichal Rostecki
2873633274SMichal Rosteckidef _bpftool(args, json=True):
2973633274SMichal Rostecki    _args = ["bpftool"]
3073633274SMichal Rostecki    if json:
3173633274SMichal Rostecki        _args.append("-j")
3273633274SMichal Rostecki    _args.extend(args)
3373633274SMichal Rostecki
3473633274SMichal Rostecki    return subprocess.check_output(_args)
3573633274SMichal Rostecki
3673633274SMichal Rostecki
3773633274SMichal Rosteckidef bpftool(args):
3873633274SMichal Rostecki    return _bpftool(args, json=False).decode("utf-8")
3973633274SMichal Rostecki
4073633274SMichal Rostecki
4173633274SMichal Rosteckidef bpftool_json(args):
4273633274SMichal Rostecki    res = _bpftool(args)
4373633274SMichal Rostecki    return json.loads(res)
4473633274SMichal Rostecki
4573633274SMichal Rostecki
4673633274SMichal Rosteckidef get_default_iface():
4773633274SMichal Rostecki    for iface in socket.if_nameindex():
4873633274SMichal Rostecki        if iface[1] != "lo":
4973633274SMichal Rostecki            return iface[1]
5073633274SMichal Rostecki    raise IfaceNotFoundError("Could not find any network interface to probe")
5173633274SMichal Rostecki
5273633274SMichal Rostecki
5373633274SMichal Rosteckidef default_iface(f):
5473633274SMichal Rostecki    @functools.wraps(f)
5573633274SMichal Rostecki    def wrapper(*args, **kwargs):
5673633274SMichal Rostecki        iface = get_default_iface()
5773633274SMichal Rostecki        return f(*args, iface, **kwargs)
5873633274SMichal Rostecki    return wrapper
5973633274SMichal Rostecki
60*4190c299SDave MarchevskyDMESG_EMITTING_HELPERS = [
61*4190c299SDave Marchevsky        "bpf_probe_write_user",
62*4190c299SDave Marchevsky        "bpf_trace_printk",
63*4190c299SDave Marchevsky        "bpf_trace_vprintk",
64*4190c299SDave Marchevsky    ]
6573633274SMichal Rostecki
6673633274SMichal Rosteckiclass TestBpftool(unittest.TestCase):
6773633274SMichal Rostecki    @classmethod
6873633274SMichal Rostecki    def setUpClass(cls):
6973633274SMichal Rostecki        if os.getuid() != 0:
7073633274SMichal Rostecki            raise UnprivilegedUserError(
7173633274SMichal Rostecki                "This test suite needs root privileges")
7273633274SMichal Rostecki
7373633274SMichal Rostecki    @default_iface
7473633274SMichal Rostecki    def test_feature_dev_json(self, iface):
75*4190c299SDave Marchevsky        unexpected_helpers = DMESG_EMITTING_HELPERS
7673633274SMichal Rostecki        expected_keys = [
7773633274SMichal Rostecki            "syscall_config",
7873633274SMichal Rostecki            "program_types",
7973633274SMichal Rostecki            "map_types",
8073633274SMichal Rostecki            "helpers",
8173633274SMichal Rostecki            "misc",
8273633274SMichal Rostecki        ]
8373633274SMichal Rostecki
8473633274SMichal Rostecki        res = bpftool_json(["feature", "probe", "dev", iface])
8573633274SMichal Rostecki        # Check if the result has all expected keys.
8673633274SMichal Rostecki        self.assertCountEqual(res.keys(), expected_keys)
8773633274SMichal Rostecki        # Check if unexpected helpers are not included in helpers probes
8873633274SMichal Rostecki        # result.
8973633274SMichal Rostecki        for helpers in res["helpers"].values():
9073633274SMichal Rostecki            for unexpected_helper in unexpected_helpers:
9173633274SMichal Rostecki                self.assertNotIn(unexpected_helper, helpers)
9273633274SMichal Rostecki
9373633274SMichal Rostecki    def test_feature_kernel(self):
9473633274SMichal Rostecki        test_cases = [
9573633274SMichal Rostecki            bpftool_json(["feature", "probe", "kernel"]),
9673633274SMichal Rostecki            bpftool_json(["feature", "probe"]),
9773633274SMichal Rostecki            bpftool_json(["feature"]),
9873633274SMichal Rostecki        ]
99*4190c299SDave Marchevsky        unexpected_helpers = DMESG_EMITTING_HELPERS
10073633274SMichal Rostecki        expected_keys = [
10173633274SMichal Rostecki            "syscall_config",
10273633274SMichal Rostecki            "system_config",
10373633274SMichal Rostecki            "program_types",
10473633274SMichal Rostecki            "map_types",
10573633274SMichal Rostecki            "helpers",
10673633274SMichal Rostecki            "misc",
10773633274SMichal Rostecki        ]
10873633274SMichal Rostecki
10973633274SMichal Rostecki        for tc in test_cases:
11073633274SMichal Rostecki            # Check if the result has all expected keys.
11173633274SMichal Rostecki            self.assertCountEqual(tc.keys(), expected_keys)
11273633274SMichal Rostecki            # Check if unexpected helpers are not included in helpers probes
11373633274SMichal Rostecki            # result.
11473633274SMichal Rostecki            for helpers in tc["helpers"].values():
11573633274SMichal Rostecki                for unexpected_helper in unexpected_helpers:
11673633274SMichal Rostecki                    self.assertNotIn(unexpected_helper, helpers)
11773633274SMichal Rostecki
11873633274SMichal Rostecki    def test_feature_kernel_full(self):
11973633274SMichal Rostecki        test_cases = [
12073633274SMichal Rostecki            bpftool_json(["feature", "probe", "kernel", "full"]),
12173633274SMichal Rostecki            bpftool_json(["feature", "probe", "full"]),
12273633274SMichal Rostecki        ]
123*4190c299SDave Marchevsky        expected_helpers = DMESG_EMITTING_HELPERS
12473633274SMichal Rostecki
12573633274SMichal Rostecki        for tc in test_cases:
12673633274SMichal Rostecki            # Check if expected helpers are included at least once in any
12773633274SMichal Rostecki            # helpers list for any program type. Unfortunately we cannot assume
12873633274SMichal Rostecki            # that they will be included in all program types or a specific
12973633274SMichal Rostecki            # subset of programs. It depends on the kernel version and
13073633274SMichal Rostecki            # configuration.
13173633274SMichal Rostecki            found_helpers = False
13273633274SMichal Rostecki
13373633274SMichal Rostecki            for helpers in tc["helpers"].values():
13473633274SMichal Rostecki                if all(expected_helper in helpers
13573633274SMichal Rostecki                       for expected_helper in expected_helpers):
13673633274SMichal Rostecki                    found_helpers = True
13773633274SMichal Rostecki                    break
13873633274SMichal Rostecki
13973633274SMichal Rostecki            self.assertTrue(found_helpers)
14073633274SMichal Rostecki
14173633274SMichal Rostecki    def test_feature_kernel_full_vs_not_full(self):
14273633274SMichal Rostecki        full_res = bpftool_json(["feature", "probe", "full"])
14373633274SMichal Rostecki        not_full_res = bpftool_json(["feature", "probe"])
14473633274SMichal Rostecki        not_full_set = set()
14573633274SMichal Rostecki        full_set = set()
14673633274SMichal Rostecki
14773633274SMichal Rostecki        for helpers in full_res["helpers"].values():
14873633274SMichal Rostecki            for helper in helpers:
14973633274SMichal Rostecki                full_set.add(helper)
15073633274SMichal Rostecki
15173633274SMichal Rostecki        for helpers in not_full_res["helpers"].values():
15273633274SMichal Rostecki            for helper in helpers:
15373633274SMichal Rostecki                not_full_set.add(helper)
15473633274SMichal Rostecki
15573633274SMichal Rostecki        self.assertCountEqual(full_set - not_full_set,
156*4190c299SDave Marchevsky                              set(DMESG_EMITTING_HELPERS))
15773633274SMichal Rostecki        self.assertCountEqual(not_full_set - full_set, set())
15873633274SMichal Rostecki
15973633274SMichal Rostecki    def test_feature_macros(self):
16073633274SMichal Rostecki        expected_patterns = [
16173633274SMichal Rostecki            r"/\*\*\* System call availability \*\*\*/",
16273633274SMichal Rostecki            r"#define HAVE_BPF_SYSCALL",
16373633274SMichal Rostecki            r"/\*\*\* eBPF program types \*\*\*/",
16473633274SMichal Rostecki            r"#define HAVE.*PROG_TYPE",
16573633274SMichal Rostecki            r"/\*\*\* eBPF map types \*\*\*/",
16673633274SMichal Rostecki            r"#define HAVE.*MAP_TYPE",
16773633274SMichal Rostecki            r"/\*\*\* eBPF helper functions \*\*\*/",
16873633274SMichal Rostecki            r"#define HAVE.*HELPER",
16973633274SMichal Rostecki            r"/\*\*\* eBPF misc features \*\*\*/",
17073633274SMichal Rostecki        ]
17173633274SMichal Rostecki
17273633274SMichal Rostecki        res = bpftool(["feature", "probe", "macros"])
17373633274SMichal Rostecki        for pattern in expected_patterns:
17473633274SMichal Rostecki            self.assertRegex(res, pattern)
175