xref: /linux/tools/testing/selftests/bpf/test_bpftool.py (revision 736332740e295d9b6fc524f0447448f6089911d9)
1*73633274SMichal Rostecki# SPDX-License-Identifier: GPL-2.0
2*73633274SMichal Rostecki# Copyright (c) 2020 SUSE LLC.
3*73633274SMichal Rostecki
4*73633274SMichal Rosteckiimport collections
5*73633274SMichal Rosteckiimport functools
6*73633274SMichal Rosteckiimport json
7*73633274SMichal Rosteckiimport os
8*73633274SMichal Rosteckiimport socket
9*73633274SMichal Rosteckiimport subprocess
10*73633274SMichal Rosteckiimport unittest
11*73633274SMichal Rostecki
12*73633274SMichal Rostecki
13*73633274SMichal Rostecki# Add the source tree of bpftool and /usr/local/sbin to PATH
14*73633274SMichal Rosteckicur_dir = os.path.dirname(os.path.realpath(__file__))
15*73633274SMichal Rosteckibpftool_dir = os.path.abspath(os.path.join(cur_dir, "..", "..", "..", "..",
16*73633274SMichal Rostecki                                           "tools", "bpf", "bpftool"))
17*73633274SMichal Rosteckios.environ["PATH"] = bpftool_dir + ":/usr/local/sbin:" + os.environ["PATH"]
18*73633274SMichal Rostecki
19*73633274SMichal Rostecki
20*73633274SMichal Rosteckiclass IfaceNotFoundError(Exception):
21*73633274SMichal Rostecki    pass
22*73633274SMichal Rostecki
23*73633274SMichal Rostecki
24*73633274SMichal Rosteckiclass UnprivilegedUserError(Exception):
25*73633274SMichal Rostecki    pass
26*73633274SMichal Rostecki
27*73633274SMichal Rostecki
28*73633274SMichal Rosteckidef _bpftool(args, json=True):
29*73633274SMichal Rostecki    _args = ["bpftool"]
30*73633274SMichal Rostecki    if json:
31*73633274SMichal Rostecki        _args.append("-j")
32*73633274SMichal Rostecki    _args.extend(args)
33*73633274SMichal Rostecki
34*73633274SMichal Rostecki    return subprocess.check_output(_args)
35*73633274SMichal Rostecki
36*73633274SMichal Rostecki
37*73633274SMichal Rosteckidef bpftool(args):
38*73633274SMichal Rostecki    return _bpftool(args, json=False).decode("utf-8")
39*73633274SMichal Rostecki
40*73633274SMichal Rostecki
41*73633274SMichal Rosteckidef bpftool_json(args):
42*73633274SMichal Rostecki    res = _bpftool(args)
43*73633274SMichal Rostecki    return json.loads(res)
44*73633274SMichal Rostecki
45*73633274SMichal Rostecki
46*73633274SMichal Rosteckidef get_default_iface():
47*73633274SMichal Rostecki    for iface in socket.if_nameindex():
48*73633274SMichal Rostecki        if iface[1] != "lo":
49*73633274SMichal Rostecki            return iface[1]
50*73633274SMichal Rostecki    raise IfaceNotFoundError("Could not find any network interface to probe")
51*73633274SMichal Rostecki
52*73633274SMichal Rostecki
53*73633274SMichal Rosteckidef default_iface(f):
54*73633274SMichal Rostecki    @functools.wraps(f)
55*73633274SMichal Rostecki    def wrapper(*args, **kwargs):
56*73633274SMichal Rostecki        iface = get_default_iface()
57*73633274SMichal Rostecki        return f(*args, iface, **kwargs)
58*73633274SMichal Rostecki    return wrapper
59*73633274SMichal Rostecki
60*73633274SMichal Rostecki
61*73633274SMichal Rosteckiclass TestBpftool(unittest.TestCase):
62*73633274SMichal Rostecki    @classmethod
63*73633274SMichal Rostecki    def setUpClass(cls):
64*73633274SMichal Rostecki        if os.getuid() != 0:
65*73633274SMichal Rostecki            raise UnprivilegedUserError(
66*73633274SMichal Rostecki                "This test suite needs root privileges")
67*73633274SMichal Rostecki
68*73633274SMichal Rostecki    @default_iface
69*73633274SMichal Rostecki    def test_feature_dev_json(self, iface):
70*73633274SMichal Rostecki        unexpected_helpers = [
71*73633274SMichal Rostecki            "bpf_probe_write_user",
72*73633274SMichal Rostecki            "bpf_trace_printk",
73*73633274SMichal Rostecki        ]
74*73633274SMichal Rostecki        expected_keys = [
75*73633274SMichal Rostecki            "syscall_config",
76*73633274SMichal Rostecki            "program_types",
77*73633274SMichal Rostecki            "map_types",
78*73633274SMichal Rostecki            "helpers",
79*73633274SMichal Rostecki            "misc",
80*73633274SMichal Rostecki        ]
81*73633274SMichal Rostecki
82*73633274SMichal Rostecki        res = bpftool_json(["feature", "probe", "dev", iface])
83*73633274SMichal Rostecki        # Check if the result has all expected keys.
84*73633274SMichal Rostecki        self.assertCountEqual(res.keys(), expected_keys)
85*73633274SMichal Rostecki        # Check if unexpected helpers are not included in helpers probes
86*73633274SMichal Rostecki        # result.
87*73633274SMichal Rostecki        for helpers in res["helpers"].values():
88*73633274SMichal Rostecki            for unexpected_helper in unexpected_helpers:
89*73633274SMichal Rostecki                self.assertNotIn(unexpected_helper, helpers)
90*73633274SMichal Rostecki
91*73633274SMichal Rostecki    def test_feature_kernel(self):
92*73633274SMichal Rostecki        test_cases = [
93*73633274SMichal Rostecki            bpftool_json(["feature", "probe", "kernel"]),
94*73633274SMichal Rostecki            bpftool_json(["feature", "probe"]),
95*73633274SMichal Rostecki            bpftool_json(["feature"]),
96*73633274SMichal Rostecki        ]
97*73633274SMichal Rostecki        unexpected_helpers = [
98*73633274SMichal Rostecki            "bpf_probe_write_user",
99*73633274SMichal Rostecki            "bpf_trace_printk",
100*73633274SMichal Rostecki        ]
101*73633274SMichal Rostecki        expected_keys = [
102*73633274SMichal Rostecki            "syscall_config",
103*73633274SMichal Rostecki            "system_config",
104*73633274SMichal Rostecki            "program_types",
105*73633274SMichal Rostecki            "map_types",
106*73633274SMichal Rostecki            "helpers",
107*73633274SMichal Rostecki            "misc",
108*73633274SMichal Rostecki        ]
109*73633274SMichal Rostecki
110*73633274SMichal Rostecki        for tc in test_cases:
111*73633274SMichal Rostecki            # Check if the result has all expected keys.
112*73633274SMichal Rostecki            self.assertCountEqual(tc.keys(), expected_keys)
113*73633274SMichal Rostecki            # Check if unexpected helpers are not included in helpers probes
114*73633274SMichal Rostecki            # result.
115*73633274SMichal Rostecki            for helpers in tc["helpers"].values():
116*73633274SMichal Rostecki                for unexpected_helper in unexpected_helpers:
117*73633274SMichal Rostecki                    self.assertNotIn(unexpected_helper, helpers)
118*73633274SMichal Rostecki
119*73633274SMichal Rostecki    def test_feature_kernel_full(self):
120*73633274SMichal Rostecki        test_cases = [
121*73633274SMichal Rostecki            bpftool_json(["feature", "probe", "kernel", "full"]),
122*73633274SMichal Rostecki            bpftool_json(["feature", "probe", "full"]),
123*73633274SMichal Rostecki        ]
124*73633274SMichal Rostecki        expected_helpers = [
125*73633274SMichal Rostecki            "bpf_probe_write_user",
126*73633274SMichal Rostecki            "bpf_trace_printk",
127*73633274SMichal Rostecki        ]
128*73633274SMichal Rostecki
129*73633274SMichal Rostecki        for tc in test_cases:
130*73633274SMichal Rostecki            # Check if expected helpers are included at least once in any
131*73633274SMichal Rostecki            # helpers list for any program type. Unfortunately we cannot assume
132*73633274SMichal Rostecki            # that they will be included in all program types or a specific
133*73633274SMichal Rostecki            # subset of programs. It depends on the kernel version and
134*73633274SMichal Rostecki            # configuration.
135*73633274SMichal Rostecki            found_helpers = False
136*73633274SMichal Rostecki
137*73633274SMichal Rostecki            for helpers in tc["helpers"].values():
138*73633274SMichal Rostecki                if all(expected_helper in helpers
139*73633274SMichal Rostecki                       for expected_helper in expected_helpers):
140*73633274SMichal Rostecki                    found_helpers = True
141*73633274SMichal Rostecki                    break
142*73633274SMichal Rostecki
143*73633274SMichal Rostecki            self.assertTrue(found_helpers)
144*73633274SMichal Rostecki
145*73633274SMichal Rostecki    def test_feature_kernel_full_vs_not_full(self):
146*73633274SMichal Rostecki        full_res = bpftool_json(["feature", "probe", "full"])
147*73633274SMichal Rostecki        not_full_res = bpftool_json(["feature", "probe"])
148*73633274SMichal Rostecki        not_full_set = set()
149*73633274SMichal Rostecki        full_set = set()
150*73633274SMichal Rostecki
151*73633274SMichal Rostecki        for helpers in full_res["helpers"].values():
152*73633274SMichal Rostecki            for helper in helpers:
153*73633274SMichal Rostecki                full_set.add(helper)
154*73633274SMichal Rostecki
155*73633274SMichal Rostecki        for helpers in not_full_res["helpers"].values():
156*73633274SMichal Rostecki            for helper in helpers:
157*73633274SMichal Rostecki                not_full_set.add(helper)
158*73633274SMichal Rostecki
159*73633274SMichal Rostecki        self.assertCountEqual(full_set - not_full_set,
160*73633274SMichal Rostecki                                {"bpf_probe_write_user", "bpf_trace_printk"})
161*73633274SMichal Rostecki        self.assertCountEqual(not_full_set - full_set, set())
162*73633274SMichal Rostecki
163*73633274SMichal Rostecki    def test_feature_macros(self):
164*73633274SMichal Rostecki        expected_patterns = [
165*73633274SMichal Rostecki            r"/\*\*\* System call availability \*\*\*/",
166*73633274SMichal Rostecki            r"#define HAVE_BPF_SYSCALL",
167*73633274SMichal Rostecki            r"/\*\*\* eBPF program types \*\*\*/",
168*73633274SMichal Rostecki            r"#define HAVE.*PROG_TYPE",
169*73633274SMichal Rostecki            r"/\*\*\* eBPF map types \*\*\*/",
170*73633274SMichal Rostecki            r"#define HAVE.*MAP_TYPE",
171*73633274SMichal Rostecki            r"/\*\*\* eBPF helper functions \*\*\*/",
172*73633274SMichal Rostecki            r"#define HAVE.*HELPER",
173*73633274SMichal Rostecki            r"/\*\*\* eBPF misc features \*\*\*/",
174*73633274SMichal Rostecki        ]
175*73633274SMichal Rostecki
176*73633274SMichal Rostecki        res = bpftool(["feature", "probe", "macros"])
177*73633274SMichal Rostecki        for pattern in expected_patterns:
178*73633274SMichal Rostecki            self.assertRegex(res, pattern)
179