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