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